Skip to main content

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:

  • service must be a valid service key
  • model must exist in that service
  • act must exist for that model
  • details.set must match the action's validator shape
  • details.get must 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โ€‹

FunctionDescription
generateSchemTypes(schemas, acts)Generate TypeScript declarations
lesanApi({ URL, settings, baseHeaders })Create type-safe API client

Client Methodsโ€‹

MethodDescription
api.send(body, additionalHeaders?)Type-safe API request
api.setHeaders(headers)Update default headers
api.getSetting()Get current config

Core Typesโ€‹

TypeDescription
TLesanBodyStandard request shape
LesanContextRequest context
ActFnAction handler signature
ActAction configuration
ActInpAction registration input
ServicesServices registry
ActsActions collection

Model Typesโ€‹

TypeDescription
IPureFieldsPure field definitions
TRelationRelation definition
IMainRelationMain relation shape
IRelatedRelationRelated relation shape
IRelationsFiledsRelation fields map
TInsertRelationsInsert relation input
IModelComplete model

Utility Typesโ€‹

TypeDescription
ProjectionMongoDB projection
DeepPartialRecursive partial
RelationDataTypesingle or multiple
RelationSortOrderTypeasc or desc