@zerva/core
Version:
🌱 Simple event driven server
174 lines (122 loc) • 5.16 kB
Markdown
# 🌱 Zerva
**Minimal event driven web service infrastructure.**
## Get started
It all starts with the `context` which is the common ground for any **module** we use or create. It serves as a hub/bus for emitting and listening on **events**.
Usually you would start to build a HTTP server like this:
```ts
// main.ts
import { useHttp } from '@zerva/http'
useHttp({
port: 8080
})
serve()
```
With a `package.json` like the following you get started with `npm start`:
```json
{
"name": "zerva-example",
"type": "module",
"version": "1.0.0",
"scripts": {
"start": "LEVEL=i ZEED=* zerva",
"build": "zerva build"
},
"devDependencies": {
"@zerva/bin": "*",
"@zerva/core": "*",
"@zerva/http": "*"
}
}
```
## Lifecycle of a module
`serve` itself is a **module** as well. That means it does **emit** and **listen** to **events**.
`serve` provides the basic life-cycle, which emits the following events in this order:
- `serveInit` to do some preliminary setup
- `serveStart` to actually start a module
- `serveStop` to stop the module or its service
- `serveDispose` to free and dispose objects
Listening to these events works like this:
```ts
import { on } from '@zerva/core'
on('serveStart', async () => {
console.log('Did start')
})
```
Modules themselves may follow a similar pattern to allow their sub-modules to start in the right moment as well. For example `/http` module emits `httpInit` which is the used to set up the routes or a Websocket connection for `@zerva/websocket`. You don't have to care a lot about the complexity, since one builds on the other.
By convenstion module initializers start by `use` like `useHttp` or `useWebsocket`.
## Dependencies
Some modules depend on others. To make sure nothing is missing, you should **register** modules like:
```ts
module('websocket', {
requires: ['http']
}
```
## Config
## Contexts
TBD
## useHttp example
```ts
import { zerva } from '/core'
function useCounter() {
let counter = 0
zerva.on('httpInit', ({ onGET }) => {
onGET('/counter', () => `Counter ${counter++}`)
})
}
```
As you can see a **module** is just a function connecting itself to the **context**. But where is the context? Well `context` is set globally. Similarly, as for Vue and React our helper routines like `on` or `emit` make use of it. This is a great simplification while still being a robust approach.
> If you want to use multiple **contexts** just wrap the code using it into `withContext(context, () => { /* your code */ })`.
On `httpInit` we make use of the http-modules specific `get` helper to answer to http-reuests on `/counter`.
To make sure the `http` module is around as well in our context, it is good practice to register oneself and tell about dependencies using `register`:
```ts
function useCounter() {
register('counter', ['http'])
let counter = 1
zerva.on('httpInit', ({ get }) => {
get(
'/',
() => `Counter ${counter++}.<br><br>Reload page to increase counter.`
)
})
return {
getCounter: () => counter,
}
}
```
As you can see we can also return some custom data or functions from our module, like `getCounter` in our case.
That's basically it!
## Command line convenience
For convenience, you can use the `zerva` command line tool. It is based on [estrella](https://github.com/rsms/estrella) and translates your Typescript code on the fly without further configuration. It also watches for changes and restarts the server immediately. To build add `build` as first argument. The last argument can point to the entry file (Javascript or Typescript), otherwise `src/main.ts` will be used.
In your `package.json` you might want to add these lines:
```json
{
"scripts": {
"start": "zerva",
"build": "zerva build",
"serve": "node dist/main.cjs"
}
}
```
## Docker
Zerva integrates perfectly with Docker. Learn more at [demos/docker](https://github.com/holtwick/zerva/demos/docker).
## Available Modules
- [/http](zerva-http/README.md)
- [/http-log](zerva-http-log/README.md)
- [/mqtt](zerva-mqtt/README.md)
- [/sqlite](zerva-sqlite/README.md)
- [/vite](zerva-vite/README.md)
- [/websocket](zerva-websocket/README.md)
## Conditional building
Zerva uses [esbuild](https://esbuild.github.io) to create both the development server code and the production code. You can take advantage of conditional building using [defines](https://esbuild.github.io/api/#define). This can be used to have code that avoids certain imports or otherwise unneed stuff in production mode. I.e. in your code you can do stuff like this:
```ts
if (ZERVA_DEVELEPMENT) {
/* do something */
}
```
Valid defines are:
- `ZERVA_DEVELOPMENT` is `true` when started as `zerva`
- `ZERVA_PRODUCTION` is `true` when started as `zerva build`
For better compatibility the defines can also be accessed as `process.env.ZERVA_DEVELOPMENT` and `process.env.ZERVA_PRODUCTION`.
## Related Projects
- [zeed](https://github.com/holtwick/zeed) - Helper lib providing the logging for server
- [zeed-dom](https://github.com/holtwick/zeed-dom) - Lightweight offline DOM