maanlamp / OBA-wrapper

A wrapper for the OBA-API to intuitively request data.
MIT License
15 stars 2 forks source link

The OBA-wrapper logo

OBA-wrapper

The OBA (Openbare Bibliotheek Amsterdam) has a public API that is usable by everyone to create very cool stuff; here is a list of such cool stuff.

Sadly, the API is a bit clunky, so I set out to make it easy to work with!

Built and maintained by @maanlamp.

Don't forget to ⭐ the repo if you like it :)




Glossary

Click to expand - [OBA-wrapper](#oba-wrapper) - [Glossary](#glossary) - [User feedback](#user-feedback) - [Getting started](#getting-started) - [ES6 Module](#es6-module) - [Node](#node) - [Sandbox](#sandbox) - [Iteration plan / planned features](#iteration-plan--planned-features) - [Tips for understanding the docs](#tips-for-understanding-the-docs) - [Technologies](#technologies) - [Simple Promise (Native promises)](#simple-promise-native-promises) - [How to use](#how-to-use) - [Promise streaming (Concurrency)](#promise-streaming-concurrency) - [How to use](#how-to-use-1) - [PromiseStream.prepend (*any[]:* ...values) -> PromiseStream](#codepromisestreamprepend-any-values---promisestreamcode) - [PromiseStream.append (*any[]:* ...values) -> PromiseStream](#codepromisestreamappend-any-values---promisestreamcode) - [PromiseStream.insert (*number*: index?, *any[]:* ...values) -> PromiseStream](#codepromisestreaminsert-number-index-any-values---promisestreamcode) - [PromiseStream.pipe (*function:* through) -> PromiseStream](#codepromisestreampipe-function-through---promisestreamcode) - [PromiseStream.pipeOrdered(*function:* through) -> PromiseStream](#codepromisestreampipeorderedfunction-through---promisestreamcode) - [PromiseStream.all () -> Promise](#codepromisestreamall----promiseanycode) - [PromiseStream.catch (*function:* handler) -> PromiseStream](#codepromisestreamcatch-function-handler---promisestreamcode) - [Asynchronous iterator (Consecutiveness)](#asynchronous-iterator-consecutiveness) - [How to use](#how-to-use-2) - [for await ... of ...](#codefor-await--of-code) - ["Smart" Requests](#%22smart%22-requests) - [How to use](#how-to-use-3) - [smartRequest(_url_: url, _object_: options?) -> Promise\](#codesmartrequesturl-url-object-options---promiseresponsecode) - [License](#license)






User feedback

Impressive! Also a bit overengineered.
- Rijk van Zanten, 2019

Your feedback here?
- Name






Getting started

Install the module with a package manager:

npm i github:maanlamp/OBA-wrapper

Or just download the repo as a ZIP.

ES6 Module

To use the es6 modules version, link to it in your html using a script tag:

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- ... -->
  <script src="https://github.com/maanlamp/OBA-wrapper/raw/master/js/index.js" type="module"></script>
  <!-- ... -->
</head>
<!-- ... -->

Or import it in another module:

import { API } from "OBA-wrapper/js/index.js";

Node

const API = require("OBA-wrapper/node");

Sandbox

The quickest way to start a working request is as follows:

(async () => {
  localStorage.clear();

  const api = new API({
      key: "ADD YOUR KEY HERE"
  });
  const stream = await api.createStream("search/banaan{5}");

  stream
    .pipe(console.log)
    .catch(console.error);
})();

You can also just have some fun inside the sandbox!





Iteration plan / planned features

Symbol Description
🏃 Will be in next release
💪 Expected in next release
⚫️ Under discussion





Tips for understanding the docs

Click to expand Methods are described as such: ##### methodName (_type_: argument) -> returnValue Typing is not enforced, but for clarity. When a method has no (explicit) return value, it is omitted in the description: ##### methodName (_type_: argument) Optional arguments are suffixed with a `?`: ##### methodName (_type_: optionalArgument?) When a method returns a *Promise*, the value of its fulfillment is denoted between angled brackets `< >`: ##### methodName () -> promise\






Technologies

Interfacing with the API can be done in several ways. This is to facilitate the coding style of everyone while using the wrapper.



Simple Promise (Native promises)

To use the API as some sort of fetch request, use the method createPromise, which will return a Promise that resolves to an array of responses.

How to use

To create a Promise through the wrapper, you simply call its method createPromise, which will return a promise that that resolves to an array of responses. This has no special methods. Refer to the Promise specification for more information.

An example:

//Imagine the functions toJson, cleanJSON and
//renderToDocument exist, and do what their
//name says.
const requests = await api.createPromise("endpoint/query");
requests
  .then(responses => {
    const mapped = responses.map(toJSON);
    return Promise.all(mapped);
  }).then(jsons => {
    const cleaned = responses.map(cleanJSON);
    return Promise.all(cleaned);
  }).then(cleanJsons => {
    cleanJsons.forEach(renderToDocument);
  });



Promise streaming (Concurrency)

A PromiseStream is a class that allows the "piping" of promises through functions. It is not like a Node Stream, since those require you to pipe into another stream. For those who understand streams, they are almost the same, just with functions. For those who do not know streams, let me introduce to you the wonderful world of streams! 😍

How to use

To create a PromiseStream through the wrapper, you simply call its method createStream, which will return a promise that resolves into a new PromiseStream. The stream has several methods:

PromiseStream.prepend (any[]: ...values) -> PromiseStream

Inserts values at the beginning of the stream. values do not have to be promises, the stream will internally convert all values to promises.

PromiseStream.append (any[]: ...values) -> PromiseStream

Inserts values at the end of the stream. values do not have to be promises, the stream will internally convert all values to promises.

PromiseStream.insert (number: index?, any[]: ...values) -> PromiseStream

Inserts values into the stream at index. values do not have to be promises, the stream will internally convert all values to promises. If index is not provided, it will be treated as values.

PromiseStream.pipe (function: through) -> PromiseStream

⚠️ Does not pipe in order! Use PromiseStream.pipeOrdered instead.

Runs a function through for every resolved promise in the stream. Accepts both synchronous and asynchronous functions. Returns a new stream filled with promises that resolve to the value of through, so you can chain them (and use previous values).

An example:

//Imagine the functions toJson, cleanJSON and
//renderToDocument exist, and do what their
//name says.
const stream = await api.createStream("endpoint/query");
stream
  .pipe(toJSON)
  .pipe(cleanJSON)
  .pipe(renderToDocument);
PromiseStream.pipeOrdered(function: through) -> PromiseStream

Runs a function through for every resolved promise in the stream, waiting for each previous resolvement. Accepts both synchronous and asynchronous functions. Returns a new stream filled with promises that resolve to the value of through, so you can chain them (and use previous values).

PromiseStream.all () -> Promise<Any[]>

Shorthand for calling Promise.all(stream.promises).

PromiseStream.catch (function: handler) -> PromiseStream

Adds a .catch() to every promise to allow for individual error handling. If you just want to handle all errors at once, use .all().catch().



Asynchronous iterator (Consecutiveness)

An iterator is a protocol used in JavaScript to iterate over enumerable objects. If that makes no sense to you, I mean things like arrays. You can loop (iterate) over those.

However, arrays have synchronous iterators. That means they do not await the values inside, so you cannot use them for promises.

But don't fret! I've made a custom asynchronous iterator for you! Simply call the API's method createIterator, which will return a promise that resolves into an asynchrounous array iterator. How to use it? Let me show you:

How to use

for await ... of ...

Because the iterator is asynchronous, you can use it within a for await of loop. If you have no idea what that means, take a look:

//Imagine the functions toJson, cleanJSON and
//renderToDocument exist, and do what their
//name says.
const iterator = await api.createIterator("endpoint/query");
for await (const response of iterator) {
  const json = toJSON(response);
    const cleanedJSON = cleanJSON(json);
    renderToDocument(cleanedJSON);
}

This will do the same as this PromiseStream example.



"Smart" Requests

A smart request is a request that retries 4 times (implementing exponential backoff), but only if the reason of failure is not a fatal one (i.e. "userRateLimitExceeded", etc...).

This means that there will be a greater chance of recovering from (accidental) rate limit exceedances or internal server errors.

Besides that, it will use localStorage to cache responses by url, so later smart requests can check if their provided url was already cached. Blazingly fast 🔥!

How to use

You should not have to use a SmartRequest directly, since this wrapper uses them under the hood. You could use them standalone for other purposes though. You can make use of the following methods:

smartRequest(url: url, object: options?) -> Promise\<response>

Sends out a fetch request that retries options.maxTries (defaults to 5) times if possible. If a fatal error occured, or the maximum amount of tries was exceeded, the promise rejects with an error. If all went well, it will cache the result from url in localStorage with the key of url, and resolve with a response.





License

Licensed under MIT - Copyright © 2019 maanlamp