Skip to main content

count (purchaseOrder)

count returns the number of purchase orders matching a filter โ€” a cheap badge counter for dashboards ("37 Pending", "5 Awaiting approval"). Unlike gets it returns no documents, just { count }, so it's ideal for status chips and header stats. Every authenticated role can call it.

note

Package import The tutorial source imports the framework as "lesan" โ€” in this repo that alias maps to the local framework source (deno.json โ†’ ../../src/mod.ts). In your own app import from @hemedani/lesan (npm/Bun) or jsr:@hemedani/lesan (Deno). @lib and @model are the tutorial's aliases for utils/ and models/.

The validator (count.val.ts)โ€‹

Filters by status, organizationId, or requestingUnitId โ€” all optional. Note the get is object({}): this act ignores the projection entirely.

import { object, optional, string } from "lesan";
import { activeRoleMixin } from "@lib";
import { purchaseOrder_status_emums } from "@model";

export const countValidator = () => {
return object({
set: object({
...activeRoleMixin,
status: optional(purchaseOrder_status_emums),
organizationId: optional(string()),
requestingUnitId: optional(string()),
}),
get: object({}),
});
};

The implementation (count.fn.ts)โ€‹

Builds the same dotted-path filters as gets (without search), then runs a single aggregation: $match (only if there are filters) โ†’ $count: "count". .toArray() yields at most one document; count?.count || 0 guards the empty case.

import { type ActFn, type Document, ObjectId } from "lesan";
import { purchaseOrder } from "../../../mod.ts";

export const countFn: ActFn = async (body) => {
const {
set: { status, organizationId, requestingUnitId },
} = body.details;

const filters: Document = {};
status && (filters.status = status as string);
organizationId && (filters["organization._id"] = new ObjectId(organizationId as string));
requestingUnitId && (filters["requestingUnit._id"] = new ObjectId(requestingUnitId as string));

const [count] = await purchaseOrder
.aggregation({
pipeline: [
...(Object.keys(filters).length > 0 ? [{ $match: filters }] : []),
{ $count: "count" },
] as Document[],
})
.toArray();

return { count: count?.count || 0 };
};

In the workflowโ€‹

count complements gets โ€” same filters, no payload. A dashboard could call count for each status (Draft, Pending, InProgress, Approved) to render the pipeline, then call gets with page/limit when the user opens one tab.

Links: overview, purchaseOrder model, po-gets.

Run itโ€‹

curl -X POST http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: $TOKEN" \
-d '{
"model": "purchaseOrder",
"act": "count",
"details": {
"set": {
"activeRoleId": "ghost-role",
"status": "Draft"
},
"get": {}
}
}'

Expect { "body": { "count": 1 }, "success": true }.

Errors & fixesโ€‹

The fn throws nothing of its own. A valid count of zero is still success ({ count: 0 }). Shared auth-chain errors apply; all roles are allowed.