Skip to content

Latest commit

Β 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

README.md

🌱 @zerva/core

npm version License: MIT

Event-driven server foundation for building modular web services

The core of the Zerva ecosystem - a minimal, powerful foundation for creating event-driven web services with a clean module system and lifecycle management.

✨ Features

  • 🎯 Event-driven architecture - Clean separation of concerns
  • πŸ”§ Modular design - Composable, reusable modules
  • ⚑ Lightweight core - Minimal footprint, maximum flexibility
  • πŸ”„ Lifecycle management - Structured startup and shutdown
  • πŸŽͺ Dependency system - Automatic module loading and ordering
  • πŸ“‘ Global context - Simplified event bus with implicit context
  • πŸ› οΈ TypeScript first - Full type safety and IDE support
  • 🎨 Clean APIs - Intuitive, consistent module interfaces

πŸ“¦ Installation

npm install @zerva/core
# or
pnpm add @zerva/core
# or
yarn add @zerva/core

πŸš€ Quick Start

Basic Server Setup

// main.ts
import { serve } from '@zerva/core'
import { useHttp } from '@zerva/http'

// Setup HTTP server
useHttp({
  port: 8080,
  showServerInfo: true
})

// Start the server
serve()

Package.json Setup

{
  "name": "my-zerva-app",
  "type": "module",
  "version": "1.0.0",
  "scripts": {
    "dev": "zerva",
    "build": "zerva build",
    "start": "node dist/main.cjs"
  },
  "devDependencies": {
    "@zerva/bin": "^0.73.0",
    "@zerva/core": "^0.73.0",
    "@zerva/http": "^0.73.0"
  }
}

Run Your Server

npm run dev
# Server starts with hot-reload
# 🌐 Zerva: http://localhost:8080 (🚧 DEVELOPMENT)

πŸ“‹ Core Concepts

Event-Driven Architecture

Zerva uses a global event system where modules communicate through events:

import { on, emit } from '@zerva/core'

// Listen to events
on('userRegistered', async (user) => {
  console.log(`Welcome ${user.name}!`)
  await sendWelcomeEmail(user)
})

// Emit events
await emit('userRegistered', { name: 'Alice', email: 'alice@example.com' })

Module System

Modules are functions that hook into the event system:

function useCounter() {
  let count = 0
  
  // Listen to HTTP module initialization
  on('httpInit', ({ onGET }) => {
    onGET('/counter', () => `Count: ${++count}`)
    onGET('/reset', () => {
      count = 0
      return 'Reset!'
    })
  })
  
  return {
    getCount: () => count
  }
}

// Use the module
const counter = useCounter()

πŸ”„ Lifecycle Management

Server Lifecycle Events

The serve() function orchestrates the application lifecycle:

import { on, serve } from '@zerva/core'

// Lifecycle events (in order)
on('serveInit', async () => {
  console.log('πŸ”§ Initializing modules...')
  // Setup configuration, database connections, etc.
})

on('serveStart', async () => {
  console.log('πŸš€ Starting services...')
  // Start HTTP servers, connect to external services, etc.
})

on('serveStop', async () => {
  console.log('πŸ›‘ Stopping services...')
  // Close connections, cleanup resources
})

on('serveDispose', async () => {
  console.log('🧹 Final cleanup...')
  // Release final resources
})

serve()

Graceful Shutdown

Zerva handles graceful shutdown automatically:

// Automatic handling of SIGTERM, SIGINT signals
// Calls serveStop() and serveDispose() in order
// Your cleanup code runs before process exit

πŸ”Œ Module Dependencies

Declaring Dependencies

Ensure modules load in the correct order:

import { register } from '@zerva/core'

function useDatabase() {
  register('database', []) // No dependencies
  
  on('serveInit', async () => {
    // Initialize database connection
  })
}

function useUserService() {
  register('userService', ['database', 'http']) // Depends on database and http
  
  on('serveInit', async () => {
    // Setup user routes and database queries
  })
}

Module Registration

function useAuth() {
  register('auth', ['http', 'database'])
  
  let sessions = new Map()
  
  on('httpInit', ({ onPOST, addMiddleware }) => {
    // Add auth middleware
    addMiddleware((req, res, next) => {
      // Check authentication
      next()
    })
    
    onPOST('/login', async (req) => {
      // Handle login
      return { token: 'abc123' }
    })
  })
  
  return {
    validateSession: (token) => sessions.has(token)
  }
}

πŸŽͺ Advanced Usage

Custom Module with Events

function useNotifications() {
  register('notifications')
  
  const subscribers = new Set()
  
  // Listen to app events
  on('userRegistered', async (user) => {
    await emit('notificationSend', {
      to: user.email,
      subject: 'Welcome!',
      body: 'Thanks for joining!'
    })
  })
  
  on('notificationSend', async (notification) => {
    console.log(`πŸ“§ Sending: ${notification.subject}`)
    // Send email, push notification, etc.
  })
  
  return {
    subscribe: (callback) => subscribers.add(callback),
    unsubscribe: (callback) => subscribers.delete(callback)
  }
}

