Wiki Events

Describes the different event sources, producers, and clients used in wiki.

This effort is experimental. The datalog plugin is the only plugin that implements this pattern. The intent is to migrate this capability into the wiki plugin subsystem should the experiment prove to be successful. server-events client-events

Plugins

Events in wiki build off of the concept of event sources. Plugins produce and consume event sources of specific types.

digraph G { PluginA -> Type [label="produces"]; PluginB -> Type [label="consumes"]; Type [label="Event Type"]; }

The client side of a plugin declares the event types it produces by exporting an array called produces from its module definition.

const produces = ['.server-source'] ... module.export = {produces, ...}

The client side of a plugin declares the event types it consumes by exporting an object literal that contains a mapping of event type to an event handler for those events.

const consumes = { '.server-source': ({pageItem, result}) => { } } ... module.exports = {consumes, ...}

The client side plugin subsystem orders the running of bind operations so that a plugin which produces an event type will complete its bind operation before the bind operations of the plugins that consume that event type.

There is no deterministic ordering of plugin loading on the server side.

Event Types, Producers, Consumers

Events of the declared type can be produced by both the client and the server side of a plugin.

A producer is something that emits events of a specific type.

A consumer is something that listens to events of a specific type.

digraph G { Type [label="Event Type"]; subgraph cluster_server { label="Server"; sProducer[label="Producer"]; sConsumer[label="Consumer"]; sProducer -> sConsumer [label="publish to"]; } subgraph cluster_client { label="Client"; cProducer[label="Producer"]; cConsumer[label="Consumer"]; cProducer -> cConsumer [label="publish to"]; } sProducer -> Type [label="of"]; cProducer -> Type [label="of"]; }

Events are always of a single type, but there can be multiple producers of that event type.

Events can be emitted by more than one producer in either half of the plugin.

Producers emit events of a specific type and identify themselves as the source of the event.

There is, similarly, no limit on the number of event consumers.

Consumers register their interest not in all events of a specific type, but rather, in all events of a specific type emitted by a specific producer.

Note that the way to identify a unique event producer varies depending on whether one is on the client or the server.

This is due to the fact that on the server, there is only one instance of a given page, but on the client, there can be many copies of the same page in the lineup.

Server Side

Server side event producers and consumers are items within pages within a wiki site.

When a new item is saved, the item's plugin has the opportunity to create and register event producers and consumers.

Other means for creating consumers may be implemented. Consumers are not required to be created during item save.

Individual producers are identified by their slugItem.

A slugItem is the slug of the page and the id of the item.

digraph G { subgraph cluster_0 { style=filled; color=lightgrey; node [style=filled,color=white]; sSource [label="Event\nSource\nInstance"]; sProducer [label="Producer"]; sConsumer [label="Consumer"]; sProducer -> sSource [label="emit\ntype, {slugItem, result}"]; sSource -> sConsumer [label="on\ntype, {slugItem, result}"]; slugItem -> sSource [label="ids", constraint=false]; label = "Server"; } }

Client Side

A client's event sources are identified by their pageItem.

A pageItem is the page key and item id of the item that is the event source.

Instances of client side event producers are created whenever an item is created, edited, or moved.

They are destroyed whenever an item is moved or deleted.

Consumers register and clean up their listeners in their bind method which gets called on initial page load and whenever an item of type Object.keys(plugin.consumes) is created or edited.

digraph G { subgraph cluster_1 { rankdir=BT; node [style=filled]; label = "Client"; cConsumer [label="Consumer"]; cSource [label="Event\nSource\nInstance"]; cProducer [label="Producer"]; cSource -> cConsumer [label="on\ntype, {pageItem, result}"]; cProducer -> cSource [label="emit\ntype, {pageItem, result}"]; pageItem -> cSource [label="ids", constraint=false]; color=blue; } }

Event Forwarding

Client producers can generate events from any source. One possible pattern is to have a client producer act as a forwarder of server side events.

To do this, a client producer registers itself as a server consumer. This causes all events emitted by the server producer to be received by the registered server consumer and forwarded on to the client producer so the producer can send them on to any client consumers. The diagram below illustrates this.

digraph G { subgraph cluster_server { style=filled; color=lightgrey; node [style=filled,color=white]; sSource [label="Event\nSource\nInstance"]; sProducer [label="Producer"]; sConsumer [label="Consumer"]; sProducer -> sSource [label="emit\ntype, {slugItem, result}"]; sSource -> sConsumer [label="on\ntype, {slugItem, result}"]; slugItem -> sSource [label="ids", constraint=false]; label = "Server"; } subgraph cluster_client { rankdir=BT; node [style=filled]; label = "Client"; cConsumer [label="Consumer"]; cSource [label="Event\nSource\nInstance"]; cProducer [label="Producer"]; cSource -> cConsumer [label="on\ntype, {pageItem, result}"]; cProducer -> cSource [label="emit\ntype, {pageItem, result}"]; pageItem -> cSource [label="ids", constraint=false]; color=blue; } sConsumer -> cProducer [label="Event forward via socket-io"]; cProducer -> sSource [label="subscribe\nslugItem"]; }

Event forwarding in this way introduces some additional challenges.

In order to prevent duplicate server side consumers from being registered, the client side producer needs to only request a new server consumer if it hasn't already registered one for the given slugItem.

The relationship between server side consumers and client side producers ends up looking like this.

digraph G { subgraph cluster_producer1 { label="Client\nPage 1 Item 1"; p1Producer[label="Producer"]; } subgraph cluster_producer2 { label="Client\nPage 2 Item 5"; p2Producer[label="Producer"]; } subgraph cluster_client1 { label="Client\nPage 1 Item 2"; c1Consumer[label="Consumer"]; } subgraph cluster_client2 { label="Client\nPage 2 Item 6"; c2Consumer[label="Consumer"]; } subgraph cluster_server { label="Server"; sConsumer[label="Consumer"]; sProducer[label="Producer"]; sProducer -> sConsumer; } sConsumer -> p1Producer; sConsumer -> p2Producer; p1Producer -> c1Consumer; p2Producer -> c2Consumer; }

Implementation Details

These details do not necessarily match the design.

The intention is to modify the implementation to match the design should the design prove useful.

This section exists primarily to help the interested reader map the existing implementation to elements within the proposed design.

The server side uses unique event emitters for each event source. They are stored in a hash keyed off of the slugItem.

An alternative design would be to use a single event emitter and then to emit events of one of the types in plugin.produces. The specific instance of the event producer can be placed in an event property called slugItem.

The client does not use a unique event emitter for each producer. Instead, it sends events with a type of one of the types in plugin.produces and passes the unique id of the producer the pageItem event property.

A client consumer listens for events of type pageItem via document.addEventListener. mdn

A client producer emits events of type pageItem via document.dispatchEvent. mdn

Client side events are emitted and consumed using the document's EventTarget methods. mdn