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?
- 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.
- Centralization: All data access logic for a specific entity is kept in one place.
- 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 <-- Implementation2. Implementation
A provider is an object that implements the ObjectProvider interface, exposing a get method.
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.
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.
// 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.
| Method | Description |
|---|---|
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_invoicemodule usesshop.order.get({ id })to generate a PDF. - Bad: The
mod_invoicemodule importsdband 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.