Product
product is an item in the procurement catalog โ the thing a purchase order buys. It collapses a full category hierarchy into a single self-referencing parent relation, carries a price, an active flag, and optional tags.
// models/product.ts (definition, trimmed of the doc comment)
import { coreApp } from "../mod.ts";
import {
boolean, defaulted, number, optional,
type RelationDataType, type RelationSortOrderType, string,
} from "lesan";
import { createUpdateAt } from "@lib";
import { product_excludes, tag_excludes } from "./excludes.ts";
export const product_pure = {
name: string(),
code: string(),
price: defaulted(number(), 0),
unit: optional(string()),
active: defaulted(boolean(), true),
description: optional(string()),
...createUpdateAt,
};
export const product_relations = {
parent: {
schemaName: "product",
type: "single" as RelationDataType,
optional: true,
excludes: product_excludes,
relatedRelations: {
children: { type: "multiple" as RelationDataType, limit: 50, sort: { field: "_id", order: "desc" as RelationSortOrderType } },
},
},
tags: {
schemaName: "tag",
type: "multiple" as RelationDataType,
optional: true,
excludes: tag_excludes,
limit: 20,
sort: { field: "name", order: "asc" as RelationSortOrderType },
relatedRelations: {
products: { type: "multiple" as RelationDataType, limit: 50, sort: { field: "_id", order: "desc" as RelationSortOrderType } },
},
},
};
Pure fieldsโ
| Field | Type | Notes |
|---|---|---|
name | string() | |
code | string() | indexed unique |
price | defaulted(number(), 0) | unit price |
unit | optional(string()) | e.g. pack, box |
active | defaulted(boolean(), true) | soft-disable products |
description | optional(string()) | |
createdAt / updatedAt | spread from createUpdateAt |
Relationsโ
| Relation | Target | Type | Back-reference |
|---|---|---|---|
parent | product (self) | single (optional) | product.children |
tags | tag | multiple (optional, limit 20, sorted by name asc) | tag.products |
Note the tags sort is on field: "name" ascending, unlike most relations here that sort _id descending โ tags come back alphabetized. Also note product_excludes includes price, so a product embedded inside, say, a purchase order does not leak its price by default.
Factoryโ
export const products = () =>
coreApp.odm.newModel("product", product_pure, product_relations, {
createIndex: {
indexSpec: { code: 1 },
options: { unique: true },
},
});
In the workflowโ
- Catalog acts: add-product, get-product, get-products, remove-product, update-product-relations
- Inventory: each inventory record tracks one product in one store
- Procurement: every purchaseOrder references a product
Run itโ
curl -X POST http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: <jwt>" \
-d '{
"service": "main",
"model": "product",
"act": "getProducts",
"details": {
"set": { "query": { "active": true }, "page": 1 },
"get": { "name": true, "code": true, "price": true, "tags": { "name": true } }
}
}'
Errors & fixesโ
| Error | Cause | Fix |
|---|---|---|
E11000 duplicate key error collection: advancedTutorial.products | code already used | pick a different code |
product not found | getProduct/removeProduct/updateProductRelations given an unknown _id | pass a real _id |
please clear below relations status before deletion | removeProduct on a product still referenced | clear the relations first (see remove-product) |
Runtime
On npm/Bun import the framework from @hemedani/lesan; on Deno from jsr:@hemedani/lesan. The repo app itself uses the lesan path alias.