1 | # Hypermedia Pipeline
|
2 |
|
3 | This project provides helper functions and default implementations for creating Hypermedia Processing Pipelines.
|
4 |
|
5 | It uses reducers and continuations to create a simple processing pipeline that can pre-and post-process HTML, JSON, and other hypermedia.
|
6 |
|
7 | # Status
|
8 |
|
9 | [![codecov](https://img.shields.io/codecov/c/github/adobe/hypermedia-pipeline.svg)](https://codecov.io/gh/adobe/hypermedia-pipeline)
|
10 | [![CircleCI](https://img.shields.io/circleci/project/github/adobe/hypermedia-pipeline.svg)](https://circleci.com/gh/adobe/parcel-plugin-htl)
|
11 | [![GitHub license](https://img.shields.io/github/license/adobe/hypermedia-pipeline.svg)](https://github.com/adobe/hypermedia-pipeline/blob/master/LICENSE.txt)
|
12 | [![GitHub issues](https://img.shields.io/github/issues/adobe/hypermedia-pipeline.svg)](https://github.com/adobe/hypermedia-pipeline/issues)
|
13 | [![npm](https://img.shields.io/npm/dw/@adobe/hypermedia-pipeline.svg)](https://www.npmjs.com/package/@adobe/hypermedia-pipeline) [![Greenkeeper badge](https://badges.greenkeeper.io/adobe/hypermedia-pipeline.svg)](https://greenkeeper.io/)
|
14 |
|
15 | ## Anatomy of a Pipeline
|
16 |
|
17 | A pipeline consists of following main parts:
|
18 |
|
19 | - pre-processing functions
|
20 | - the main response generating function
|
21 | - an optional wrapper function
|
22 | - post-processing functions
|
23 |
|
24 | Each step of the pipeline is processing a single payload object, that will slowly accumulate the `return` values of the functions above through `Object.assign`.
|
25 |
|
26 | See below for the anatomy of a payload.
|
27 |
|
28 | Typically, there is one pipeline for each content type supported and pipeline are identified by file name, e.g.
|
29 |
|
30 | - `html.pipe.js` – creates HTML documents with the `text/html` content-type
|
31 | - `json.pipe.js` – creates JSON documents with the `application/json` content-type
|
32 |
|
33 | ### Building a Pipeline
|
34 |
|
35 | A pipeline builder can be created by creating a CommonJS module that exports a function `pipe` which accepts following arguments and returns a Pipeline function.
|
36 |
|
37 | - `cont`: the main function that will be executed as a continuation of the pipeline
|
38 | - `params`: a map of parameters that are interpreted at runtime
|
39 | - `secrets`: a map of protected configuration parameters like API keys that should be handled with care. By convention, all keys in `secret` are in ALL_CAPS_SNAKE_CASE.
|
40 | - `logger`: a [Winston](https://www.github.com/winstonjs/winston) logger
|
41 |
|
42 | This project's main entry provides a helper function for pipeline construction and a few helper functions, so that a basic pipeline can be constructed like this:
|
43 |
|
44 | ```javascript
|
45 | // the pipeline itself
|
46 | const pipeline = require("@adobe/hypermedia-pipeline");
|
47 | // helper functions and log
|
48 | const { adaptOWRequest, adaptOWResponse, log } = require('@adobe/hypermedia-pipeline/src/defaults/default.js');
|
49 |
|
50 | module.exports.pipe = function(cont, params, secrets, logger = log) {
|
51 | logger.log("debug", "Constructing Custom Pipeline");
|
52 |
|
53 | return pipeline()
|
54 | .pre(adaptOWRequest) // optional: turns OpenWhisk-style arguments into a proper payload
|
55 | .once(cont) // required: execute the continuation function
|
56 | .post(adaptOWResponse) // optional: turns the Payload into an OpenWhisk-style response
|
57 | }
|
58 | ```
|
59 |
|
60 | In a typical pipeline, you will add additional processing steps as `.pre(require('some-module'))` or as `.post(require('some-module'))`.
|
61 |
|
62 | ### The Main Function
|
63 |
|
64 | The main function is typically a pure function that converts the `request`, `context`, and `content` properties of the payload into a `response` object.
|
65 |
|
66 | In most scenarios, the main function is compiled from a template in a templating language like HTL, JST, or JSX.
|
67 |
|
68 | Typically, there is one template (and thus one main function) for each content variation of the file type. Content variations are identified by a selector (the piece of the file name before the file extension, e.g. in `example.navigation.html` the selector would be `navigation`). If no selector is provided, the template is the default template for the pipeline.
|
69 |
|
70 | Examples of possible template names include:
|
71 |
|
72 | - `html.jsx` (compiled to `html.js`) – default for the HTML pipeline
|
73 | - `html.navigation.jst` (compiled to `html.navigation.js`) – renders the navigation
|
74 | - `dropdown.json.js` (not compiled) – creates pure JSON output
|
75 | - `dropdown.html.htl` (compiled to `dropdown.html.js`) – renders the dropdown component
|
76 |
|
77 |
|
78 | ### (Optional) The Wrapper Function
|
79 |
|
80 | Sometimes it is neccessary to pre-process the payload in a template-specific fashion. This wrapper function (often called "Pre-JS" for brevity sake) allows the full transformation of the pipeline's payload.
|
81 |
|
82 | Compared to the pipeline-specific pre-processing functions which handle the request, content, and response, the focus of the wrapper function is implementing business logic needed for the main template function. This allows for a clean separation between:
|
83 |
|
84 | 1. presentation (in the main function, often expressed in declarative templates)
|
85 | 2. business logic (in the wrapper function, often expressed in imperative code)
|
86 | 3. content-type specific implementation (in the pipeline, expressed in functional code)
|
87 |
|
88 | A simple implementation of a wrapper function would look like this:
|
89 |
|
90 | ```javascript
|
91 | // All wrapper functions must export `pre`
|
92 | // The functions takes following arguments:
|
93 | // - `cont` (the continuation function, i.e. the main template function)
|
94 | // - `payload` (the payload of the pipeline)
|
95 | module.exports.pre = (cont, payload) => {
|
96 | const {request, content, context, response} = payload;
|
97 |
|
98 | // modifying the payload content before invoking the main function
|
99 | content.hello = 'World';
|
100 | const modifiedpayload = {request, content, context, response};
|
101 |
|
102 | // invoking the main function with the new payload. Capturing the response
|
103 | // payload for further modification
|
104 |
|
105 | const responsepayload = cont(modifiedpayload);
|
106 |
|
107 | // Adding a value to the payload response
|
108 | const modifiedresponse = modifiedpayload.response;
|
109 | modifiedresponse.hello = 'World';
|
110 |
|
111 | return Object.assign(modifiedpayload, modifiedresponse);
|
112 | }
|
113 | ```
|
114 |
|
115 | ### Pre-Processing Functions
|
116 |
|
117 | Pre-Processing functions are meant to:
|
118 |
|
119 | - parse and process request parameters
|
120 | - fetch and parse the requested content
|
121 | - transform the requested content
|
122 |
|
123 | ### Post-Processing Functions
|
124 |
|
125 | Post-Processing functions are meant to:
|
126 |
|
127 | - process and transform the response
|
128 |
|
129 | ## Anatomy of the Payload
|
130 |
|
131 | Following main properties exist:
|
132 |
|
133 | - `request`
|
134 | - `content`
|
135 | - `response`
|
136 | - `context`
|
137 | - `error`
|
138 |
|
139 | ### The `request` object
|
140 |
|
141 | - `params`: a map of request parameters
|
142 | - `headers`: a map of HTTP headers
|
143 |
|
144 | ### The `content` object
|
145 |
|
146 | - `body`: the unparsed content body as a `string`
|
147 | - `mdast`: the parsed [Markdown AST](https://github.com/syntax-tree/mdast)
|
148 | - `meta`: a map metadata properties, including
|
149 | - `title`: title of the document
|
150 | - `intro`: a plain-text introduction or description
|
151 | - `type`: the content type of the document
|
152 | - `htast`: the HTML AST
|
153 | - `html`: a string of the content rendered as HTML
|
154 | - `children`: an array of top-level elements of the HTML-rendered content
|
155 |
|
156 | ### The `response` object
|
157 |
|
158 | - `body`: the unparsed response body as a `string`
|
159 | - `headers`: a map of HTTP response headers
|
160 | - `status`: the HTTP status code
|
161 |
|
162 | ### The `context` object
|
163 |
|
164 | TBD: used for stuff that is neither content, request, or response
|
165 |
|
166 | ### The `error` object
|
167 |
|
168 | This object is only set when there has been an error during pipeline processing. Any step in the pipeline may set the `error` object. Subsequent steps should simply skip any processing if they encounter an `error` object.
|
169 |
|
170 | Alternatively, steps can attempt to handle the `error` object, for instance by generating a formatted error message and leaving it in `response.body`.
|
171 |
|
172 | The only known property in `error` is
|
173 |
|
174 | - `message`: the error message
|