Skip to main content

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โ€‹

FieldTypeNotes
namestring()
codestring()indexed unique
pricedefaulted(number(), 0)unit price
unitoptional(string())e.g. pack, box
activedefaulted(boolean(), true)soft-disable products
descriptionoptional(string())
createdAt / updatedAtspread from createUpdateAt

Relationsโ€‹

RelationTargetTypeBack-reference
parentproduct (self)single (optional)product.children
tagstagmultiple (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โ€‹

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โ€‹

ErrorCauseFix
E11000 duplicate key error collection: advancedTutorial.productscode already usedpick a different code
product not foundgetProduct/removeProduct/updateProductRelations given an unknown _idpass a real _id
please clear below relations status before deletionremoveProduct on a product still referencedclear the relations first (see remove-product)
note

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.