@owlmeans/module
v0.1.1
Published
The `@owlmeans/module` package provides a comprehensive module system for OwlMeans Common Libraries, designed for fullstack microservices and microclients development.
Downloads
16
Readme
OwlMeans Module — Shared library
The @owlmeans/module package provides a comprehensive module system for OwlMeans Common Libraries, designed for fullstack microservices and microclients development.
Overview
In the context of OwlMeans Common Libraries, a module is not a programmatic module but a URL unit in the system. Modules allow you to:
- Declare URLs and their nesting relationships
- Transform URLs into routes with attached handlers (backend) or specify components to render (frontend)
- Generate final URLs for navigation or API calls on both backend and frontend
- Provide a centralized place where all possible routes are registered
- Enable micro-applications or micro-services to flawlessly address different parts of the system
For client-side applications, the @owlmeans/client-module package extends this base module system with client-specific capabilities such as API calls, URL generation, and request validation.
Core Concepts
Module
A module represents a URL unit that can be transformed into routes or components depending on the environment (frontend/backend). It consists of:
- A route with URL path and alias
- Optional guards for authentication/authorization
- Optional gates for parameter validation
- Optional filters for request/response validation
- Optional handlers for processing requests
Parent-Child Relationships
Modules can be organized in hierarchical structures where child modules inherit properties from their parents, such as guards and gates.
Fullstack Consistency
A key architectural benefit of the module system is that all validators are defined at the module level using AJV (Another JSON Schema Validator) format. Since the system is designed for fullstack development (existing on both frontend and backend), these validation schemas are consistently accessible across all application services and clients. This ensures:
- Unified validation: Same validation rules apply on both frontend and backend
- Consistent data contracts: API contracts are shared between client and server
- Reduced duplication: No need to define validation schemas separately for frontend and backend
- Type safety: AJV schemas provide runtime validation that complements TypeScript's compile-time checking
API Reference
Types
CommonModule
The main module interface that extends BasicModule from @owlmeans/context.
interface CommonModule extends BasicModule {
route: CommonRouteModel
sticky: boolean // If true, router attaches this module unconditionally
filter?: Filter // Request/response validation schemas
guards?: string[] // Authentication guards
gate?: string // Authorization gate
gateParams?: string | string[] // Gate parameters
handle?: ModuleHandler // Request handler function
// Methods
getAlias(): string
getPath(): string
getParentAlias(): string | null
hasParent(): boolean
resolve<M extends CommonModule>(): Promise<M>
getParent<M extends CommonModule>(): M
setService(service: string): void
getGuards(): string[]
getGates(): [string, string[]][]
}ModuleHandler
Function signature for handling module requests.
interface ModuleHandler {
<T, R extends AbstractRequest<any> = AbstractRequest<any>,
P extends AbstractResponse<any> = AbstractResponse<any>>
(req: R, res: P): T | Promise<T>
}Filter
Schema definitions for request/response validation using AJV (Another JSON Schema Validator) format. All validators are defined at the module level, ensuring consistent validation schemas across both frontend and backend environments in fullstack applications.
interface Filter {
query?: AnySchemaObject // Query parameters validation (AJV schema)
params?: AnySchemaObject // Path parameters validation (AJV schema)
body?: AnySchemaObject // Request body validation (AJV schema)
response?: AnySchemaObject // Response validation (AJV schema)
headers?: AnySchemaObject // Headers validation (AJV schema)
}Since the module system is designed for fullstack development, these AJV validation schemas are accessible and consistent across all application services and clients, providing unified data validation throughout the entire application stack.
AbstractRequest
Generic request interface for both frontend and backend.
interface AbstractRequest<T extends {} = {}> {
alias: string
auth?: Auth
params: Record<string, string | number | undefined | null> | Partial<T>
body?: Record<string, any> | Partial<T>
headers: Record<string, string[] | string | undefined>
query: Record<string, string | number | undefined | null> | Partial<T>
path: string
original?: any
canceled?: boolean
cancel?: () => void
host?: string
base?: string | boolean
}AbstractResponse
Generic response interface for handling module responses.
interface AbstractResponse<T> {
responseProvider?: any
value?: T
outcome?: ModuleOutcome
error?: Error
resolve(value: T, outcome?: ModuleOutcome): void
reject(error: Error): void
}Core Functions
module(route, opts?)
Creates a new module instance.
function module(route: CommonRouteModel, opts?: CommonModuleOptions): CommonModuleParameters:
route: CommonRouteModel - The route configurationopts: CommonModuleOptions - Optional module configuration
Returns: CommonModule instance
Example:
import { module } from '@owlmeans/module'
import { route } from '@owlmeans/route'
const userModule = module(route('users', '/users'), {
sticky: true,
guards: ['authenticated']
})parent(module, aliasOrParent, _parent?)
Sets parent-child relationships between modules.
function parent<T extends CommonModule | CommonModule[]>(
module: T,
aliasOrParent: string,
_parent?: string
): TParameters:
module: CommonModule or CommonModule[] - Module(s) to set parent foraliasOrParent: string - Parent alias or module alias (when working with arrays)_parent: string - Parent name (required when working with arrays)
Returns: The module(s) with parent relationship set
Example:
const userModule = module(route('users', '/users'))
const userProfileModule = module(route('user-profile', '/profile'))
parent(userProfileModule, 'users') // Sets users as parent of user-profileHelper Functions
filter(filter, opts?)
Creates module options with filter configuration.
function filter(filter: Filter, opts?: CommonModuleOptions): CommonModuleOptionsExample:
const userModule = module(route('users', '/users'), filter({
query: { type: 'object', properties: { limit: { type: 'number' } } }
}))guard(guard, opts?)
Adds authentication guard to module options.
function guard(guard: string, opts?: CommonModuleOptions): CommonModuleOptionsExample:
const adminModule = module(route('admin', '/admin'), guard('admin'))gate(gate, params, opts?)
Adds authorization gate with parameters to module options.
function gate(gate: string, params: string | string[], opts?: CommonModuleOptions): CommonModuleOptionsExample:
const userModule = module(route('user', '/user/:id'), gate('user-access', ['id']))provideResponse(originalResponse?)
Creates an abstract response handler.
function provideResponse<T>(originalResponse?: unknown): AbstractResponse<T>Example:
const response = provideResponse<UserData>()
response.resolve(userData, ModuleOutcome.Ok)clone(modules, from, to, service)
Clones an existing module with new alias and service.
function clone<M extends CommonModule>(
modules: M[],
from: string,
to: string,
service: string
): voidParameters:
modules: M[] - Array of modules to add cloned module tofrom: string - Source module aliasto: string - New module aliasservice: string - Service name for the cloned module
Filter Building Functions
These functions create validation filters using AJV (Another JSON Schema Validator) format. All schemas are defined at the module level and remain consistent across both frontend and backend environments, ensuring unified validation throughout your fullstack application.
body(schema, filter?)
Creates or extends a filter with body validation schema using AJV format.
function body<T>(schema: JSONSchemaType<T>, filter?: Filter): Filterquery(schema, filter?)
Creates or extends a filter with query parameters validation schema using AJV format.
function query<T>(schema: JSONSchemaType<T>, filter?: Filter): Filterparams(schema, filter?)
Creates or extends a filter with path parameters validation schema using AJV format.
function params<T>(schema: JSONSchemaType<T>, filter?: Filter): Filterresponse(schema, code?, filter?)
Creates or extends a filter with response validation schema using AJV format.
function response<T>(schema: JSONSchemaType<T>, code?: number, filter?: Filter): Filterheaders(schema, filter?)
Creates or extends a filter with headers validation schema using AJV format.
function headers<T>(schema: JSONSchemaType<T>, filter?: Filter): FilterExample with AJV Schema Format:
import { body, query, params, response } from '@owlmeans/module'
// AJV schema for request body validation
const userFilter = body({
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' }
},
required: ['name', 'email']
}, query({
type: 'object',
properties: {
include: { type: 'string', enum: ['profile', 'preferences'] }
}
}))
// These AJV schemas are accessible on both frontend and backend
// providing consistent validation across your fullstack applicationUtility Functions
isModule(object)
Type guard to check if an object is a CommonModule.
function isModule(module: Object): module is CommonModuleExample:
if (isModule(someObject)) {
// someObject is definitely a CommonModule
console.log(someObject.getAlias())
}Constants
ModuleOutcome
Enumeration of possible module response outcomes.
enum ModuleOutcome {
Ok = 'ok',
Accepted = 'accepted',
Created = 'created',
Finished = 'finished'
}Service Interfaces
GuardService
Service interface for implementing authentication guards.
interface GuardService extends InitializedService {
token?: string
authenticated(req?: Partial<AbstractRequest>): Promise<string | null>
match: ModuleMatch
handle: ModuleHandler
}GateService
Service interface for implementing authorization gates.
interface GateService extends LazyService {
assert: ModuleAssert // Throws Error if assertion fails
}Usage Examples
Basic Module Creation
import { module, guard, filter, body } from '@owlmeans/module'
import { route } from '@owlmeans/route'
// Create a simple module
const homeModule = module(route('home', '/'))
// Create a protected module with authentication
const dashboardModule = module(
route('dashboard', '/dashboard'),
guard('authenticated')
)
// Create a module with request validation
const createUserModule = module(
route('create-user', '/users', { method: 'POST' }),
filter(body({
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' }
},
required: ['name', 'email']
}))
)Module Hierarchies
// Create parent module
const apiModule = module(route('api', '/api'))
// Create child modules
const usersModule = module(route('users', '/users'))
const postsModule = module(route('posts', '/posts'))
// Set parent relationships
parent(usersModule, 'api')
parent(postsModule, 'api')
// Child modules inherit parent guards and gates
const userProfileModule = module(
route('user-profile', '/profile'),
guard('user-access')
)
parent(userProfileModule, 'users')Module with Handler
const userModule = module(route('get-user', '/users/:id'), {
handle: async (req, res) => {
const userId = req.params.id
const user = await getUserById(userId)
res.resolve(user, ModuleOutcome.Ok)
}
})Complex Filter Example
import { filter, body, query, params, response } from '@owlmeans/module'
const complexModule = module(
route('complex-api', '/api/users/:id'),
filter(
params({
type: 'object',
properties: {
id: { type: 'string', pattern: '^[0-9]+$' }
},
required: ['id']
},
query({
type: 'object',
properties: {
include: { type: 'string', enum: ['profile', 'posts'] },
limit: { type: 'number', minimum: 1, maximum: 100 }
}
},
response({
type: 'object',
properties: {
user: { type: 'object' },
profile: { type: 'object' }
},
required: ['user']
})))
)
)Integration Patterns
Backend Integration
On the backend, modules are typically transformed into Express-like routes:
// Transform module to route with handler
app.get(userModule.getPath(), async (req, res) => {
const moduleReq = adaptRequest(req)
const moduleRes = provideResponse()
await userModule.handle(moduleReq, moduleRes)
if (moduleRes.error) {
res.status(500).json({ error: moduleRes.error.message })
} else {
res.json(moduleRes.value)
}
})Frontend Integration
On the frontend, modules help generate URLs and determine which components to render:
// Generate URL for navigation
const userUrl = userModule.getPath() // '/users/:id'
const finalUrl = userUrl.replace(':id', userId)
// Navigate to the route
router.push(finalUrl)Best Practices
- Organize modules hierarchically to take advantage of guard and gate inheritance
- Use meaningful aliases for modules to make them easy to reference
- Apply filters consistently to ensure proper validation
- Leverage guards and gates for security at the module level
- Keep modules focused on a single responsibility
- Use the clone function to create variations of existing modules for different services
Dependencies
This package depends on:
@owlmeans/route- For route management@owlmeans/context- For contextual module support@owlmeans/auth- For authentication integrationajv- For JSON schema validation using AJV format, providing consistent validation across fullstack applications
Related Packages
@owlmeans/client-module- Client-side extension with API calls and URL generation@owlmeans/server-module- Server-side module implementation@owlmeans/route- Core routing functionality@owlmeans/context- Context management system
