contentful-aggregator
v1.0.1
Published
functional contentful configuration for static site generators
Downloads
19
Maintainers
Readme
Configures content type entries for use in static site generators. Supports linked entries and localization. Built on top of Contentful.js
:warning: Please Note: this project is in early stages and follows Readme-Driven Development practices, so beware - not all the functionality below works just yet and the documentation is subject to change at a rapid rate. Consider this project as unstable for now.
:battery: Status
:point_up: Please click on any of these to see more information/context
:book: Table of Contents
:arrow_double_down: Installation
Requirements
- Node.js v4.0.0 or higher
- NPM (v3.0.0+ highly recommended) (this comes with Node.js)
Instructions
contentful-aggregator
is a Node module. So, as long as you have Node.js and NPM, installing contentful-aggregator
is as simple as running this in a terminal at the root of your project:
$ npm install contentful-aggregator --save
:books: Usage
contentful-aggregator
uses a Future-based syntax. If you are not familiar with Futures, please read this short primer.
contentful-aggregator
exposes a single curried function that receives two arguments. The first argument is your Contentful API Key, and the second argument is the ID of the space you want to obtain your data from.
import ca from 'contentful-aggregator'
const space = ca('apiToken', 'space')
Since this function is curried, you may omit the second argument. Doing so will return a function that has the apiToken, but expects the space ID.
const client = ca('apiToken')
const myPortfolioSpace = client('portfolio site space id')
const myBlogSpace = client('blog site space id')
This will return an instance of the Space
class.
API
Util
import util from 'contentful-aggregator/util'
util.pluralize()
pluralize :: String -> String
Returns the plural form of the String str
util.pluralize('octopus') // -> 'octopi'
util.pluralize('book') // -> 'books'
util.underscored()
underscored :: String -> String
Returns the underscored form of the String str
util.underscored('foo bar') // -> 'foo_bar'
util.underscored('fooBar') // -> 'foo_bar'
util.slugify()
slugify :: String -> String
Returns the slugified form of the String str
util.slugify('foo bar') // -> 'foo-bar'
util.slugify('foo_bar') // -> 'foo-bar
Space()
An instance of this class is returned once the contentful-aggregator
module is fully setup.
Space::getClient()
getClient :: -> Object
Returns a direct reference to the Contentful.js client, in case you need it to do anything that contentful-aggregator
doesn't already offer out of the box (psst... send us a pull request! :smile:)
const client = space.getClient()
client.entries(...)...
client.contentType(...)... // etc
Space::fetchInfo()
fetchInfo :: -> Future Object
Asynchronously fetches information about the current space from Contentful's content delivery API.
space.fetchInfo().fork(null, (info) => {
return info === {
sys: {
type: 'Space',
id: 'cfexampleapi'
},
name: 'Contentful Example API',
locales: [
{ code: 'en-US', name: 'English' },
{ code: 'tlh', name: 'Klingon' }
]
}
})
Space::fetchLocales()
fetchLocales :: -> Future [Object]
Asynchronously fetches an array of the locales you have configured for your space on Contentful.
space.fetchLocales().fork(null, (locales) => {
return locales === [
{ code: 'en-US', name: 'English' },
{ code: 'tlh', name: 'Klingon' }
]
})
Space::fetchLocaleCodes()
fetchLocaleCodes :: -> Future [String]
Asynchronously fetches an array of the locale codes for every locale you have configured for your space on Contentful.
space.fetchLocaleCodes().fork(null, (localeCodes) => {
return localeCodes === ['en-US', 'tlh']
}
Space::fetchContentType()
fetchContentType :: String -> [ContentType]
Asynchronously fetches information about a single content type with an id
property that matches id
from Contentful. Resolves to an instance of ContentType
space.fetchContentType('content type id').map((contentType) => {
// contentType === instanceof ContentType
contentType.setName('blog_posts')
})
ContentType()
ContentType::fetchEntries()
fetchEntries :: Object -> Future [Entry]
Asynchronously fetch all entries that match search parameter opts and belong to this ContentType. Resolves to an array of Entry instances
blogPosts.fetchEntries({
include: 3,
'fields.title[exists]': true
}).fork(posts => {
console.log(posts[0].path())
})
ContentType::path()
setEntryPath :: (Object -> any) -> [Entry] -> [Entry]
Convenience method for setting the path()
method on each entry that belongs to this ContentType. Returns an array of entries with the newly added/updated path()
method.
blogPosts.fetchEntries()
.map(blogPosts.path(post => `posts/${post.name}.html`))
Entry()
[WIP]
:mortar_board: Required Knowledge
A Short Primer on Futures
A careful design decision was made to use Futures in place of Promises for asynchronous control flow in contentful-aggregator
, and as such, a lot of the documented methods will return a Future. So, for those not familiar with them, here's a short primer.
Futures, otherwise known as Tasks, are similar to Promises, but are a more pure and functional alternative. This means they are easily composed. They look like this:
// fetch :: String -> Future String
const fetch = (url) => new Future((resolve, reject) => {
const request = new XMLHttpRequest()
request.addEventListener('load', resolve, false)
request.addEventListener('error', reject, false)
request.addEventListener('abort', reject, false)
request.open('get', url, true)
request.send()
})
Like Promises, Futures need to be unwrapped if you want to access their value. The difference from Promises, however, is that instead of using .then()
to unwrap a Future, you use either one of .chain()
or .map()
.
.chain()
creates a dependent Future. Here, we create a Future fetchJSON()
that depends on the result of sequencing fetch()
(from above) and then parseJSON()
:
// parseJSON :: String -> Future Object
const parseJSON = (str) => new Future((resolve, reject) => {
try {
resolve(JSON.parse(str))
} catch (error) {
reject(error)
}
})
// fetchJSON :: String -> Future Object
const fetchJSON = fetch.chain(parseJSON)
We can use .map()
similarly to unwrap the value and run any function on it. So, if we wanted to only get the items
property of the JSON response, that would look like this:
// fetchItems :: Future Object -> Future [Object]
const fetchItems = fetchJSON.map(json => json.items)
If we wanted to filter out only the items that are new, then we can do that too:
// getNewItems :: Future [Object] -> Future [Object]
const getNewItems = fetchItems.map(items => items.filter(item => item.new))
An important aspect of Futures that's definitely worth noting is that, unlike Promises, which execute as soon as they are created, none of the above Futures have executed yet or sent any requests over the wire. This is because a Future will only execute once you have explicitly made a call to .fork()
. This reduces side-effects and contains them to a single place. Like .then()
, .fork()
accepts an onResolved
as well as an onRejected
handler, but note the difference in order:
getNewItems('/products.json').fork(console.error, console.log) // -> onRejected, onResolved
One other interesting aspect is the ability to easily pipe logic - say we need to hit a JSON endpoint, grab a url
property, and then send another request to that url
to grab it's HTML. Simple:
// fetchUrlFromUrl :: Future Object -> Future String
const fetchUrlFromUrl = fetchJSON.map(json => json.url).chain(fetch)
The biggest strength, especially in the context of contentful-aggregator
, is the ability for Futures to be easily cached. This means you can take a Future that executes an HTTP request, but wrap it in another Future that will return any subsequent calls to .fork()
from a cache:
// getCachedNewItems :: Future [Object] -> Future [Object]
const getCachedNewItems = Future.cache(getNewItems)
// makes an HTTP request
getCachedNewItems('/products.json').fork(console.error, console.log)
// doesn't make an HTTP request, just returns from cache:
getCachedNewItems('/products.json').fork(console.error, console.log)
Futures in contentful-aggregator
are based on the Ramda-Fantasy library which include other methods like ap
, of
, chainReject
, biMap
, and reject
.