Published
Edited
Jul 19, 2022
4 forks
14 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
exampleEndpoint = endpoint(
/* endpoint name, must be unique per notebook */ "example",
/* endpoint handler, written like an express handler https://expressjs.com/en/api.html#app */
async (req, res) => {
console.log(`Received request from '${req.query.name}'`, req);
console.log("Writing to response", res);
mutable handlerLog = mutable handlerLog.concat(req);
debugger; // 👈 triggers a breakpoint if DevTools is open
res.send({
msg: `Hi! ${req.query.name}`,
time: Date.now()
});
},
/* endpoint options */ {
// For _this_ example, we turn on **public livecoding** in the options with `{livecode: 'public'}`
// I production use you would only allow livecoding by a developer (the default).
// Never combine secrets and *public* livecoding!
// Any secrets bound to a *public* livecode endpoint will be exposed.
// In contrast, default livecoding secrets are only exposed to the authenticated team members,
// who presumably had access by other means anyway.
livecode: "public",
host // WEBCode is a federated compute technology, you can host it yourself on a custom domain
}
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof response = Inputs.button("Make random request (press me!)", {
reduce: async () => {
try {
console.log(`Sending request with name parameter '${name}'`);
const response = await fetch(
exampleEndpoint.href + `?name=${encodeURIComponent(name)}`
);
console.log("Got response", response);
return response.json();
} catch (err) {
return err.message;
}
}
})
Insert cell
Insert cell
response || html``
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
webserver = endpoint(
"webserver",
async (req, res, ctx) => {
try {
mutable weblog = mutable weblog.concat(Date.now());
// Forward to a flowQueue for (async) processing
const response = await viewof webRequest.send({
req,
res,
ctx
});
} catch (err) {
res.status(err.code || 500).send(err.message);
}
},
{
livecode: "public",
host
}
)
Insert cell
Insert cell
viewof webRequest = flowQueue({
timeout_ms: 1000
})
Insert cell
webRequest
Insert cell
Insert cell
Insert cell
webRequest.req.url
Insert cell
Insert cell
webRequest.req
Insert cell
Insert cell
router = {
switch (webRequest.req.url) {
default:
return viewof webRequest.resolve(viewof defaultRequest.send(webRequest));

// Form handling example
case "/form.html":
return viewof webRequest.resolve(viewof formRequest.send(webRequest));

// Image serving (SVG and PNG)
case "/weblog.svg":
return viewof webRequest.resolve(viewof imgRequest.send(webRequest));
case "/weblog.png":
return viewof webRequest.resolve(viewof imgRequest.send(webRequest));

// HTML streaming
case "/stream.html":
return viewof webRequest.resolve(viewof streamRequest.send(webRequest));
}
}
Insert cell
Insert cell
viewof defaultRequest = flowQueue()
Insert cell
defaultRequest
Insert cell
Insert cell
defaultRequestHandler = {
const message = `Unknown path ${defaultRequest.req.url}`;
defaultRequest.res.status(404).send(message);
return message;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof formRequest = flowQueue()
Insert cell
formRequest
Insert cell
formResponseContent = {
if (formRequest.req.method === "POST") {
return await viewof formPostRequest.send(formRequest);
} else if (formRequest.req.method === "GET") {
return `<form method="post">
<textarea id="note" name="note"
rows="5" cols="40"></textarea>
<div><button type="submit">Submit</button></div>
</form>`;
}
}
Insert cell
Insert cell
Insert cell
${/* Programatically refresh this cell by incrementing refreshForm */ (refreshForm, '')}
<iframe width="${width}" height="170px" src=${webserver.href + "/form.html"}></iframe>
Insert cell
Insert cell
Insert cell
formResponder = {
if (formRequest.req.id !== /* previous value of cell */ this) {
// New request, respond to it
formRequest.res.status(200).send(formResponseContent);
} else {
mutable refreshForm++;
}
return formRequest.req.id;
}
Insert cell
Insert cell
viewof formPostRequest = flowQueue()
Insert cell
formPostRequest
Insert cell
Insert cell
formDataRaw = formPostRequest.req.body
Insert cell
Insert cell
decodedFormData = new URLSearchParams(formDataRaw)
Insert cell
Insert cell
formData = Object.fromEntries(decodedFormData.entries())
Insert cell
formPostRequestResolver = {
formPostRequest;
viewof formPostRequest.resolve(
`<h2>Thanks, your note was:</h2><p>${formData.note}</p>`
);
}
Insert cell
Insert cell
viewof imgRequest = flowQueue()
Insert cell
imgRequest
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
imageData = {
if (imgRequest.req.url.endsWith(".svg")) {
return serialize(mutable weblogDataviz);
} else if (imgRequest.req.url.endsWith(".png")) {
return rasterize(mutable weblogDataviz);
} else {
const error = new Error(
`Unrecognised image extension ${imgRequest.req.url}`
);
error.code = 400;
viewof imgRequest.reject(error);
throw error;
}
}
Insert cell
Insert cell
imageData.type
Insert cell
Insert cell
imgRequestResponder = {
imgRequest.res.header("content-type", imageData.type);
imgRequest.res.send(await imageData.arrayBuffer());
}
Insert cell
Insert cell
imgRequestResolve = {
imgRequestResponder;
if (imgRequest.req.id !== this) {
viewof imgRequest.resolve("OK");
return imgRequest.req.id;
} else {
mutable refreshDashboardImage++;
return this;
}
}
Insert cell
Insert cell
${(refreshDashboardImage, '')}
<img width="${Math.min(width, 640)}" src=${webserver.href + "/weblog.svg"}></img>
Insert cell
Insert cell
${(refreshDashboardImage, '')}
<img width="${Math.min(width, 640)}" src=${webserver.href + "/weblog.png"}></img>
Insert cell
Insert cell
Insert cell
viewof streamRequest = flowQueue()
Insert cell
streamRequest
Insert cell
Insert cell
streamRequestResponse = {
yield "ok"; // Yeild a value so request processing can proceed dowstream

// Run the streaming outside of the runtime with event listeners
// Its not easily possible to handle concurrent long lived requests using dataflow
const res = streamRequest.res;
const req = streamRequest.req;
const changeHandler = () => {
console.log(`change ${req.id}`);
res.write(
`<script>document.querySelector("pre")?.remove()</script>` +
`<pre>latest: ${viewof streamValue.value}</pre>`
);
};
console.log(`opening ${req.id}`);
res.write(`<body>Streaming for request ${req.id}`);
changeHandler();

viewof streamValue.addEventListener("input", changeHandler);
invalidation.then(() => {
console.log(`close ${req.id}`);
viewof streamValue.removeEventListener("input", changeHandler);
res.end();
});
}
Insert cell
streamRequestResolver = {
streamRequestResponse;
if (this !== streamRequest.req.id) {
viewof streamRequest.resolve("ok");
} else {
mutable streamingPreviewRefresh++;
}
return streamRequest.req.id;
}
Insert cell
Insert cell
Insert cell
Insert cell
${runStreamingPreview && streamingPreviewRefresh}
<iframe width="${width}" height="100px" src=${link}></iframe>

Insert cell
Insert cell
Insert cell
Insert cell
mutable streamingPreviewRefresh = 0
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more