Object Providers

The standard mechanism for modules to expose cached data access to the rest of the application.

Object Providers

In the JopiJS architecture, Object Providers are the standard mechanism for modules to expose data retrieving logic. They serve as a bridge between your raw data sources (Database, API, CMS) and the rest of the application.

Instead of writing ad-hoc functions like getProductById in every consumer, you define an Object Provider within your module. JopiJS then exposes it to the entire workspace.


Why use Object Providers?

  1. Encapsulation: The internal logic of how to fetch a product (SQL query, API call, etc.) is hidden within the owning module. Other modules just ask for the data.
  2. Centralization: All data access logic for a specific entity is kept in one place.
  3. Uniform API: Every data entity in your system is accessed the same way (get, set), simplifying the learning curve for new developers.

Creating a Provider

Object Providers are located in the @alias/objectProviders directory of your module. The folder name dictates the public name of the provider.

1. File Structure

To create a provider named shop.product, create the following file:

src/
└── mod_shop/
    └── @alias/
        └── objectProviders/
            └── shop.product/   <-- Provider Name
                └── index.ts    <-- Implementation

2. Implementation

A provider is an object that implements the ObjectProvider interface, exposing a get method.

src/mod_shop/@alias/objectProviders/shop.product/index.ts
import { db } from "@/lib/db";
import type { ObjectProvider } from "jopijs";

export default <ObjectProvider>{
    async get({ id }: { id?: string }) {
        if (!id) return undefined;
        
        const product = await db.query("SELECT * FROM products WHERE id = ?", [id]);
        
        return product;
    }
}

3. Flexible Key Usage

The get method receives a keys object (the first argument), which you can define to suit your needs.

  • shopCategory.get({}) -> Returns all categories.
  • shopCategory.get({ slug: "bouquets" }) -> Returns only the bouquets category.
  • shopCategory.get({ lang: "fr-fr" }) -> Returns categories in French.
src/mod_shop/@alias/objectProviders/shop.category/index.ts
import type { ObjectProvider } from "jopijs";

type CategoryKeys = { slug?: string, lang?: string };

export default <ObjectProvider<CategoryKeys, any>>{
    async get(keys) {
        const lang = keys.lang || "en-us";

        // Strategy: if no slug, return everything
        if (!keys.slug) {
            const all = await db.query("SELECT * FROM categories WHERE lang = ?", [lang]);
            return all;
        }
        
        // Strategy: if slug, return specific item
        const specific = await db.query("SELECT * FROM categories WHERE slug = ? AND lang = ?", [keys.slug, lang]);
        return specific;
    }
}

Consuming Data

Once defined, the provider can be imported and used anywhere in your application (other modules, API routes, or pageData.ts).

JopiJS automatically links the provider to the @objectProviders/ alias.

src/mod_website/@routes/cart/pageData.ts
// Import using the workspace alias system
import shopProduct from "@/objectProviders/shop.product";

export default {
    async getDataForCache({ req }) {
        // Simple usage
        const item = await shopProduct.get({ id: "123" });
        
        // Batch usage (parallel fetching)
        const items = await Promise.all([
            shopProduct.get({ id: "101" }),
            shopProduct.get({ id: "102" })
        ]);

        return { items };
    }
}

API & Features

The imported provider object exposes the method(s) you implemented.

MethodDescription
get(keys)Returns the value associated with the keys.
set(keys, value)(Optional) Updates the value.
delete(keys)(Optional) Deletes the value.

Module Boundaries

Object Providers are the preferred way to share data between modules.

  • Good: The mod_invoice module uses shop.order.get({ id }) to generate a PDF.
  • Bad: The mod_invoice module imports db and writes its own SQL query to select orders.

This ensures that if you change your database schema (e.g., in mod_shop), you only edit the provider, and mod_invoice remains unaffected.