Type System
Lesan provides end-to-end type safety through automatic TypeScript type generation. Your schemas, actions, and API client are all fully typed based on your actual code.
Type Generationโ
When you enable typeGeneration: true in your server configuration, Lesan automatically generates TypeScript declaration files based on your schemas and actions.
await coreApp.runServer({
port: 8000,
typeGeneration: true, // Auto-generate types on startup
});
This creates a declarations/selectInp.ts file containing:
- Schema input types (for projections)
- Schema types (full document shapes)
- Request types (for all actions)
- A type-safe API client (
lesanApi)
Generated Typesโ
Schema Input Typesโ
For each schema, Lesan generates an input type that represents valid projection shapes:
export type cityInp = {
province?: number | provinceInp
users?: number | userInp
}
These types are used by the selectStruct validator to ensure client projections are type-safe at runtime.
Schema Typesโ
Full TypeScript types representing your document structure:
export type citySchema = {
_id?: string
name?: string
population?: number
province?: {
_id?: string
name?: string
population?: number
country?: {
_id?: string
name?: string
population?: number
}
}
}
Request Typesโ
A comprehensive ReqType that maps every service, model, and action to its validator shape:
export type ReqType = {
main: {
city: {
addCity: {
set: {
name?: string
population?: number
provinceId?: string
}
get: Record<string, any>
}
getCities: {
set: Record<string, any>
get: Record<string, any>
}
}
province: {
// ...
}
}
}
This allows compile-time verification that your API requests match the expected shape.
Type-Safe API Clientโ
Lesan generates a fully type-safe HTTP client called lesanApi.
Creating the Clientโ
import { lesanApi } from "./declarations/selectInp.ts";
const api = lesanApi({
URL: "http://localhost:8000/lesan",
baseHeaders: {
"X-API-Key": "your-api-key",
},
});
Making Type-Safe Requestsโ
const result = await api.send({
service: "main",
model: "city",
act: "addCity",
details: {
set: {
name: "Tehran",
population: 9000000,
provinceId: "507f1f77bcf86cd799439011",
},
get: {
name: 1,
population: 1,
},
},
});
TypeScript guarantees:
servicemust be a valid service keymodelmust exist in that serviceactmust exist for that modeldetails.setmust match the action's validator shapedetails.getmust be a valid projection
Custom Headersโ
api.setHeaders({
"Authorization": "Bearer token123",
});
const config = api.getSetting();
// Returns current configuration
Additional Request Headersโ
const result = await api.send(
{
model: "city",
act: "addCity",
details: { /* ... */ },
},
{
"X-Request-ID": "uuid-123",
}
);
Superstruct Integrationโ
Lesan uses Superstruct for runtime validation and type inference.
Validation Functionsโ
All Superstruct validators are exported from @hemedani/lesan:
import {
string, number, boolean, object, array, optional,
assert, create, is, Infer,
} from "@hemedani/lesan";
Inferring Types from Schemasโ
Use Superstruct's Infer type to extract TypeScript types from validators:
import { object, string, number, Infer } from "@hemedani/lesan";
const UserStruct = object({
name: string(),
email: string(),
age: optional(number()),
});
type User = Infer<typeof UserStruct>;
// Equivalent to: { name: string; email: string; age?: number }
Creating Validators for Actionsโ
const addUserValidator = object({
set: object({
name: string(),
email: string(),
age: optional(number()),
}),
get: object(), // Projection - any shape is valid
});
coreApp.acts.setAct({
schema: "user",
actName: "addUser",
validator: addUserValidator,
fn: addUserFn,
});
Runtime Validation Modesโ
Assert Mode (Default)โ
Throws on invalid input:
// In your action handler, Lesan calls:
assert(body.details, addUserValidator);
Create Modeโ
Coerces and fills defaults:
import { defaulted } from "@hemedani/lesan";
const updateUserValidator = object({
set: object({
name: defaulted(string(), "Anonymous"),
isActive: defaulted(boolean(), true),
}),
get: object(),
});
coreApp.acts.setAct({
schema: "user",
actName: "updateUser",
validator: updateUserValidator,
fn: updateUserFn,
validationRunType: "create", // Use create mode
});
Core Types Referenceโ
TLesanBodyโ
The standard request body shape:
interface TLesanBody {
service?: string;
model: string;
act: string;
details: {
set: Record<string, any>;
get: Record<string, any>;
};
}
LesanContextโ
The request context container:
interface LesanContext {
[key: string]: any;
Headers: Headers;
body: TLesanBody | null;
}
ActFnโ
The action handler function type:
type ActFn = (body: TLesanBody) => any;
Actโ
The action configuration type:
interface Act {
validator: Struct<any>;
fn: ActFn;
preAct?: Function[];
preValidation?: Function[];
validationRunType?: "assert" | "create";
}
ActInpโ
The input for registering an action:
interface ActInp {
schema: string;
actName: string;
validator: Struct<any>;
fn: ActFn;
preAct?: Function[];
preValidation?: Function[];
validationRunType?: "assert" | "create";
}
Servicesโ
The services registry type:
interface Services {
main: Acts;
[key: string]: Acts | string | undefined;
}
Actsโ
The actions collection type:
interface Acts {
[schemaName: string]: {
[actName: string]: Act;
};
}
Model Typesโ
IPureFieldsโ
Pure field definitions:
interface IPureFields {
[key: string]: Struct<any>;
}
TRelationโ
Relation definition:
interface TRelation {
schemaName: string;
type: RelationDataType;
optional: boolean;
excludes?: string[];
limit?: null | number;
sort?: {
field: string;
order: "asc" | "desc";
};
relatedRelations: {
[key: string]: TRelatedRelation;
};
}
IMainRelationโ
Main relation (stored on source document):
interface IMainRelation {
schemaName: string;
type: RelationDataType;
optional: boolean;
excludes?: string[];
limit?: null | number;
sort?: {
field: string;
order: "asc" | "desc";
};
}
IRelatedRelationโ
Related relation (auto-generated back-reference):
interface IRelatedRelation {
schemaName: string;
mainRelationName: string;
mainRelationType: RelationDataType;
type: RelationDataType;
limit?: null | number;
excludes?: string[];
sort?: {
field: string;
order: "asc" | "desc";
};
}
IRelationsFiledsโ
Collection of relation definitions:
interface IRelationsFileds {
[key: string]: TRelation;
}
TInsertRelationsโ
Type for inserting relations:
type TInsertRelations<T extends IRelationsFileds> = {
[mainKey in keyof T]?: {
_ids: ObjectId | ObjectId[];
relatedRelations?: {
[key in keyof T[mainKey]["relatedRelations"]]: boolean;
};
};
};
IModelโ
Complete model definition:
interface IModel {
pure: IPureFields;
relations: Record<string, TRelation>;
mainRelations: Record<string, IMainRelation>;
relatedRelations: Record<string, IRelatedRelation>;
options?: { excludes?: (string | number)[] };
}
ODM Typesโ
Projectionโ
MongoDB projection type:
type Projection = { [key: string]: number | Projection };
OptionTypeโ
Model options:
type OptionType<PF extends IPureFields> = {
createIndex?: {
indexSpec: IndexSpecification;
options?: CreateIndexesOptions;
};
excludes?: Partial<(keyof PF)>[];
};
IFindModelInputsโ
Find operation inputs:
interface IFindModelInputs {
filters: Filter<Document>;
projection?: Projection;
options?: FindOptions;
}
Aggregation Typesโ
Pipeline Stage Typesโ
type PipelineStage =
| LookupObj // $lookup
| UnwindObj // $unwind
| ProjectionObj // $project
| MatchObj // $match
| AddFieldsObj // $addFields
| SortObj // $sort
| GroupObj // $group
| LimitObj // $limit
| SkipObj // $skip;
Lookup Configurationโ
interface Lookup {
from: string;
localField?: string;
foreignField?: string;
as: string;
let?: { [key: string]: string };
pipeline?: ProjectionPip;
}
Utility Typesโ
DeepPartialโ
Recursively makes all properties optional:
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
Used by the lesanApi client for partial request bodies.
RelationDataTypeโ
Relation cardinality:
type RelationDataType = "single" | "multiple";
RelationSortOrderTypeโ
Sort direction:
type RelationSortOrderType = "asc" | "desc";
RelationTypeโ
Relation category:
type RelationType = "mainRelations" | "relatedRelations" | "relations";
MongoDB Re-exportsโ
Lesan re-exports commonly used MongoDB types for convenience:
import {
ObjectId,
Document,
Filter,
FindOptions,
UpdateFilter,
InsertOneOptions,
DeleteOptions,
AggregateOptions,
// ... and many more
} from "@hemedani/lesan";
See npmDeps.ts in the source for the complete list of re-exports.
Type Best Practicesโ
1. Use Infer for Schema Typesโ
import { object, string, number, Infer } from "@hemedani/lesan";
const CityStruct = object({ name: string(), population: number() });
type City = Infer<typeof CityStruct>;
2. Export Relation Typesโ
export const cityRelations = {
province: {
schemaName: "province",
type: "single" as RelationDataType,
// ...
},
};
export type city_relations = typeof cityRelations;
3. Use TInsertRelations for Type-Safe Insertsโ
import { TInsertRelations } from "@hemedani/lesan";
import type { city_relations } from "./models.ts";
const relations: TInsertRelations<typeof city_relations> = {
province: {
_ids: new ObjectId(provinceId),
relatedRelations: { cities: true },
},
};
4. Enable Type Generation in Developmentโ
await coreApp.runServer({
port: 8000,
typeGeneration: process.env.NODE_ENV === "development",
});
API Reference Tableโ
Type Generationโ
| Function | Description |
|---|---|
generateSchemTypes(schemas, acts) | Generate TypeScript declarations |
lesanApi({ URL, settings, baseHeaders }) | Create type-safe API client |
Client Methodsโ
| Method | Description |
|---|---|
api.send(body, additionalHeaders?) | Type-safe API request |
api.setHeaders(headers) | Update default headers |
api.getSetting() | Get current config |
Core Typesโ
| Type | Description |
|---|---|
TLesanBody | Standard request shape |
LesanContext | Request context |
ActFn | Action handler signature |
Act | Action configuration |
ActInp | Action registration input |
Services | Services registry |
Acts | Actions collection |
Model Typesโ
| Type | Description |
|---|---|
IPureFields | Pure field definitions |
TRelation | Relation definition |
IMainRelation | Main relation shape |
IRelatedRelation | Related relation shape |
IRelationsFileds | Relation fields map |
TInsertRelations | Insert relation input |
IModel | Complete model |
Utility Typesโ
| Type | Description |
|---|---|
Projection | MongoDB projection |
DeepPartial | Recursive partial |
RelationDataType | single or multiple |
RelationSortOrderType | asc or desc |