{"id":17291317,"url":"https://github.com/cxres/express-prep","last_synced_at":"2026-02-27T12:44:29.139Z","repository":{"id":249325673,"uuid":"825922295","full_name":"CxRes/express-prep","owner":"CxRes","description":"A Connect/Express style middleware to send Per Resource Events.","archived":false,"fork":false,"pushed_at":"2024-11-10T00:35:26.000Z","size":177,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-28T01:01:39.969Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CxRes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-08T19:02:09.000Z","updated_at":"2024-11-10T00:35:29.000Z","dependencies_parsed_at":"2025-02-25T08:31:35.078Z","dependency_job_id":"1ef7f161-577d-4b59-ae9d-ae743bda3b61","html_url":"https://github.com/CxRes/express-prep","commit_stats":null,"previous_names":["cxres/express-prep"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CxRes%2Fexpress-prep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CxRes%2Fexpress-prep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CxRes%2Fexpress-prep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CxRes%2Fexpress-prep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CxRes","download_url":"https://codeload.github.com/CxRes/express-prep/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248877986,"owners_count":21176239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T10:40:43.430Z","updated_at":"2026-02-27T12:44:24.099Z","avatar_url":"https://github.com/CxRes.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Express PREP\n\nA Connect/Express style middleware to send [Per Resource Events](https://cxres.github.io/prep/draft-gupta-httpbis-per-resource-events.html).\n\n## Installation\n\nInstall **Express PREP** and **Express Accept Events** using your favourite package manager.\n\n```sh\nnpm|pnpm|yarn add express-prep express-accept-events\n```\n\n## Usage\n\n_Consider using the **[Express Negotiate Events](https://www.npmjs.com/package/express-negotiate-events)** package instead for a simplified notifications setup._\n\nWe are going to describe here a non-trivial implementation of **Express PREP** to serve notifications with deltas.\n\n### Setup\n\nAdd the following imports to your server:\n\n```js\n// Process the Accept-Events header\nimport acceptEvents from \"express-accept-events\";\n// PREP Middleware Factory\nimport prep from \"express-prep\";\n// EventID is optional but recommended\nimport eventID from \"express-prep/event-id\";\n// For Custom Content Negotiation Logic\nimport * as negotiate from \"express-prep/negotiate\";\n// Notification templates (or BYO)\nimport * as templates from \"express-prep/templates\";\n```\n\n### Invocation\n\nInvoke the middleware in your server. In case one is using an Express server:\n\n```js\nconst app = express();\napp.use(acceptEvents, eventID, prep);\n```\n\nThe Event ID middleware populates the response with a `lastEventID` property and a `setEventID` method. Using this middleware is optional but recommended.\n\nThe PREP middleware populates the response object with a `events.prep` object that provide methods to configure, send and trigger notifications.\n\n### Sending Notifications\n\nWe used the `Accept-Events` middleware to already parse the `Accept-Events` header field. This populates `res.acceptEvents` with the notifications request headers.\n\nFirst configure notifications using `res.events.prep.configure()` in the `GET` handler.\n\nTo send notifications call `res.events.prep.send()` in your `GET` handler:\n\n```js\napp.get(\"/foo\", (req, res) =\u003e {\n  // Get the response body first\n  const body = getContent(req.url);\n  // Get the content-* headers\n  const headers = getMediaType(responseBody);\n\n  // Configures notifications to be sent as `message/rfc822` with deltas.\n  // The default is to omit the delta.\n  let failStatus = res.events.prep.configure(\n    `accept=(\"message/rfc822\"; delta=\"text/plain\")`,\n  );\n\n  // Custom logic for negotiating media-type for deltas\n  // The headers are parsed \"npm:structured-headers\". PREP adds a second\n  // Map after parameters to List Item with requested deltas, allowing\n  // an implementor to negotiate against the configured parameters.\n  function negotiateEvents(defaultEvents) {\n    const cType = defaultEvents[\"content-type\"];\n    if (cType[0].toString() === \"message/rfc822\" \u0026\u0026 cType.length \u003e 2) {\n      // Check for additional map after parameters for the\n      // \"message/rfc822\" item\n      if (cType[2].has(\"delta\")) {\n        // Manually negotiate Media-Type for delta\n        const match = negotiate.type(\n          cType[2].get(\"delta\"),\n          cType[1].get(\"delta\"),\n        );\n        if (match) {\n          // If match, set the matched format as the delta parameter\n          cType[1].set(\"delta\", match);\n        } else {\n          // If no match, delete the delta parameter\n          cType[1].delete(\"delta\");\n        }\n      }\n      // Second Map is automatically removed\n      return defaultEvents;\n    }\n  }\n\n  // Fail quickly if server is misconfigured\n  if (!failStatus) {\n    // Iterate to the first PREP notifications request\n    for (const [protocol, params] of req.acceptEvents || []) {\n      if (protocol === \"prep\") {\n        const eventsStatus = res.events.prep.send({\n          body, // can also be a stream\n          headers,\n          params,\n          modifiers: {\n            negotiateEvents,\n          },\n        });\n\n        // if notifications are sent, you can quit\n        if (!eventsStatus) return;\n\n        // Record the first failure only\n        if (!failStatus) {\n          failStatus = eventsStatus;\n        }\n      }\n    }\n  }\n\n  // If notifications are not sent, send regular response\n  if (failStatus) {\n    // Serialize failed events as header\n    headers.events = serializeDictionary(failStatus);\n  }\n  res.setHeaders(new Headers(headers));\n  res.write(responseBody);\n  res.end();\n});\n```\n\n### Triggering Notifications\n\nNow you can trigger a notification using `res.events.prep.trigger()`, when the resource is modified, for example, in your `PATCH` handler.\n\n```js\napp.patch(\"/foo\", bodyParser.text(), (req, res, next) =\u003e {\n  let patchSuccess = false;\n\n  // ...handle the PATCH request\n\n  // notification is triggered if response is successful\n  if (patchSuccess) {\n    // set success response for notifications\n    res.statusCode = 200;\n    // set eventID, if you support it\n    res.setHeader(\"Event-ID\", res.setEventID());\n    // you can set eventID on other paths, say, in case of side effects\n    //   res.setEventID(\"/another/path\")\n    // you also set your own eventID for a given path\n    //   res.setEventID({ path: req.path, id:\"foo\" })\n    // close the response first\n    res.end();\n\n    // IMPORTANT: Go to the next middleware when request succeeds to trigger the notification\n    return next \u0026\u0026 next();\n  }\n});\n\napp.patch(\"/foo\", bodyParser.text(), (req, res) =\u003e {\n  // Define a function that generates the notification to send\n  function generateNotification(\n    negotiatedFields,\n    // which can be specific to the parsed content-* event fields\n    // for a given path specified in the trigger function\n    // (see npm:structured-headers for format)\n  ) {\n    // Generate part header from template\n    const header = templates.header(negotiatedFields);\n\n    // Check if delta is requested with the template\n    let ifDiff;\n    if (negotiatedFields[\"content-type\"]?.[0] === \"message/rfc822\") {\n      const params = negotiatedFields[\"content-type\"][1];\n      ifDiff = params.get(\"delta\")?.[0].toString() === \"text/plain\";\n    }\n\n    // Generate part body from a template\n    const body = templates.rfc822({\n      date: res._header.match(/^Date: (.*?)$/m)?.[1],\n      method: req.method,\n      eventID: res.getHeader(\"event-id\"), // (optional, but recommended)\n      // location: res.getHeader(\"Location\"), // (optional)\n      // diff from the last response\n      delta: ifDiff \u0026\u0026 req.body, // (optional)\n    });\n\n    // Return the notification\n    return `${header}\\r\\n${body}`;\n  }\n\n  // Trigger the notification\n  res.events.prep.trigger({\n    // path               // where to trigger notification\n    // (default: req.path)\n    generateNotification, // function for notification to send, defined above\n    // (default: message/rfc822 notifications with only headers)\n    // lastEvent          // Set to true to close stream after this notification\n    // (default: false)\n  });\n});\n```\n\n#### Default Template\n\nThe `generateNotification()` function when not specified at the time of triggering notification results in a default `message/rfc822` format notification being generated.\n\nThis default notification is also exposed as `res.events.prep.defaultNotification()`. Users may use this function to modify default values rather than calling the template:\n\n```js\n  res.events.prep.trigger({\n    generateNotification(negotiatedFields) {\n      // ... determine if the diff exists as before\n      return res.events.prep.defaultNotification({\n        delta: ifDiff \u0026\u0026 req.body\n      }),\n    },\n  });\n```\n\n## Copyright and License\n\n(c) 2024, [Rahul Gupta](https://cxres.pages.dev/profile#i) and Express PREP contributors.\n\nThe source code in this repository is released under the [Mozilla Public License v2.0](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcxres%2Fexpress-prep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcxres%2Fexpress-prep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcxres%2Fexpress-prep/lists"}