Stone.jsDocs
Paradigm

Start here

Your first domain

The domain is the part of your application that would still be true on any platform: its entities, its rules, its use cases. You write it first, and you write it without ever naming a server, a request object or a runtime.

#A service and a handler

Two pieces. A service holds the logic and the state; a handler exposes intentions and delegates to the service. The handler reads values off an event and returns plain data, nothing platform-shaped.

declarativeimperative
import { Service } from '@stone-js/core'

interface Task { id: string, title: string, done: boolean }

@Service({ alias: 'tasks' })
export class TaskService {
  private readonly items = new Map<string, Task>()

  list (): Task[] { return [...this.items.values()] }

  add (title: string): Task {
    const task: Task = { id: crypto.randomUUID(), title, done: false }
    this.items.set(task.id, task)
    return task
  }
}
import { defineService } from '@stone-js/core'

const TaskService = () => {
  const items = new Map()
  return {
    list: () => [...items.values()],
    add: (title) => {
      const task = { id: crypto.randomUUID(), title, done: false }
      items.set(task.id, task)
      return task
    }
  }
}

export const services = [defineService(TaskService, { alias: 'tasks' }, true)]
import { IncomingHttpEvent } from '@stone-js/http-core'
import { Get, Post, EventHandler } from '@stone-js/router'

@EventHandler('/tasks')
export class TaskController {
  private readonly tasks: TaskService
  constructor ({ tasks }: { tasks: TaskService }) { this.tasks = tasks }

  @Get('/')
  list (): Task[] { return this.tasks.list() }

  @Post('/')
  create (event: IncomingHttpEvent): Task {
    return this.tasks.add(event.get<string>('title', 'Untitled'))
  }
}
import { defineEventHandler, defineRoutes } from '@stone-js/router'

const TaskController = ({ tasks }) => ({
  list: () => tasks.list(),
  create: (event) => tasks.add(event.get('title', 'Untitled'))
})

export const routes = defineRoutes([
  [defineEventHandler(TaskController, 'list'),   { path: '/tasks', method: 'GET' }],
  [defineEventHandler(TaskController, 'create'), { path: '/tasks', method: 'POST' }]
])

#What is deliberately missing

There is no port, no listen(), no request or response type, no mention of Node or the browser. The handler takes an intention (event.get('title')) and returns a value. Where that intention comes from, and where the value goes, is the context's job, added next.

Write the what. The where comes later, and never leaks back in.

#Injection, not lookup

The controller does not fetch its service; it receives it. @Service({ alias: 'tasks' }) registers the service under a name, and the container hands it to the controller's constructor. Everything flows toward the domain, which is what keeps the domain unaware of everything around it.

app/Tasks.tsts
constructor ({ tasks }: { tasks: TaskService }) {
  this.tasks = tasks   // resolved from the container by its alias
}

Stone.js

Your app exists in every runtime. Until you run it.

An open-source project by Stone Foundation
Created by Mr. Stone (Evens Pierre)