Installation

Install Dependencies via yarn

git clone https://github.com/merchisdk/merchi_sdk_ts.git
cd merchi_sdk_ts
yarn

Install Dependencies via npm

git clone https://github.com/merchisdk/merchi_sdk_ts.git
cd merchi_sdk_ts
npm install

Important: Authentication

To access any API endpoints, authentication is required.

Generating an API Key

You can generate your API key in your developer dashboard. This key will serve as your unique identifier when making API requests.

Including the API Key

Add the generated API key to all requests as a GET parameter. Ensure it is included in the query string for every API call.

For example:

const merchi = new Merchi("your-session-token");

Key Requirement:

Important: Nothing will function unless you include a valid API key in your request. Make sure to keep your key secure and avoid sharing it publicly.


The Merchi object

The Merchi class serves as the central access point for all functionality provided by the Merchi TypeScript SDK. To begin, create an instance of this class:

import { Merchi } from "merchi_sdk_ts";

// Create an instance of the Merchi SDK
const merchi = new Merchi();

Import Path

If you have placed the TypeScript SDK in a non-standard directory, you may need to adjust the import path accordingly. For instance, if the SDK resides in merchisdk/typescript/, ensure your import path aligns with your project’s directory structure.


Compiling

Compiling TypeScript in React and Next.js Applications

For modern React and Next.js projects, manual compilation with a tool like tsc is not required. These frameworks come with built-in support for TypeScript and handle compilation automatically. Below is a streamlined explanation of how TypeScript works in Next.js and React projects:

Using TypeScript in Next.js

  1. Automatic TypeScript Setup When you add .ts or .tsx files to a Next.js project, the framework automatically enables TypeScript support. If TypeScript isn’t installed in your project, you’ll be prompted to install the necessary dependencies.

    To set up manually:

    npm install --save-dev typescript @types/react @types/node
    
  2. Create a TypeScript File Replace or create a file named index.tsx in the pages/ folder. For example:

     const HomePage = () => {
      return <h1>Hello, TypeScript in Next.js! <h1/>;
    };
       
    export default HomePage;
    
  3. Run the Development Server Start the Next.js app:

    npm run dev
    

    Next.js will compile your TypeScript files into JavaScript during development and production builds (npm run build).

  4. Compiled Output Next.js automatically bundles your code for the browser. There’s no need to include compiled JavaScript files manually with <script> tags.

Using TypeScript in Standalone React Projects

If you’re working on a standalone React project without Next.js, you can still use TypeScript with the same simplicity.

  1. Install TypeScript in Your React App If you’re creating a new React app, you can initialize it with TypeScript:

    npx create-react-app my-app --template typescript
    
  2. Write and Compile TypeScript Code Add your TypeScript files (.ts or .tsx) as needed. React’s tooling (such as Webpack) will handle the compilation process automatically.

Advanced Compilation (Optional)

If you’re working outside of these frameworks or need to compile TypeScript manually for specific tasks, you can still use the TypeScript compiler directly:

  1. Compile TypeScript Code Assuming you have a file named index.ts:

    npx tsc index.ts
    

    This will generate an index.js file in the same directory.

  2. Include Compiled Output in HTML You can use the compiled JavaScript output in an HTML file as follows:

    <script type="application/javascript" src="index.js"></script>
    

Listing Entities

Merchi organizes entities into distinct types, which can be considered similar to REST resources. For each entity type, there are typically many specific instances available. After initializing the merchi object, you can retrieve an array of entities using the list method.

For example, to fetch a list of categories in the system (which are used to group products), you can use the following code:

Example: Listing categories

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi();
const categories: any[] = [];

