Stone.jsDocs
Paradigm

Essentials

Outgoing response

The mirror of the incoming event: what your handler returns is turned into a response by the active context. Return plain data and let the context shape it, or take control with an explicit response when the situation calls for it.

#Return data, stay portable

The principle

A handler that builds a platform response is tied to that platform. A handler that returns a value leaves the shaping to the boundary, so the same handler yields JSON on an API and a rendered view in the browser.

In Stone.js

Return an object or array and it becomes the body, serialised by the context. Throw a domain error and the error handler maps it to the right status. Most handlers never build a response at all.

app/Tasks.tsts
@Get('/:id')
show (event: IncomingHttpEvent) {
  return this.tasks.find(event.get('id'))   // -> 200 + JSON, on an HTTP context
}

#Taking control

When you need a specific status, headers, or cookies, build a response with the HTTP layer's helpers and set what you need.

app/Tasks.tsts
import { jsonHttpResponse } from '@stone-js/http-core'

@Post('/')
create (event: IncomingHttpEvent) {
  const task = this.tasks.add(event.get('title'))
  const response = jsonHttpResponse(task, 201)   // explicit status
  response.setHeader('Location', `/tasks/${task.id}`)
  return response
}

#A status without naming a platform

Those helpers are HTTP, which is right in a handler that knows it serves HTTP and wrong in a module meant to run anywhere. There are two portable ways to answer with a status, and both leave the shaping to the boundary:

app/Tasks.tsts
// on the route, when the status is a constant of the endpoint
@Post('/', { response: { type: 'json', status: 201 } })
create (event: IncomingHttpEvent) { return this.tasks.add(event.get('title')) }

// from the handler, when the status is decided at run time
async handle (event: IncomingEvent) {
  const report = await this.checks.run()
  return { content: report, statusCode: report.ok ? 200 : 503 }
}

The second form is the kernel's own: an object carrying a statusCode is handed to the platform's resolver, which turns it into an HTTP response here and into an exit code on a CLI. Building the agnostic OutgoingResponse works too and takes the same road. Nothing in either form names a platform, which is what lets a module serve every context.

#Response helpers

HelperTypeDescription
jsonHttpResponse(data, status?)(data, status) => ResponseA JSON body with an optional status.
htmlHttpResponse(html, status?)(html, status) => ResponseAn HTML body.
noContentHttpResponse()() => ResponseA 204 with no body.
jsonpHttpResponse(data, cb)(data, callback) => ResponseA JSONP body for a named callback.
redirectHttpResponse(url, status?)(url, status) => ResponseA redirect (default 302).
fileHttpResponse(file)(file) => ResponseStream or download a file.
emptyHttpResponse()() => ResponseAn empty body (defaults to 200).

For the common failures there are status shortcuts, so you can be explicit when throwing a domain error is not the right fit: badRequestHttpResponse, unauthorizedHttpResponse, forbiddenHttpResponse, notFoundHttpResponse, serverErrorHttpResponse.

#Shaping a response

Any response is fluent: set the status, headers, cookies and caching hints before returning it. The caching setters (setEtag, setLastModified) let a handler participate in conditional requests without touching the platform.

app/Reports.tsts
return jsonHttpResponse(report)
  .setStatus(200)
  .setHeader('Cache-Control', 'public, max-age=60')
  .setEtag(report.hash)            // enables 304 on re-request
  .setLastModified(report.updatedAt)
SetterTypeDescription
setStatus(code)(number) => thisThe HTTP status.
setHeader(name, value)(name, value) => thisA response header.
setCookie(name, value, opts?)(name, value, opts) => thisA cookie (see Cookies).
setContent(data)(data) => thisReplace the body.
setEtag(tag) / setLastModified(date)cachingConditional-request hints for 304 handling.

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)