Multiple Contexts

For advanced use cases with isolated contexts:

import { createContext, withContext } from '@zerva/core'

const mainContext = createContext()
const workerContext = createContext()

// Main application
withContext(mainContext, () => {
  useHttp({ port: 3000 })
  serve()
})

// Worker process
withContext(workerContext, () => {
  useBackgroundJobs()
  serve()
})

Conditional Module Loading

function setupEnvironment() {
  if (process.env.NODE_ENV === 'development') {
    useDevelopmentTools()
    useHotReload()
  }
  
  if (process.env.ENABLE_METRICS === 'true') {
    useMetrics()
    useHealthChecks()
  }
}

function useDevelopmentTools() {
  on('httpInit', ({ onGET }) => {
    onGET('/dev/reload', () => {
      process.exit(0) // Will be restarted by zerva-bin
    })
  })
}

πŸ§ͺ Testing

Testing Modules

// test/counter.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { createContext, withContext, emit, on } from '@zerva/core'

describe('Counter Module', () => {
  let context
  
  beforeEach(() => {
    context = createContext()
  })
  
  it('should increment counter', async () => {
    let counter = 0
    
    await withContext(context, async () => {
      on('increment', () => counter++)
      await emit('increment')
      expect(counter).toBe(1)
    })
  })
})

Integration Testing

// test/integration.test.ts
import request from 'supertest'
import { serve } from '@zerva/core'
import { useHttp } from '@zerva/http'

describe('Server Integration', () => {
  it('should start server and respond', async () => {
    const { app } = useHttp({ port: 0 }) // Random port
    
    on('httpInit', ({ onGET }) => {
      onGET('/health', () => ({ status: 'ok' }))
    })
    
    await serve()
    
    const response = await request(app)
      .get('/health')
      .expect(200)
    
    expect(response.body.status).toBe('ok')
  })
})

πŸ—οΈ Project Structure

Recommended Structure

my-zerva-app/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.ts              # Entry point
β”‚   β”œβ”€β”€ modules/             # Custom modules
β”‚   β”‚   β”œβ”€β”€ auth.ts         # Authentication module
β”‚   β”‚   β”œβ”€β”€ users.ts        # User management
β”‚   β”‚   └── notifications.ts # Notification system
β”‚   β”œβ”€β”€ routes/             # HTTP route handlers
β”‚   β”œβ”€β”€ services/           # Business logic
β”‚   β”œβ”€β”€ utils/              # Shared utilities
β”‚   └── types/              # TypeScript types
β”œβ”€β”€ test/                   # Test files
β”œβ”€β”€ dist/                   # Built files (auto-generated)
β”œβ”€β”€ package.json
└── tsconfig.json

Module Organization

// src/modules/users.ts
import { register, on } from '@zerva/core'

export function useUsers() {
  register('users', ['database', 'http'])
  
  const users = new Map()
  
  on('httpInit', ({ onGET, onPOST }) => {
    onGET('/users', () => Array.from(users.values()))
    onPOST('/users', async (req) => {
      const user = { id: Date.now(), ...req.body }
      users.set(user.id, user)
      
      await emit('userCreated', user)
      return user
    })
  })
  
  return {
    getUser: (id) => users.get(id),
    getAllUsers: () => Array.from(users.values())
  }
}

πŸ”§ Conditional Compilation

Use build-time defines for environment-specific code:

// This code only runs in development
if (ZERVA_DEVELOPMENT) {
  console.log('Development mode - enabling debug features')
  useDevelopmentTools()
}

// This code only runs in production
if (ZERVA_PRODUCTION) {
  useProductionOptimizations()
  useMonitoring()
}

// Alternative syntax
if (process.env.ZERVA_DEVELOPMENT === 'true') {
  // Development-only code
}

Available Defines

  • ZERVA_DEVELOPMENT: true in development mode
  • ZERVA_PRODUCTION: true in production builds

πŸ“š Available Modules

Core ecosystem modules you can use with @zerva/core:

🐳 Docker Integration

Zerva works perfectly with Docker:

# Dockerfile
FROM node:18-alpine
WORKDIR /app

# Copy package files
COPY package*.json ./
RUN npm ci --only=production

# Copy built application
COPY dist/ ./dist/

# Expose port
EXPOSE 3000

# Start the server
CMD ["node", "dist/main.cjs"]
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - ZERVA_PRODUCTION=true

πŸ“š Examples

Check out the examples directory for complete applications:

🀝 Related Projects

  • zeed - Utility library and logging system
  • zeed-dom - Lightweight offline DOM for server-side rendering

πŸ“„ License

MIT License - see LICENSE file for details.

πŸ™‹β€β™‚οΈ Support