Packages
lustre
5.0.2
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/lustre/runtime/server/runtime.ffi.mjs
import { diff } from "../../vdom/diff.mjs";
import * as Events from "../../vdom/events.mjs";
import {
ClientDispatchedMessage,
ClientRegisteredCallback,
ClientDeregisteredCallback,
EffectDispatchedMessage,
EffectEmitEvent,
SelfDispatchedMessages,
SystemRequestedShutdown,
} from "./runtime.mjs";
import {
//
Mount,
Reconcile,
Emit,
//
AttributeChanged,
EventFired,
} from "../transport.mjs";
import { run as decode } from "../../../../gleam_stdlib/gleam/dynamic/decode.mjs";
import { Ok } from "../../../gleam.mjs";
//
export class Runtime {
#model;
#update;
#view;
#on_attribute_change;
#vdom;
#events;
#callbacks = new Set();
constructor([model, effects], view, update, on_attribute_change) {
this.#model = model;
this.#update = update;
this.#view = view;
this.#on_attribute_change = on_attribute_change;
this.#vdom = this.#view(this.#model);
this.#events = Events.from_node(this.#vdom);
this.#tick(effects.all, false);
}
send(message) {
if (this.#model === null) return;
switch (message.constructor) {
case ClientDispatchedMessage:
switch (message.message.constructor) {
case AttributeChanged: {
const { name, value } = message.messgae;
let effects = [];
const decoder = this.#on_attribute_change.get(name);
if (!decoder) break;
const result = decode(value, decoder);
if (result.constructor !== Ok) break;
const [model, more_effects] = this.#update(
this.#model,
result[0],
);
this.#model = model;
effects.push(more_effects);
while (effects.length) {
this.#tick(effects.shift().all);
}
}
case EventFired: {
const { path, name, event } = message.message;
const [events, result] = Events.handle(this.#events, path, name, event);
this.#events = events;
if (result.constructor === Ok) {
this.dispatch(result[0]);
}
return;
}
}
case ClientRegisteredCallback: {
if (this.#callbacks.has(message.callback)) return;
const mount = new Mount(this.#vdom);
this.#callbacks.add(message.callback);
message.callback(mount);
return;
}
case ClientDeregisteredCallback: {
this.#callbacks.delete(message.callback);
return;
}
case EffectDispatchedMessage: {
this.dispatch(message.message);
return;
}
case EffectEmitEvent: {
const event = new Emit(message.name, message.data);
for (const callback of this.#callbacks) {
callback(event);
}
return;
}
case SelfDispatchedMessages: {
let messages = message.messages;
let effects = [message.effect];
for (let list = messages; messages.tail; list = list.tail) {
const [model, more_effects] = this.#update(this.#model, list.head);
this.#model = model;
effects.push(more_effects);
}
while (effects.length) {
this.#tick(effects.shift().all);
}
return;
}
case SystemRequestedShutdown: {
this.#model = null;
this.#update = null;
this.#view = null;
this.#on_attribute_change = null;
this.#events = Events.new$();
this.#callbacks.clear();
}
}
}
dispatch(msg) {
const [model, effects] = this.#update(this.#model, msg);
this.#model = model;
this.#tick(effects.all, immediate);
}
#tick(effects) {
const queue = [];
const effect_params = {
root: null,
emit: (event, data) => this.#emit(event, data),
dispatch: (msg) => queue.push(msg),
select: () => {},
};
while (true) {
for (let list = effects; list.tail; list = list.tail) {
list.head(effect_params);
}
if (!queue.length) {
break;
}
const msg = queue.shift();
[this.#model, effects] = this.#update(this.#model, msg);
}
this.#render();
}
#render() {
const next = this.#view(this.#model);
const { patch, events } = diff(this.#events, this.#vdom, next);
this.#events = events;
this.#vdom = next;
const reconcile = new Reconcile(patch);
for (const callback of this.#callbacks) {
callback(reconcile);
}
}
#emit(event, data) {
const message = new Emit(event, data);
for (const callback of this.#callbacks) {
callback(message);
}
}
}
export const start = (app, flags) => {
return new Runtime(
app.init(flags),
app.update,
app.view,
app.on_attribute_change,
);
};
export const send = (runtime, message) => {
runtime.send(message);
};