// Fetch categories using the 'list' method on the 'Category' entity
await merchi.Category.list()
  .then((result: { items: any[] }) => {
    
    for (const category of result.items) {
      categories.push(category.toJson());
    }
    
    console.log("Categories:", categories);
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Example: Listing Invoices

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi("your-session-token"); // Authentication Required
const parameters = { inDomain: 18 };

try {
  const result = await merchi.Invoice.list(parameters);
    
  if (!result.items || result.items.length === 0) {
    console.log("Log - No invoices found");
    return;
  }

  for (const invoice of result.items) {
    const invoiceJSON = invoice.toJson();
    console.log(`Log - Invoice ID: ${invoiceJSON.id}`);
  }
} catch (error) {
  console.error("Fetching Error:", error);
}

console.log("Log - Finished fetching invoices");

Fetching a Single Entity

In Merchi, almost every entity is uniquely identified by its id. Once you have the id of an entity, you can fetch that specific entity using the get static method.

Example: Fetching a job

import { Merchi } from "merchi_sdk_ts";
const merchi = new Merchi("your-session-token"); // Authentication Required
const jobId = 42;  
const embed = {
  domain: {},
};

await merchi.Job.get(jobId, { embed: embed })
  .then((job: any) => {
    console.log("Job: " + JSON.stringify(job));
  })
  .catch((error: any) => {
    console.error("Error:", error);
  });

Example: Fetching a category

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi();

// Define the ID of the category you want to fetch
const categoryId = 42;

// Use the 'get' method to fetch the category by its ID
await merchi.Category.get(categoryId)
  .then((category: any) => {
    // Log or process the category's name
    console.log("Category Name:", category.toJson().name);
  })
  .catch((error: any) => {
    // Handle errors that occur during the fetch operation
    console.error("Error fetching category:", error);
  });

Creating a New Entity

New entities can be added to the system using the create method.

To create a new entity, follow these steps:

  1. Instantiate a new SDK object using the constructor provided by the Merchi object.
  2. Set the values for the new entity.
  3. Call the create method to save the entity to the Merchi system.

Note: Creating and editing objects locally in JavaScript/TypeScript has no effect on the server. The entity will only be stored in Merchi after you call create, which triggers a network request to the server.

Important: You do not need to manually assign an id to the entity. Merchi will automatically generate an id for the new object when it’s created.

Example: Creating single category

import { Merchi } from "merchi_sdk_ts";
const merchi = new Merchi("your-session-token");

const newCategory = new merchi.Category();
newCategory.name = "New Category Name";
// Call the 'create' method to store the new category in the system
newCategory
  .create()
  .then(() => {
    // Once the category is created, its ID is automatically generated
    console.log("The new category's ID is: " + newCategory.id);
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Editing an Entity

Existing entities can be edited using the save method.

To edit an entity:

  1. Modify the attributes of the object locally.
  2. Ensure that the id attribute is set (this is required for editing).
  3. Call the save method to update the entity in the system.

You can use the objects returned by the list or get methods to edit them, as these objects will already have the id populated. However, if you already know the id of the entity you wish to edit, you can directly specify the id without needing to fetch the entity from the server.

Example: Editing a category

import { Merchi } from "merchi_sdk_ts";
const merchi = new Merchi("your-session-token");
const categoryToEdit = new merchi.Category();

categoryToEdit.id = 42;
categoryToEdit.name = "New Name"; // make a correction to the name
categoryToEdit
  .save()
  .then(() => {
    console.log("ok, the category name was edited.");
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Deleting an Entity

Entities can be deleted via the delete method.

As with editing, if you know the id of the object, you do not have to fetch it before deleting.

Example: Deleting a category

import { Merchi } from "merchi_sdk_ts";
const merchi = new Merchi("your-session-token");
const categoryToDelete = new merchi.Category();
categoryToDelete.id = 42;
categoryToDelete
  .delete()
  .then(() => {
    console.log("ok, the category with id 42 was deleted.");
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Creating Nested entities

Entities in Merchi can have relationships with other entities. For instance, categories are always linked to a Domain, which is Merchi’s term for a store.

When creating an entity, you can associate a Domain object with it to specify which domain it should belong to. This relationship ensures that the new entity is correctly linked within the Merchi system.

Example: Creating a category with a domain

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi("your-session-token");
const newCategory = new merchi.Category();
const existingDomain = new merchi.Domain();
existingDomain.id = 42; // note: this id already exists
newCategory.domain = existingDomain;
newCategory.name = "vegetables";
newCategory
  .create()
  .then(() => {
    console.log("ok, new vegetables category created on domain 42");
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Embedding

By default, the get and list methods in Merchi only fetch scalar properties (e.g., strings, numbers, dates, etc.) of an entity. Nested entities (like relationships to other objects) are not automatically included. This behavior prevents excessive and unnecessary data transfer, especially for deeply nested or cyclic references.

However, if you need specific nested entities, you can use the embed parameter to request them. You can define which nested entities to include and to what depth.


Handling null vs undefined

On a newly fetched category category.domain will be undefined, even if the category has a domain on the server. undefined means that the domain has not been included, or updated locally. If the category did not have a domain at all, category.domain would instead be null.

This distinction allows you to determine whether the data was not fetched or the relationship does not exist.

Example: Fetching a Category with Embedded Domain

The following example uses the embed parameter to include the domain entity when fetching a Category. The embed parameter works with both get and list methods.

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi();
const categoryId = 42;
const embed = {
  domain: {},
};

merchi.Category.get(categoryId, { embed: embed })
  .then((category: any) => {
    const categoryDomainEmail = category.domain.emailDomain;
    console.log(
      `The email of the domain of category ${categoryId} is: ` + categoryDomain
    );
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Nested Editing

The save method in Merchi not only saves changes to the entity itself but also automatically propagates any local changes made to attached nested entities.

This allows you to update related entities simultaneously, avoiding the need for multiple save calls.

Example: Updating a Store (Domain) through a Category

In the following example, we update the name of a Domain (store) that is linked to a Category object:

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi("your-session-token");
const category = new merchi.Category();
category.id = 12; // assume we already have the entity id's
const domain = new merchi.Domain();
domain.id = 42;
category.domain = domain;
domain.domain = "new-store.example.com"; // newly chosen name
category
  .save()
  .then(() => {
    console.log("ok, the category and domain are both updated.");
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Types vs Constructors

In Merchi’s TypeScript SDK, entity types and their constructors are intentionally separated to work around a TypeScript limitation. This separation ensures proper type checking and object instantiation.

When interacting with entities, you should always use the constructors attached to the Merchi object for creating or listing entities. The entity types, however, can be imported separately for static type declarations.

Key Points

  1. Entity Types: These are imported for type checking and are never directly instantiated.
  2. Constructors: These are accessed via the Merchi object and are responsible for creating and managing entities.
  3. Best Practice: Use the constructors from the Merchi object and reference the types for type annotations.

Example: Importing Entity Type and Using the Constructor

Here’s an example demonstrating the correct way to handle entity types and constructors:

import { Merchi } from "merchi_sdk_ts";
import { Category } from "merchi_sdk_ts/src/entities/category";

const merchi = new Merchi();
const category: Category = new merchi.Category();
category.name = "groceries";

console.log(`Category name: ${category.name}`);

Pagination

Handling Large Entity Lists with Pagination

In systems with tens or even hundreds of thousands of entities, it is inefficient and impractical to fetch all results at once. Instead, the list method in the Merchi SDK returns results in manageable “pages.” This paginated approach ensures efficient data retrieval and avoids overwhelming the server or client application.

Pagination Parameters

The list method offers two key options for controlling pagination:

  1. limit

    • Specifies the maximum number of results to include in a single page.
    • If not provided, it defaults to 25.
    • The actual number of results returned might be further restricted by backend-enforced limits or the total number of available entities.

    Example: A limit of 10 will return up to 10 entities per page.

  2. offset

    • Determines how many entities to skip before starting to return results.
    • Defaults to 0 if not specified, meaning no results are skipped.
    • When combined with limit, you can access subsequent pages.

    Example:

    • Setting limit to 10 and offset to 20 will fetch the third page of results (entities 21–30).

Pagination Metadata

When calling list, the response includes a metadata object (result.metadata) that provides information about the query results:

This metadata is essential for building robust pagination mechanisms and validating the completeness of fetched data.

Example: Basic Pagination Usage

The following example retrieves the second page of Category entities, with 10 results per page:

pagination

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi();
merchi.Category.list({ offset: 10, limit: 10 })
  .then((result: any) => {
    const categories = result.items;
    const info = result.metadata;
    console.log("Categories returned: " + info.count);
    console.assert(categories.length === info.count, "oh no!");
    console.log("Categories available: " + info.available);
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Searching

In addition to pagination, the list method allows you to search or filter entities using the q parameter. This parameter enables keyword-based searches across multiple fields, such as names, descriptions, or even related categories.

How the q Parameter Works

Example: Searching for Products

The following example demonstrates how to search for products with a specific keyword, such as “egg”:

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi();
const query = "product name";
merchi.Product.list({ q: query, embed: embedProduct, limit: 25 })
  .then((result: any) => {
    console.log("Search results for: " + query);
    for (const product of result.items) {
      console.log(product.name);
    }
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Filtering

In addition to basic keyword search using the q parameter, the Merchi SDK provides powerful filtering capabilities that allow for fine-grained control over query results. Filters enable you to restrict results based on specific criteria, making it easier to locate entities that meet your exact needs.

Common Filtering Options

  1. inDomain: Limits results to entities associated with a specific domain. Example: Retrieve only the products belonging to “example.com.”
  2. tags: Restricts results to entities tagged with specific keywords. Example: Retrieve products tagged as "big" and "blue".
  3. Entity-Specific Filters: Each entity type supports its own unique set of filters.
    • Example: Categories might support filters for associated domains or parent-child relationships.

For a full list of available filters for a given entity, refer to the official Filtering Reference).

Example: Filtering by Tags

This example demonstrates how to retrieve products that have specific tags applied:

import { Merchi } from "merchi_sdk_ts";

const merchi = new Merchi();
merchi.Product.list({ tags: ["big", "blue"] })
  .then((result: any) => {
    console.log("Search results: ");
    for (const product of result.items) {
      console.log(product.name);
    }
  })
  .catch((error: any) => {
    console.error(
      `Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`
    );
  });

Error Handling in the Merchi SDK

Error handling is a critical part of any application, ensuring that issues are caught and dealt with appropriately. Since the Merchi SDK utilizes JavaScript Promises, errors are typically handled using catch() on a Promise. If an error occurs, the catch() method is triggered, providing an ApiError object that contains useful details about the error.

Error Object (ApiError)

The ApiError object is returned when an error occurs during an API call. This object contains several useful properties to help you understand and troubleshoot the issue:

Basic Error Handling Example

Here’s how to catch and handle errors when calling the Merchi API:

import { Merchi } from 'merchi_sdk_ts';
import { ApiError } from 'merchisdk/typescript/src/request';

const merchi = new Merchi();
merchi.Category.get(42)
  .then(() => {
    console.log("ok, got a category");
  })
  .catch((error: ApiError) => {
    console.error(`Error: ${error.errorCode} ${error.statusCode} ${error.errorMessage}`)
  });
});

Job Entity

There are three key functions (getQuote, deduct, and bulkDeduct) within the Job class. These methods allow you to interact with Merchi’s API to fetch quotes, deduct inventory, and manage job-related operations.

1. getQuote Method

Purpose

This method fetches a specialized order estimate (quote) from the API based on the current job’s details. It updates the job’s cost and other relevant fields using the fetched data.

Key Features

2. deduct Method

Purpose

This method deducts inventory items associated with the job. It sends a request to the /jobs/{jobId}/deduct/ endpoint, specifying the inventories to be deducted.

Key Features

3. bulkDeduct Method

Purpose

This method automates the process of deducting all matching inventories associated with the job. It simplifies the deduction operation by using the deduct method internally.

Key Features

Supporting Methods and Utilities

authenticatedFetch

Handles API calls by adding authentication tokens and other common parameters.

toFormData

Converts the job’s internal state into a format suitable for form submission.

fromJson

Updates the job’s fields using data fetched from the API.


Practical Workflow

Here’s a practical usage flow to demonstrate how these methods can work together:

  1. Fetch a Quote:

    const job = new merchi.Job();
    job.fromJson({'quantity': 10, 'cost': 0, product: {'id': 1}});
    job.getQuote().then(updatedJob => console.log('Updated cost:', updatedJob.cost));
    
  2. Deduct Specific Inventories:

    const inventory1 = new merchi.Inventory();
    inventory1.id = 1;
    const matchingInventory = new merchi.MatchingInventory();
    matchingInventory.inventory = inventory1;
       
    job.deduct([matchingInventory]).then(updatedJob => console.log('Deduction complete.'));
    
  3. Bulk Deduct All Inventories:

    job.matchingInventories = [matchingInventory];
    job.bulkDeduct().then(() => console.log('Bulk deduction complete.'));
    

Exploring Other Merchi SDK Entities

So far, we’ve worked with products, categories, and job but the Merchi SDK offers many other entities such as cart, invoice, and payment. These entities help manage different parts of your store, like cart info, order details, and payment status.

For a complete list of available entities, check out the API Reference.