This page is generated from the following source files:
Lesan is a TypeScript framework designed for building data-centric applications with a focus on schema-driven development, cross-platform compatibility, and flexible architecture patterns. The framework provides an integrated solution for Object-Document Mapping (ODM), schema validation, action routing, and server management, supporting both monolithic and microservice deployments across Deno, Node.js, and Bun runtimes.
The Lesan framework is organized into four primary subsystems that work together to provide a complete application development platform. The architecture emphasizes modularity and separation of concerns, allowing developers to use individual components independently or as an integrated system.
正在加载图表渲染器...
Key Architectural Points:
Factory Pattern Entry: The lesan() function acts as the application factory, creating a self-contained instance with all core functionalities exposed through a unified API object (src/lesan.ts:7-23).
Three-Pillar Core: The framework is built around three interconnected subsystems—Schemas (data structure), Acts (business logic), and ODM (persistence)—that share internal state through closure-based dependency injection.
Platform Abstraction Layer: A dedicated platform module isolates runtime-specific code behind stable interfaces, enabling true cross-platform compatibility without conditional logic in the core (src/platform/index.ts:121-157).
Automatic Runtime Detection: The adapter system automatically detects the execution environment (Deno, Node.js, or Bun) at startup and loads the appropriate implementations (src/platform/adapters/index.ts:1-75).
Microservice-Ready Design: The Acts module supports service registration and remote action invocation, allowing applications to span multiple processes or servers transparently.
The lesan() function serves as the primary entry point and application factory. When invoked, it initializes three core management objects—schemas, acts, and odm—along with server configuration and context utilities. This design follows the factory pattern, allowing multiple independent application instances to coexist.
typescript1export const lesan = () => { 2 const schemasObj: TSchemas = {}; 3 const actsObj: Services = { main: {} }; 4 5 return { 6 schemas: { ...schemas(schemasObj) }, 7 acts: { ...acts(actsObj) }, 8 odm: { ...odm(schemasObj) }, 9 runServer: lesanServer(schemasObj, actsObj), 10 contextFns, 11 generateSchemTypes: () => generateSchemTypes(schemasObj, actsObj), 12 }; 13};
Source: src/lesan.ts:7-23
Responsibility Boundaries:
main serviceKey Data Structures:
| Structure | Type | Purpose |
|---|---|---|
schemasObj | TSchemas | Central schema registry shared across modules |
actsObj | Services | Hierarchical action registry (service → schema → act) |
contextFns | Object | Request-scoped context management utilities |
The public API is exported centrally through src/mod.ts, which re-exports all necessary types and functions for external consumption (src/mod.ts:1-12). This centralized export strategy ensures a stable public interface while allowing internal refactoring.
Lesan implements a sophisticated platform abstraction layer that enables the same codebase to run on Deno, Node.js, and Bun without modification. This is achieved through a dependency injection container that automatically selects the appropriate adapters based on runtime detection.
The PlatformAdapter interface defines the contract that all runtime implementations must fulfill:
typescript1export interface PlatformAdapter { 2 fs: FileSystemAdapter; 3 http: HttpServerAdapter; 4 env: EnvironmentAdapter; 5 runtime: RuntimeAdapter; 6 bundler?: BundlerAdapter; 7}
Source: src/platform/index.ts:121-157
The adapter selection logic resides in a centralized module that performs runtime detection at load time:
正在加载图表渲染器...
Adapter Selection Logic:
The detection mechanism uses runtime-specific globals to identify the environment, then loads the corresponding adapter implementations:
typescript1const runtimeInfo = detectRuntime(); 2let fs: FileSystemAdapter; 3let env: EnvironmentAdapter; 4let http: HttpServerAdapter; 5let bundler: BundlerAdapter; 6 7switch (runtimeInfo.type) { 8 case RuntimeType.Deno: 9 fs = denoFsAdapter; 10 env = denoEnvAdapter; 11 http = denoHttpAdapter; 12 bundler = new DenoBundlerAdapter(); 13 break; 14 case RuntimeType.Bun: 15 fs = bunFileSystemAdapter; 16 env = bunEnvAdapter; 17 http = bunHttpAdapter; 18 bundler = bunBundlerAdapter; 19 break; 20 case RuntimeType.Node: 21 fs = nodeFsAdapter; 22 env = nodeEnvAdapter; 23 http = nodeHttpAdapter; 24 bundler = nodeBundlerAdapter; 25 break; 26 default: 27 console.warn(`[Lesan] Unknown runtime detected (${runtimeInfo.name}). Falling back to Node.js adapters.`); 28 fs = nodeFsAdapter; 29 // ... fallback to Node adapters 30 break; 31}
Source: src/platform/adapters/index.ts:1-75
Error Handling and Fallback:
When an unknown runtime is detected, the system logs a warning and gracefully falls back to Node.js adapters, ensuring the application remains functional even in unsupported environments. This defensive approach prioritizes stability over strict runtime enforcement.
The data modeling subsystem provides a schema-driven approach to defining data structures, relationships, and projections. It integrates tightly with the ODM layer to enforce schema consistency at the database level.
The schemas function creates a comprehensive API for managing schema definitions:
typescript1export const schemas = (schemas: TSchemas) => { 2 return { 3 ...schemaFns(schemas), 4 ...mainRelationsFns(schemas), 5 ...relatedRelationFns(schemas), 6 ...pureFns(schemas), 7 ...relationFns(schemas), 8 ...selectStructFns(schemas), 9 createProjection: (schemaName: string, projectionType: TProjectionType) => 10 createProjection(schemas, schemaName, projectionType), 11 }; 12};
Source: src/core/models/mod.ts:13-33
Schema Components:
| Component | Responsibility | Key Functions |
|---|---|---|
schemaFns | Schema registration and retrieval | getSchemas, getSchemaKeys |
mainRelationsFns | Outbound relationship management | getMainRelations, addMainRelations |
relatedRelationFns | Inbound relationship management | getRelatedRelations |
pureFns | Pure field definitions | getPure, addPure |
selectStructFns | Query projection structures | selectStruct |
createProjection | Projection generation | Creates MongoDB-compatible projections |
The createProjection function generates MongoDB-compatible projection objects based on schema type:
typescript1export const createProjection = ( 2 schemasObj: TSchemas, 3 schemaName: string, 4 projectionType: TProjectionType, 5 excludes?: string[], 6) => { 7 switch (projectionType) { 8 case "Pure": 9 return setFiledsToOne(getPureModel(schemasObj, schemaName, excludes)); 10 case "MainRelations": 11 return setFiledsToOne( 12 getFlattenPureFromRelations(schemasObj, schemaName, "MainRelations"), 13 ); 14 case "PureMainRelations": 15 return setFiledsToOne({ 16 ...getPureModel(schemasObj, schemaName, excludes), 17 ...getFlattenPureFromRelations(schemasObj, schemaName, "MainRelations"), 18 }); 19 // ... additional cases 20 } 21};
Source: src/core/models/createProjection.ts:22-61
Projection Types:
The ODM module bridges schema definitions with MongoDB operations:
typescript1export const odm = (schemasObj: TSchemas) => { 2 let mongoDb: Db; 3 const setDb = (db: Db) => (mongoDb = db); 4 const getDbClient = () => mongoDb; 5 6 const getCollection = (collection: string) => { 7 const db = getDbClient(); 8 const getSchemas = enums(schemaFns(schemasObj).getSchemasKeys()); 9 assert(collection, getSchemas); 10 return db 11 ? db.collection(collection) 12 : throwError("No database connection"); 13 }; 14 15 return { 16 setDb, 17 getCollection, 18 newModel: <PF extends IPureFields, TR extends IRelationsFileds>( 19 name: string, 20 pureFields: PF, 21 relations: TR, 22 options?: OptionType<PF>, 23 ) => newModel<PF, TR>(mongoDb, schemasObj, name, pureFields, relations, options), 24 }; 25};
Source: src/core/odm/mod.ts:7-44
Error Handling:
The getCollection function includes validation logic that asserts the requested collection name exists in the schema registry, throwing an error for undefined schemas. Additionally, it checks for database connection availability before attempting collection access.
The Acts module provides the mechanism for organizing application logic into discrete, validated actions that can be invoked locally or remotely. This layer supports both monolithic and distributed architectures through a unified service abstraction.
Actions are registered with associated validators that define the input/output contract:
typescript1export * from "./setAct.ts"; 2export * from "./setService.ts"; 3export * from "./getAct.ts"; 4export * from "./getActs.ts"; 5export * from "./getActsWithServices.ts"; 6export * from "./getMainActs.ts";
Source: src/core/acts/mod.ts:1-18
Core Action Functions:
| Function | Purpose |
|---|---|
setAct | Register a new action with validator and handler |
setService | Register a remote service endpoint |
getAct | Retrieve a specific action by schema and name |
getActs | Retrieve all actions for a schema |
getActsWithServices | Retrieve all actions across all services |
getMainActs | Retrieve actions from the main service only |
The framework supports microservice architectures through service registration. Remote services can be registered by URL or by importing action definitions directly:
typescript1// Remote service registration 2coreApp.acts.setService("ecommerce", "http://localhost:8282/lesan"); 3 4// Or direct import of actions 5import { ecommerceActs } from "../ecommerce/mod.ts"; 6coreApp.acts.setService("ecommerce", ecommerceActs);
Source: pages/src/Microservice_Architecture_with_Lesan.md:1-213
Microservice Communication Flow:
When a request targets a remote service, the framework automatically proxies the request to the registered endpoint, maintaining a unified API surface regardless of where the action is executed.
The getAtcsWithServices function provides access to the complete action registry:
typescript1export const getAtcsWithServices = (acts: Services) => acts;
Source: src/core/acts/getActsWithServices.ts:7-7
This simple accessor returns the entire services object, enabling introspection and dynamic action discovery.
The following sequence diagram illustrates the complete request processing flow from HTTP request to database operation and response generation:
正在加载图表渲染器...
Flow Explanation:
Request Receipt: The HTTP server receives a POST request to the /lesan endpoint with a JSON body containing service identifier, content type, action target, and details.
Action Resolution: The server queries the Acts module to retrieve the action definition based on service, schema, and action name.
Validation: The request details are validated against the schema defined in the action's validator. Invalid requests are rejected immediately with a 400 response.
Execution: Upon successful validation, the action function is invoked with the validated body.
Data Access: The action function interacts with the ODM layer to perform database operations.
Response: The result is wrapped in a standard response format and returned to the client.
The framework provides consistent APIs across all supported runtimes. The following example demonstrates the complete application lifecycle:
typescript1// 1. Create Application Instance 2const app = lesan(); 3 4// 2. Connect to Database 5const client = new MongoClient(); 6await client.connect("mongodb://localhost:27017/lesan"); 7const db = client.database("test"); 8app.odm.setDb(db); 9 10// 3. Define a Model 11const userPure = { 12 name: string(), 13 age: number(), 14 email: string(), 15}; 16const users = app.odm.newModel("user", userPure, {}); 17 18// 4. Define an Action (Route) 19const addUserValidator = () => { 20 return object({ 21 set: object(userPure), 22 get: app.schemas.selectStruct("user", 1), 23 }); 24}; 25 26const addUser: ActFn = async (body) => { 27 const { name, age, email } = body.details.set; 28 return await users.insertOne({ 29 doc: { name, age, email }, 30 projection: body.details.get, 31 }); 32}; 33 34app.acts.setAct({ 35 schema: "user", 36 actName: "addUser", 37 validator: addUserValidator(), 38 fn: addUser, 39}); 40 41// 5. Run the Server 42app.runServer({ 43 port: 8080, 44 playground: true, 45 typeGeneration: true, 46});
Source: examples/node-app/src/main.ts:10-49
Cross-Runtime Consistency:
The same code structure is used across all runtimes with only minor differences in import statements and port numbers:
The following diagram illustrates the dependency relationships between core modules:
正在加载图表渲染器...
Dependency Analysis:
Unidirectional Dependencies: Core modules depend on schemas but not on each other, preventing circular dependencies.
Shared State via Closure: The schemasObj is shared between lesan factory, schemas module, and odm module through closure, avoiding explicit dependency injection overhead.
Platform Isolation: Platform adapters are completely isolated from core business logic, allowing independent testing and replacement.
| Decision | Rationale | Trade-off |
|---|---|---|
| Factory Pattern for Application Instance | Enables multiple isolated instances in the same process; simplifies testing | Slightly more verbose than singleton pattern |
| Closure-based State Sharing | Avoids circular dependencies; provides encapsulation | State is not easily accessible for debugging |
| MongoDB-only ODM | Deep integration with MongoDB features; optimized projections | Not portable to other databases |
| Schema-first Design | Type safety; automatic validation; projection generation | Requires upfront schema definition |
| Platform Abstraction Layer | True cross-platform compatibility; clean separation | Additional abstraction layer; potential performance overhead |
| Service-based Action Organization | Supports microservices; logical grouping | More complex than flat action registry |
| Runtime Detection at Load Time | Zero-configuration cross-platform support | Cannot switch runtime after initialization |
| Validator Functions | Type-safe input/output contracts; runtime validation | Validation logic must be defined separately from handlers |
| Technology | Purpose | Selection Rationale | Alternatives Considered |
|---|---|---|---|
| TypeScript | Primary language | Type safety; excellent tooling; cross-platform | JavaScript (rejected: lacks type safety) |
| MongoDB | Primary database | Document model fits schema design; flexible projections | PostgreSQL (rejected: requires ORM mapping) |
| Superstruct | Runtime validation | Lightweight; composable; TypeScript-first | Zod (rejected: larger bundle size) |
| Deno | Primary runtime | Native TypeScript; secure by default; modern APIs | - |
| Node.js | Secondary runtime | Ecosystem size; enterprise adoption | - |
| Bun | Tertiary runtime | Performance; native TypeScript support | - |
| tsup | Build tool | Zero-config; ESM/CJS dual output; fast | esbuild directly (rejected: less configuration options) |
| Mermaid | Documentation diagrams | Text-based; version controllable; widely supported | PlantUML (rejected: requires Java) |
The framework uses tsup for building, configured to produce both ESM and CommonJS outputs:
typescript1export default defineConfig({ 2 entry: { index: "src/mod.ts" }, 3 format: ["esm", "cjs"], 4 dts: true, 5 clean: true, 6 target: "es2022", 7 sourcemap: true, 8 splitting: false, 9 minify: false, 10 external: ["mongodb", "superstruct", "esbuild"], 11});
Source: tsup.config.ts:1-36
mod.ts entry pointlesan() to initialize application contextodm.setDb()odm.newModel()acts.setAct()runServer() with port and feature configurationServer Configuration Options:
| Option | Type | Description |
|---|---|---|
port | number | HTTP server listening port |
playground | boolean | Enable interactive API playground |
typeGeneration | boolean | Enable automatic TypeScript type generation |
The framework's architecture prioritizes developer experience through consistent APIs, automatic validation, and cross-platform compatibility while maintaining the flexibility to support both monolithic and microservice deployment patterns.