Skip to content

Instantly share code, notes, and snippets.

@joshnuss
Last active August 12, 2021 15:32
Show Gist options
  • Save joshnuss/926ea35d2383b14452a8306962ae5e21 to your computer and use it in GitHub Desktop.
Save joshnuss/926ea35d2383b14452a8306962ae5e21 to your computer and use it in GitHub Desktop.
Builder DSL for SvelteKit responses
/*
In SvelteKit, endpoints return an object, like:
{
status: 200,
headers: {...},
body: JSON.stringify({a: 1})
}
This is an exploration into how it feels using a DSL instead. Like express and others use.
From svelte-kits' perspective it's the same data. The DSL is returning an object that exposes a `.then()`, so it acts like a `Promise`
Examples:
// in src/routes/example.js
import { res } from '$lib/builder'
export function get(req) {
//return res().status(200)
//return res().type('application/json')
//return res().type('json')
//return res().json({test: 1234})
//return res().text('hello world')
//return res().redirect('http://google.com')
//return res().error('whoops')
//return res().cookie(name, value)
//return res().clearCookie(name)
//return res().attachment('foo.csv').body('hello,world')
//return res().download('src/routes/index.svelte')
}
*/
import mime from 'mime-types'
import path from 'path'
import fs from 'fs'
class Response {
constructor() {
this._body = null
this._status = 200
this._headers = []
this._cookies = []
}
json(data) {
this.type('application/json')
this._body = JSON.stringify(data)
return this
}
text(data) {
this.type('text/plain')
this._body = data
return this
}
type(value) {
value = mime.lookup(value) || value
this.header('content-type', value)
return this
}
header(name, value) {
this._headers[name] = value
return this
}
headers(entries) {
Object
.entries(entries)
.map(this.header.bind(this))
return this
}
status(code) {
this._status = code
return this
}
redirect(url, code = 302) {
return this
.status(code)
.header('location', url)
}
error(message) {
this.status(500)
this._error = message
return this
}
cookie(name, value, options = {}) {
this._cookies.push({name, value, options})
return this
}
clearCookie(name) {
return this.cookie(name, '', {expires: new Date(2000, 0, 1)})
}
attachment(filename=null) {
if (filename) {
this
.header('content-disposition', `attachment; filename="${filename}"`)
.header('content-type', mime.lookup(filename))
} else {
this.header('content-disposition', 'attachment')
}
return this
}
download(filepath, name=null) {
const data = fs.readFileSync(filepath)
return this
.attachment(name || path.basename(filepath))
.body(data)
}
body(data) {
this._body = data
return this
}
then(resolve) {
console.log(this)
const cookieStrings = this._cookies.map(formatCookie)
const headers = Object.assign({}, this._headers, {'set-cookie': cookieStrings})
resolve({
headers,
status: this._status,
body: this._body,
redirection: this._redirect,
error: this._error
})
}
}
// missing encoding and and optional encode function parameter
// missing value signing
function formatCookie({name, value, options = {}}) {
let string = `${name}=${value}`
if (options.expires) {
string += `;Expires=${options.expires}`
}
if (options.maxAge) {
string += `;Max-Age=${options.maxAge}`
}
if (options.domain) {
string += `;Domain=${options.domain}`
}
if (options.path) {
string += `;Path=${options.path}`
}
if (options.secure) {
string += `;Secure`
}
if (options.httpOnly) {
string += `;HttpOnly`
}
if (options.sameSite) {
string += `;SameSite=${options.sameSite}`
}
return string
}
export function res() {
return new Response()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment