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 :)
Click to expand
- OBA-wrapper
- Glossary
- User feedback
- Getting started
- Iteration plan / planned features
- Tips for understanding the docs
- Technologies
- Simple Promise (Native promises)
- Promise streaming (Concurrency)
- How to use
PromiseStream.prepend (any[]: ...values) -> PromiseStream
PromiseStream.append (any[]: ...values) -> PromiseStream
PromiseStream.insert (number: index?, any[]: ...values) -> PromiseStream
PromiseStream.pipe (function: through) -> PromiseStream
PromiseStream.pipeOrdered(function: through) -> PromiseStream
PromiseStream.all () -> Promise<Any[]>
PromiseStream.catch (function: handler) -> PromiseStream
- How to use
- Asynchronous iterator (Consecutiveness)
- "Smart" Requests
- License
Impressive! Also a bit overengineered.
- Rijk van Zanten, 2019
Your feedback here?
- Name
Install the module with a package manager:
npm i github:maanlamp/OBA-wrapper
Or just download the repo as a ZIP.
To use the es6 modules version, link to it in your html using a script tag:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
<script src="./js/index.js" type="module"></script>
<!-- ... -->
</head>
<!-- ... -->
Or import it in another module:
import { API } from "OBA-wrapper/js/index.js";
-
Note that it is not needed to import it at the bottom of a
<body>
tag, since a module will always be loaded after the document. -
type="module"
is VERY important. -
Also note that if you use a package manager, the url will probably be different. For example: for npm the url would be
node_modules/OBA-wrapper/js/index.js
.
const API = require("OBA-wrapper/node");
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!
Symbol | Description |
---|---|
🏃 | Will be in next release |
💪 | Expected in next release |
⚫️ | Under discussion |
- Make server-side usage possible.
- Separate
api._ping()
into own module - Allow other formats than text in smartRequest
- 💪 If HTTP 429, respect
Retry-After
response header (instead of exponential backoff). - 🏃 Give users control over what to cache in smartRequest
- 🏃 Allow offset requests (either set start page or define offset as items/pagesize)
- ⚫️ Make a
[Symbol().asyncIterator]
for stream - ⚫️ Builtin filter
- ⚫️ "Revivable" smart requests.
- ⚫️ Expand getFetchSafeOptions in smartRequest
Click to expand
Methods are described as such:
Typing is not enforced, but for clarity. When a method has no (explicit) return value, it is omitted in the description:
Optional arguments are suffixed with a ?
:
When a method returns a Promise, the value of its fulfillment is denoted between angled brackets < >
:
Interfacing with the API can be done in several ways. This is to facilitate the coding style of everyone while using the wrapper.
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.
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);
});
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! 😍
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:
Inserts values at the beginning of the stream. values
do not have to be promises, the stream will internally convert all values to promises.
Inserts values at the end of the stream. values
do not have to be promises, the stream will internally convert all values to promises.
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
.
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);
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).
Shorthand for calling Promise.all(stream.promises)
.
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()
.
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:
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.
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 🔥!
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:
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.
Licensed under MIT - Copyright © 2019 maanlamp