Packages
lustre
4.0.0-rc1
5.7.1
5.7.0
5.6.0
5.5.2
5.5.1
5.5.0
5.4.0
5.3.5
5.3.4
5.3.3
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.1
5.1.0
5.0.3
5.0.2
5.0.1
5.0.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-rc1
4.0.0-rc.2
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.0.1
2.0.0
1.3.0
1.2.0
1.1.0
1.0.0
Create HTML templates, single page applications, Web Components, and real-time server components in Gleam!
Current section
Files
Jump to
Current section
Files
src/server-runtime.ffi.mjs
import { Ok, isEqual } from "./gleam.mjs";
import {
AddRenderer,
Dispatch,
Event,
RemoveRenderer,
Shutdown,
} from "./lustre/internals/runtime.mjs";
export class LustreServerApplication {
#queue = [];
#effects = [];
#didUpdate = false;
#vdom = null;
#handlers = new Map();
#renderers = new Set();
#model = null;
#update = null;
#view = null;
static start(flags, init, update, view) {
const app = new LustreServerApplication(init(flags), update, view, root);
return new Ok((msg) => app.send(msg));
}
// PUBLIC METHODS ------------------------------------------------------------
constructor([model, effects], update, view) {
this.#model = model;
this.#update = update;
this.#view = view;
this.#vdom = this.#view(this.#model);
this.#effects = effects.all.toArray();
this.#didUpdate = true;
globalThis.queueMicrotask(() => this.#tick());
}
send(action) {
switch (true) {
case action instanceof AddRenderer: {
this.#renderers.add(action[0]);
return;
}
case action instanceof Dispatch: {
this.#queue.push(action[0]);
this.#tick();
return;
}
case action instanceof Event: {
const [event, data] = action;
if (this.#handlers.has(event)) {
const msg = this.#handlers.get(event)(data);
if (msg.isOk()) {
this.#queue.push(msg[0]);
this.#tick();
}
}
}
case action instanceof RemoveRenderer: {
this.#renderers.delete(action[0]);
return;
}
case action instanceof Shutdown: {
this.#shutdown();
return;
}
default:
return;
}
}
// PRIVATE METHODS -----------------------------------------------------------
#tick() {
this.#flush_queue();
if (this.#didUpdate) {
this.#vdom = this.#view(this.#model);
for (const renderer of this.#renderers) {
renderer.render(this.#vdom);
}
}
}
#flush_queue(iterations = 0) {
while (this.#queue.length) {
const [next, effects] = this.#update(this.#model, this.#queue.shift());
this.#model = next;
this.#didUpdate ||= !isEqual(this.#model, next);
this.#effects = this.#effects.concat(effects.all.toArray());
}
while (this.#effects.length) {
this.#effects.shift()(
(msg) => this.send(new Dispatch(msg)),
(event, data) => this.emit(event, data),
);
}
if (this.#queue.length) {
if (iterations < 5) {
this.#flush_queue(++iterations);
} else {
window.requestAnimationFrame(() => this.#tick());
}
}
}
#shutdown() {
this.#model = null;
this.#queue = [];
this.#effects = [];
this.#didUpdate = false;
this.#update = () => {};
this.#view = () => {};
this.#vdom = null;
this.#handlers = new Map();
this.#renderers = new Set();
}
}
export const start = (app, selector, flags) =>
LustreClientApplication.start(
flags,
selector,
app.init,
app.update,
app.view,
);