{"id":16350455,"url":"https://github.com/zspecza/contentful-aggregator","last_synced_at":"2026-05-02T05:07:53.232Z","repository":{"id":57206459,"uuid":"51080500","full_name":"zspecza/contentful-aggregator","owner":"zspecza","description":null,"archived":false,"fork":false,"pushed_at":"2020-06-01T01:12:58.000Z","size":175,"stargazers_count":0,"open_issues_count":34,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-29T03:44:52.434Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zspecza.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"contributing.md","funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-04T14:18:57.000Z","updated_at":"2019-08-06T19:53:53.000Z","dependencies_parsed_at":"2022-09-04T03:10:50.135Z","dependency_job_id":null,"html_url":"https://github.com/zspecza/contentful-aggregator","commit_stats":null,"previous_names":["declandewet/contentful-aggregator"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/zspecza/contentful-aggregator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zspecza%2Fcontentful-aggregator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zspecza%2Fcontentful-aggregator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zspecza%2Fcontentful-aggregator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zspecza%2Fcontentful-aggregator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zspecza","download_url":"https://codeload.github.com/zspecza/contentful-aggregator/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zspecza%2Fcontentful-aggregator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29833644,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-25T17:57:15.019Z","status":"ssl_error","status_checked_at":"2026-02-25T17:56:11.472Z","response_time":61,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-11T01:05:01.696Z","updated_at":"2026-02-25T18:01:40.164Z","avatar_url":"https://github.com/zspecza.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![contentful-aggregator](http://imgh.us/logo_187.svg)\n\n\n\n\u003e Configures content type entries for use in static site generators. Supports linked entries and localization. Built on top of [Contentful.js](https://github.com/contentful/contentful.js)\n\u003e \n\u003e ​:warning: **Please Note**: this project is in early stages and follows [Readme-Driven Development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html) 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.\n\n\n\n# :battery: Status\n\n[![GitHub license](https://img.shields.io/github/license/declandewet/contentful-aggregator.svg?style=flat-square)](https://github.com/declandewet/contentful-aggregator/blob/master/license.md)[![GitHub release](https://img.shields.io/github/release/declandewet/contentful-aggregator.svg?style=flat-square)](https://github.com/declandewet/contentful-aggregator/releases)[![npm](https://img.shields.io/npm/v/contentful-aggregator.svg?style=flat-square)](http://npmjs.org/package/contentful-aggregator)[![npm](https://img.shields.io/npm/dt/contentful-aggregator.svg?style=flat-square)](http://npmjs.org/package/contentful-aggregator)[![node](https://img.shields.io/node/v/contentful-aggregator.svg?style=flat-square)]()\n\n[![Build Status](https://img.shields.io/travis/declandewet/contentful-aggregator.svg?style=flat-square)](https://travis-ci.org/declandewet/contentful-aggregator)[![codecov.io](https://img.shields.io/codecov/c/github/declandewet/contentful-aggregator.svg?style=flat-square)](https://codecov.io/github/declandewet/contentful-aggregator?branch=master)[![bitHound Overall Score](https://www.bithound.io/github/declandewet/contentful-aggregator/badges/score.svg)](https://www.bithound.io/github/declandewet/contentful-aggregator)[![bitHound Code](https://www.bithound.io/github/declandewet/contentful-aggregator/badges/code.svg)](https://www.bithound.io/github/declandewet/contentful-aggregator)\n\n[![Dependency Status](https://img.shields.io/david/declandewet/contentful-aggregator.svg?style=flat-square)](https://david-dm.org/declandewet/contentful-aggregator)[![devDependency Status](https://img.shields.io/david/dev/declandewet/contentful-aggregator.svg?style=flat-square)](https://david-dm.org/declandewet/contentful-aggregator#info=devDependencies)[![bitHound Dependencies](https://www.bithound.io/github/declandewet/contentful-aggregator/badges/dependencies.svg)](https://www.bithound.io/github/declandewet/contentful-aggregator/master/dependencies/npm)[![bitHound Dev Dependencies](https://www.bithound.io/github/declandewet/contentful-aggregator/badges/devDependencies.svg)](https://www.bithound.io/github/declandewet/contentful-aggregator/master/dependencies/npm)\n\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release)[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)](http://commitizen.github.io/cz-cli/)[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/)[![greenkeeper](https://img.shields.io/badge/greenkeeper-enabled-brightgreen.svg?style=flat-square)](http://greenkeeper.io/)\n\n\u003e ​:point_up: Please click on any of these to see more information/context\n\n\n\n# :book: Table of Contents\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [:arrow_double_down: Installation](#arrow_double_down-installation)\n    - [Requirements](#requirements)\n    - [Instructions](#instructions)\n- [:books: Usage](#books-usage)\n  - [API](#api)\n    - [Util](#util)\n      - [util.pluralize(\u003cstr\u003e)](#utilpluralizestr)\n      - [util.underscored(\u003cstr\u003e)](#utilunderscoredstr)\n      - [util.slugify(\u003cstr\u003e)](#utilslugifystr)\n    - [Space()](#space)\n      - [Space::getClient()](#spacegetclient)\n      - [Space::fetchInfo()](#spacefetchinfo)\n      - [Space::fetchLocales()](#spacefetchlocales)\n      - [Space::fetchLocaleCodes()](#spacefetchlocalecodes)\n      - [Space::fetchContentType(\u003cid\u003e)](#spacefetchcontenttypeid)\n    - [ContentType()](#contenttype)\n      - [ContentType::fetchEntries(\u003copts\u003e)](#contenttypefetchentriesopts)\n      - [ContentType::path(\u003cpathValue\u003e)](#contenttypepathpathvalue)\n    - [Entry(\u003copts\u003e)](#entryopts)\n- [:mortar_board: Required Knowledge](#mortar_board-required-knowledge)\n    - [A Short Primer on Futures](#a-short-primer-on-futures)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n\n\n# :arrow_double_down: Installation\n\n### Requirements\n\n- [Node.js](https://nodejs.org/en/download/) v4.0.0 or higher\n- NPM (v3.0.0+ highly recommended) (this comes with Node.js)\n\n\n\n### Instructions\n\n`contentful-aggregator` is a [Node](https://nodejs.org) module. So, as long as you have [Node.js and NPM](https://nodejs.org/en/download/), installing `contentful-aggregator` is as simple as running this in a terminal at the root of your project:\n\n``` sh\n$ npm install contentful-aggregator --save\n```\n\n\n\n# :books: Usage\n\n`contentful-aggregator` uses a Future-based syntax. [If you are not familiar with Futures, please read this short primer.](#a-short-primer-on-futures)\n\n\n\n`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.\n\n``` javascript\nimport ca from 'contentful-aggregator'\n\nconst space = ca('apiToken', 'space')\n```\n\n\n\nSince 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.\n\n``` javascript\nconst client = ca('apiToken')\nconst myPortfolioSpace = client('portfolio site space id')\nconst myBlogSpace = client('blog site space id')\n```\n\n\n\nThis will return an instance of the `Space` class.\n\n\n\n## API\n\n### Util\n\n``` javascript\nimport util from 'contentful-aggregator/util'\n```\n\n\n\n#### util.pluralize(\u003cstr\u003e)\n\n###### pluralize :: String -\u003e String\n\nReturns the plural form of the String `str`\n\n``` javascript\nutil.pluralize('octopus') // -\u003e 'octopi'\nutil.pluralize('book') // -\u003e 'books'\n```\n\n\n\n#### util.underscored(\u003cstr\u003e)\n\n###### underscored :: String -\u003e String\n\nReturns the underscored form of the String `str`\n\n``` javascript\nutil.underscored('foo bar') // -\u003e 'foo_bar'\nutil.underscored('fooBar') // -\u003e 'foo_bar'\n```\n\n\n\n#### util.slugify(\u003cstr\u003e)\n\n###### slugify :: String -\u003e String\n\nReturns the slugified form of the String `str`\n\n``` javascript\nutil.slugify('foo bar') // -\u003e 'foo-bar'\nutil.slugify('foo_bar') // -\u003e 'foo-bar\n```\n\n\n\n---\n\n### Space()\n\nAn instance of this class is returned once the `contentful-aggregator` module is fully setup.\n\n\n\n#### Space::getClient()\n\n###### getClient :: -\u003e Object\n\nReturns a direct reference to the [Contentful.js](https://github.com/contentful/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:)\n\n``` javascript\nconst client = space.getClient()\n\nclient.entries(...)... \nclient.contentType(...)... // etc\n```\n\n\n\n#### Space::fetchInfo()\n\n###### fetchInfo :: -\u003e Future Object\n\nAsynchronously fetches information about the current space from Contentful's content delivery API.\n\n``` javascript\nspace.fetchInfo().fork(null, (info) =\u003e {\n  return info === {\n    sys: {\n      type: 'Space',\n        id: 'cfexampleapi'\n    },\n    name: 'Contentful Example API',\n    locales: [\n      { code: 'en-US', name: 'English' },\n      { code: 'tlh', name: 'Klingon' }\n   \t]\n  }\n})\n```\n\n\n\n#### Space::fetchLocales()\n\n###### fetchLocales :: -\u003e Future [Object]\n\nAsynchronously fetches an array of the locales you have configured for your space on Contentful.\n\n``` javascript\nspace.fetchLocales().fork(null, (locales) =\u003e {\n  return locales === [\n  \t{ code: 'en-US', name: 'English' },\n    { code: 'tlh', name: 'Klingon' }\n  ]\n})\n```\n\n\n\n#### Space::fetchLocaleCodes()\n\n###### fetchLocaleCodes :: -\u003e Future [String]\n\nAsynchronously fetches an array of the locale codes for every locale you have configured for your space on Contentful.\n\n``` javascript\nspace.fetchLocaleCodes().fork(null, (localeCodes) =\u003e {\n  return localeCodes === ['en-US', 'tlh']\n}\n```\n\n\n\n#### Space::fetchContentType(\u003cid\u003e)\n\n###### fetchContentType :: String -\u003e [ContentType]\n\nAsynchronously fetches information about a single content type with an `id` property that matches `id` from Contentful. Resolves to an instance of [ContentType](#ContentType)\n\n``` javascript\nspace.fetchContentType('content type id').map((contentType) =\u003e {\n  // contentType === instanceof ContentType\n  contentType.setName('blog_posts')\n})\n```\n\n\n\n---\n\n### ContentType()\n\n\n\n#### ContentType::fetchEntries(\u003copts\u003e)\n\n###### fetchEntries :: Object -\u003e Future [Entry]\n\nAsynchronously fetch all entries that match [search parameter opts]() and belong to this ContentType. Resolves to an array of [Entry]() instances\n\n``` javascript\nblogPosts.fetchEntries({\n  include: 3,\n  'fields.title[exists]': true\n}).fork(posts =\u003e {\n  console.log(posts[0].path())\n})\n```\n\n\n\n#### ContentType::path(\u003cpathValue\u003e)\n\n###### setEntryPath :: (Object -\u003e any) -\u003e [Entry] -\u003e [Entry] \n\nConvenience 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.\n\n``` javascript\nblogPosts.fetchEntries()\n  .map(blogPosts.path(post =\u003e `posts/${post.name}.html`))\n```\n\n\n\n\n\n---\n\n### Entry(\u003copts\u003e)\n\n[WIP]\n\n\n\n---\n\n# :mortar_board: Required Knowledge\n\n\n\n### A Short Primer on Futures\n\nA 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.\n\nFutures, 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:\n\n``` javascript\n// fetch :: String -\u003e Future String\nconst fetch = (url) =\u003e new Future((resolve, reject) =\u003e {\n  const request = new XMLHttpRequest()\n  request.addEventListener('load', resolve, false)\n  request.addEventListener('error', reject, false)\n  request.addEventListener('abort', reject, false)\n  request.open('get', url, true)\n  request.send()\n})\n```\n\n\n\nLike 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()`.\n\n`.chain()` creates a dependent Future. Here, we create a Future `fetchJSON()` that depends on the result of sequencing `fetch()` (from above) and then `parseJSON()`:\n\n``` javascript\n// parseJSON :: String -\u003e Future Object\nconst parseJSON = (str) =\u003e new Future((resolve, reject) =\u003e {\n  try {\n    resolve(JSON.parse(str))\n  } catch (error) {\n    reject(error)\n  }\n})\n\n// fetchJSON :: String -\u003e Future Object\nconst fetchJSON = fetch.chain(parseJSON)\n```\n\n\n\nWe 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:\n\n``` javascript\n// fetchItems :: Future Object -\u003e Future [Object]\nconst fetchItems = fetchJSON.map(json =\u003e json.items)\n```\n\n\n\nIf we wanted to filter out only the items that are new, then we can do that too:\n\n``` javascript\n// getNewItems :: Future [Object] -\u003e Future [Object]\nconst getNewItems = fetchItems.map(items =\u003e items.filter(item =\u003e item.new))\n```\n\n\n\nAn **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:\n\n``` javascript\ngetNewItems('/products.json').fork(console.error, console.log) // -\u003e onRejected, onResolved\n```\n\n\n\nOne 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:\n\n``` javascript\n// fetchUrlFromUrl :: Future Object -\u003e Future String\nconst fetchUrlFromUrl = fetchJSON.map(json =\u003e json.url).chain(fetch)\n```\n\n\n\nThe 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:\n\n``` javascript\n// getCachedNewItems :: Future [Object] -\u003e Future [Object]\nconst getCachedNewItems = Future.cache(getNewItems)\n\n// makes an HTTP request\ngetCachedNewItems('/products.json').fork(console.error, console.log)\n\n// doesn't make an HTTP request, just returns from cache:\ngetCachedNewItems('/products.json').fork(console.error, console.log)\n```\n\n\n\nFutures in `contentful-aggregator` are [based on the Ramda-Fantasy library](https://github.com/ramda/ramda-fantasy/blob/master/docs/Future.md) which include other methods like `ap`, `of`, `chainReject`, `biMap`, and `reject`.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzspecza%2Fcontentful-aggregator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzspecza%2Fcontentful-aggregator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzspecza%2Fcontentful-aggregator/lists"}