Utilities & Helpers
Lesan ships a set of small utilities and helper functions that you use while building models, actions, and request validators โ error handling, validation helpers, schema introspection, and projection generation.
Error Handlingโ
throwError(msg) โ Throw a Plain Errorโ
throwError is the framework's convenience for throwing an error from inside a model or action. Error messages in Lesan are lowercase and terse.
import { throwError } from "@hemedani/lesan";
const getUser = async (body) => {
const { _id } = body.details.set;
if (!_id) {
throwError("_id is required");
}
// ...
};
When an action throws, Lesan's server catches the error and returns a success: false response with the message:
{
"body": {
"message": "_id is required"
},
"success": false
}
HttpError โ Control the HTTP Status Codeโ
HttpError extends the native Error with an HTTP status. Throw it from an action to control the status code returned to the client.
import { HttpError } from "@hemedani/lesan";
const addUser = async (body) => {
if (!body.details.set.email) {
throw new HttpError(400, "email is required");
}
// ...
};
The server uses (error as HttpError).status || 501 for the response status, so:
- An
HttpErrorwith astatusreturns that status. - Any other thrown
Errorreturns 501 by default.
Tip: You don't need to wrap logic in try/catch in your actions. Lesan's server pipeline catches everything, maps it to a JSON error response, and applies CORS headers for you.
Validation Helpersโ
Superstruct Validatorsโ
All Superstruct validators are re-exported from @hemedani/lesan and compose to build model fields and act validators:
import {
string, number, boolean, date,
array, object, optional, defaulted,
enums, coerce, pattern, union, literal,
assert, create, is, Infer,
} from "@hemedani/lesan";
// Composing validators
const mobile = pattern(
string(),
/(\+98|0|98|0098)?([ ]|-|[()]){0,2}9[0-9]([ ]|-|[()]){0,2}(?:[0-9]([ ]|-|[()]){0,2}){8}/,
);
const gender = coerce(
enums(["Male", "Female"]),
string(),
(value) => value as "Male" | "Female",
);
const userPure = {
first_name: string(),
email: pattern(string(), /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/),
birth_date: optional(coerce(date(), string(), (value) => new Date(value))),
isActive: defaulted(boolean(), true),
features: defaulted(array(object({ feature: string() })), []),
gender,
};
The full set of exported validators includes: any, array, bigint, boolean, coerce, date, defaulted, define, enums, func, instance, integer, intersection, lazy, literal, map, max, min, never, nonempty, nullable, number, object, omit, optional, partial, pattern, pick, record, refine, regexp, set, size, string, struct, trimmed, tuple, type, union, unknown, plus runtime functions assert, create, is, mask, validate.
objectIdValidation โ ObjectId or 24-char Stringโ
Lesan exports a ready-made validator that accepts either an ObjectId instance or a 24-character hex string. Use it for _id inputs:
import { object, objectIdValidation } from "@hemedani/lesan";
const getUserValidator = object({
set: object({
_id: objectIdValidation,
}),
get: object(),
});
It is equivalent to:
union([
instance(ObjectId),
size(string(), 24),
]);
assert vs createโ
Lesan runs your validator in one of two modes per act (set via validationRunType on the act):
assertmode (default): validates and throws on invalid input. The input is not modified.createmode: coerces values and fillsdefaultedvalues (e.g. setsisActive: truewhen omitted).
coreApp.acts.setAct({
schema: "user",
actName: "addUser",
validationRunType: "create", // coerce + defaults
validator: addUserValidator,
fn: addUserFn,
});
See Server API โ Validation for details.
Schema Introspection Helpersโ
All helpers below are available on coreApp.schemas and read from the in-memory schema registry (populated by odm.newModel).
| Helper | Description |
|---|---|
getSchemas() | The full schema registry Record<string, IModel> |
getSchemasKeys() | Array of all registered schema names |
getSchema(name) | The IModel for one schema (pure + relations) |
getPureSchema(name) | Pure fields of a schema |
getPureModel(name, excludes?) | Pure fields as Superstruct structs, with optional excludes |
getMainRelations(name) | Relations where this schema stores the snapshot |
getRelatedRelations(name) | Relations where other schemas store snapshots of this one |
getRelation(name, type?) | All relations, or only mainRelations / relatedRelations |
const allSchemas = coreApp.schemas.getSchemas();
const userSchema = coreApp.schemas.getSchema("user");
const userPure = coreApp.schemas.getPureSchema("user");
const mainRels = coreApp.schemas.getMainRelations("user");
const relatedRels = coreApp.schemas.getRelatedRelations("user");
getSchemasKeys() is used internally by the server to validate the model key of incoming requests.
Projection & Struct Helpersโ
createStruct(name) โ Full Superstruct Validatorโ
Builds a Superstruct struct for a schema combining pure fields and embedded relation snapshots. Useful when validating a whole document.
const userStruct = coreApp.schemas.createStruct("user");
// Validates pure fields + embedded relations
createEmbedded(name) โ Embedded Relation Fields Onlyโ
Returns the pure fields of all related schemas (for nesting inside another struct).
const embedded = coreApp.schemas.createEmbedded("user");
selectStruct(name, depth, excludes?) โ Client Projection Validatorโ
Generates a validator that accepts any valid projection for the schema up to the given depth. depth can be a number (uniform depth) or an object (per-relation depth).
// Uniform depth: validators for projections up to 2 levels deep
const validator = object({
set: object({ _id: objectIdValidation }),
get: coreApp.schemas.selectStruct("city", 2),
});
// Per-relation depth
const deepValidator = object({
get: coreApp.schemas.selectStruct("city", {
province: 2,
users: 1,
}),
});
Each projected field validates as optional(enums([0, 1])) (or a nested object for relations), so clients may include or omit fields freely. This is the recommended way to type details.get in act validators. See Queries & Projections โ Select Struct.
createProjection(name, type, excludes?) โ MongoDB Projection Generatorโ
Generates a MongoDB projection object ({ field: 1, ... }) from the schema registry. excludes are optional.
const projection = coreApp.schemas.createProjection("city", "PureMainRelations");
// Returns: { name: 1, population: 1, province: { name: 1, ... } }
Available projection types:
| Type | Description |
|---|---|
Pure | Only pure (scalar) fields |
MainRelations | Only main relation embedded fields |
RelatedRelations | Only related relation embedded fields |
PureMainRelations | Pure + main relations |
PureRelatedRelations | Pure + related relations |
MainRelationsRelatedRelations | All relation fields (no pure) |
PureMainRelationsRelatedRelations | Everything |
Request Body Typesโ
These TypeScript types describe the client โ server request shape and are exported from @hemedani/lesan:
Detailsโ
interface Details {
set: Record<string, any>; // What the client wants to write / pass to the act
get: Record<string, any>; // What the client wants returned (projection)
}
TLesanBodyโ
interface TLesanBody {
service?: string; // "main" | "blog" | "ecommerce"
model: string; // Schema name the client wants
act: string; // Name of the action to run
details: Details;
}
LesanContenxtโ
The request context carried through the pipeline:
interface LesanContenxt {
[key: string]: any;
Headers: Headers;
body: TLesanBody | null;
}
Your actions can read and extend it via coreApp.contextFns.getContextModel() โ see Server API โ Context. The Satek-style pattern is to extend it with your authenticated user:
interface MyContext extends LesanContenxt {
user: { _id: ObjectId; name: string; roles: Role[] };
}
Internal Pipeline Utilitiesโ
These are used internally by the server and are not typically called directly, but understanding them helps when debugging:
parsBody(req, port)โ decodes the JSON ormultipart/form-datarequest body. For multipart, uploaded files land indetails.set.formDataand the JSON payload is read from thelesan-bodyfield.serveLesan(req, port, cors)โ the request pipeline entry point: parses the body, validatesserviceโmodelโact, runspreValidation/ validation /preAct/fn, and returns the{ body, success: true }response.getNumericPosition(arr, num, fieldName, type)โ binary-search insert position in a sorted array (currently unused by the framework; do not rely on it).
These live under src/core/utils/ in the framework source.