Express PREP
A Connect/Express style middleware to send Per Resource Events.
Installation
Install Express PREP and Express Accept Events using your favourite package manager.
npm|pnpm|yarn add express-prep express-accept-events
Usage
Consider using the Express Negotiate Events package instead for a simplified notifications setup.
We are going to describe here a non-trivial implementation of Express PREP to serve notifications with deltas.
Setup
Add the following imports to your server:
import acceptEvents from "express-accept-events";
import prep from "express-prep";
import eventID from "express-prep/event-id";
import * as negotiate from "express-prep/negotiate";
import * as templates from "express-prep/templates";
Invocation
Invoke the middleware in your server. In case one is using an Express server:
const app = express();
app.use(acceptEvents, eventID, prep);
The Event ID middleware populates the response with a lastEventID
property and a setEventID
method. Using this middleware is optional but recommended.
The PREP middleware populates the response object with a events.prep
object that provide methods to configure, send and trigger notifications.
Sending Notifications
We used the Accept-Events
middleware to already parse the Accept-Events
header field. This populates res.acceptEvents
with the notifications request headers.
First configure notifications using res.events.prep.configure()
in the GET
handler.
To send notifications call res.events.prep.send()
in your GET
handler:
app.get("/foo", (req, res) => {
const body = getContent(req.url);
const headers = getMediaType(responseBody);
let failStatus = res.events.prep.configure(
`accept=("message/rfc822"; delta="text/plain")`,
);
function negotiateEvents(defaultEvents) {
const cType = defaultEvents["content-type"];
if (cType[0].toString() === "message/rfc822" && cType.length > 2) {
if (cType[2].has("delta")) {
const match = negotiate.type(
cType[2].get("delta"),
cType[1].get("delta"),
);
if (match) {
cType[1].set("delta", match);
} else {
cType[1].delete("delta");
}
}
return defaultEvents;
}
}
if (!failStatus) {
for (const [protocol, params] of req.acceptEvents || []) {
if (protocol === "prep") {
const eventsStatus = res.events.prep.send({
body,
headers,
params,
modifiers: {
negotiateEvents,
},
});
if (!eventsStatus) return;
if (!failStatus) {
failStatus = eventsStatus;
}
}
}
}
if (failStatus) {
headers.events = serializeDictionary(failStatus);
}
res.setHeaders(new Headers(headers));
res.write(responseBody);
res.end();
});
Triggering Notifications
Now you can trigger a notification using res.events.prep.trigger()
, when the resource is modified, for example, in your PATCH
handler.
app.patch("/foo", bodyParser.text(), (req, res, next) => {
let patchSuccess = false;
if (patchSuccess) {
res.statusCode = 200;
res.setHeader("Event-ID", res.setEventID());
res.end();
return next && next();
}
});
app.patch("/foo", bodyParser.text(), (req, res) => {
function generateNotification(
negotiatedFields,
) {
const header = templates.header(negotiatedFields);
let ifDiff;
if (negotiatedFields["content-type"]?.[0] === "message/rfc822") {
const params = negotiatedFields["content-type"][1];
ifDiff = params.get("delta")?.[0].toString() === "text/plain";
}
const body = templates.rfc822({
date: res._header.match(/^Date: (.*?)$/m)?.[1],
method: req.method,
eventID: res.getHeader("event-id"),
delta: ifDiff && req.body,
});
return `${header}\r\n${body}`;
}
res.events.prep.trigger({
generateNotification,
});
});
Default Template
The generateNotification()
function when not specified at the time of triggering notification results in a default message/rfc822
format notification being generated.
This default notification is also exposed as res.events.prep.defaultNotification()
. Users may use this function to modify default values rather than calling the template:
res.events.prep.trigger({
generateNotification(negotiatedFields) {
return res.events.prep.defaultNotification({
delta: ifDiff && req.body
}),
},
});
Copyright and License
(c) 2024, Rahul Gupta and Express PREP contributors.
The source code in this repository is released under the Mozilla Public License v2.0.