Common target for all library website search forms. Configurable, extensible, re-usable.
Many library websites have multiple search forms that redirect users to various search engines. Janus is a common target application for all those searches, allowing for data collection and other handling of the searches with a single code base.
Janus uses a simple URI API for all search engines, which also makes search forms easier to write and maintain. For example, at UMN Libraries, Janus transforms this request...
https://stacks.lib.umn.edu/janus?target=primo&search=darwin
...into this Primo request:
One way to quickly try Janus:
-
Install Node.js. Choose an appropriate version based on the
engines
value in this project'spackage.json
. -
git clone
this repo. -
npm install
-
In the repo directory, use Node.js to run this code:
'use strict';
const janus = require('./');
const pubmedPlugin = require('./test/fixtures/pubmed-plugin');
const app = janus({
uriFactoryPlugins: {pubmed: pubmedPlugin},
});
app.listen(3000);
- Make an HTTP GET request (e.g. in a web browser) for: http://localhost:3000?target=pubmed&search=neoplasm
Janus should write a log message to STDOUT, and redirect the request to PubMed, which should respond with search results for "neoplasm".
To configure and extend Janus for use in your library, see the API section below. To see how UMN Libraries configures, extends, and deployes Janus, see our janus-deploy repository.
Janus accepts only GET requests, and recognizes these parameters:
target
search
scope
field
format
All parameter names are case-insensitive. Any unrecognized parameters will be ignored.
target
is the only required parameter, and the value, while case-insensitive, must map to a recognized URI factory plugin. If the target
value is missing or invalid, Janus cannot redirect, and will return a 400 response.
search
is the user's search expression. While not required, there is little value in redirecting a user to a search engine without it. If search
is missing, Janus will redirect the user to the target
such that no search will have been attempted yet, and log a warning.
scope
restricts the search
to a subset of items available via the target
. Each plugin may define valid scope
values. If a scope
value is invalid, Janus will still redirect, ignoring the scope
, and log a warning.
field
restricts the search
to one of these common bibliographic metadata items:
author
title
subject
All field values are case-insensitive. Any other field
value will cause Janus to ignore it and log a warning.
format
restricts the search to specific material types or formats available via the target
. If a format
is value invalid, Janus will ignore it and log a warning.
Janus is a stampit factory function that creates a Koa v2.x application. An understanding of those technologies may help, but should not be necessary, to understand the following API sections, i.e. instructions for using Janus. More advanced use of Janus will likely require understanding those technologies.
Most Janus configuration involves passing a properties object to the factory function:
const app = janus({
uriFactoryPlugins: {
target1: target1Plugin,
target2: target2Plugin,
},
sessionOpts: {
// options for koa-session
},
redirectLog: {
// options for bunyan.createLogger()
},
errorLog: {
// options for bunyan.createLogger()
},
uriPathPrefix: '/', // Default. Or set it to something custom, like '/janus'.
favicon: '/path/to/favicon.ico',
});
More about some of these properties in the sections below.
Janus generates redirect URIs with a factory. The most obvious and powerful way to extend Janus is to create plugins for this factory, one for each search engine you want to target. The plugins transform Janus request parameters into URIs for search engine targets.
The value of the uriFactoryPlugins
property must be an object, where the keys are values for the target
parameter
of the URI API, and the values are plugins for those targets.
For simple examples, see the test/fixtures/*plugin.js
files in this repo. For more complex examples, see the
UMN Libraries plugins.
In most, if not all, cases, reading and modifying existing examples should be sufficient to create your own plugins. A basic understanding of stampit and urijs would help, too.
Janus provides two re-usable plugin components, both in the uri-factory/
directory:
plugin.js
: A stampit factory function that provides base functionality, extensible by composing with other (e.g. your own) plugins.plugin-tester.js
: Functions for testing plugins. For examples, seetest/*plugin.js
, and the previously-mentioned example plugins.
See uri-factory/plugin.js
for base and example implementations of these methods and properties.
Returns an array where the first element is a string, which should contain any warnings associated with missing search expressions or invalid scopes or fields,
and the second element is an ojbect with an href()
method, which must return a string representation of the generated redirect URI. uriFor()
must never throw.
The UMN Libraries plugins use urijs objects for the second element of the return array, but any object with an href method with the appropriate signature is OK.
This is the only method that the Janus URI factory will call on a plugin, so it is the only required method a plugin must implement.
Returns an object that other plugin methods can modify to generate redirect URIs. Optional but useful.
Because uriFor() must never throw, it can be helpful to define in one place what URI to use when the user supplies no search expression. Often this URI will be the same as the base URI, so the provided implementation just returns baseUri(). Optional.
Maps Janus field parameters to their analagous parameters in the target search engine. Optional. Because many search engines use the same parameter names, the provided implementation returns:
{
author: 'author',
title: 'title',
subject: 'subject',
}
Returns an array or object that defines valid scopes for the target search engine. Because many search engines do not support scopes, the provided implementation returns an empty object. Optional.
Returns an array or object the defines valid formats or material types for the target search engine. Because many search engines do not support formats, the provided implementation returns an empty object. Optional.
This property provides a string warning for a missing search expression. The provided default is 'Missing or empty search expression.' Optional.
This property provides a warning for an invalid scope. The provided default is 'Unrecognized scope: '. Optional.
This property provides a warning for an invalid field. The provided default is 'Unrecognized field: '. Optional.
This property provides a warning for an invalid format. The provided default is 'Unrecognized format: '. Optional.
Janus uses koa-session to assign each unique visitor a session ID, which it logs by default (see Logging below). The session IDs are RFC4122 version 1 UUIDs. To customize session ID creation, override the sessionId()
method. It accepts one parameter, ctx
, a Koa context object. It returns a promise, in case you want to generate IDs via some async process.
One way to override this method when invoking Janus:
const plugins = require('your-plugins');
const janus = require('@nihiliad/janus').methods({
sessionId (ctx) {
return new Promise(function (resolve, reject) {
resolve(
// generate your session id
);
});
},
});
const app = janus({
uriFactoryPlugins: plugins,
});
app.listen(3000);
As described in Application Factory above, you can also define custom options for koa-session. See index.js
for the default options.
Janus uses Bunyan for logging. If provided, Janus will pass the values of the redirectLog
and errorLog
properties to the bunyan.createLogger() constructor. If not provided, these values default to {name: 'redirect'}
and {name: 'error'}
, which will cause Bunyan to log to stdout
and stderr
, respectively. For more control, override the Janus redirectLogger()
and/or errorLogger()
methods, which create the Bunyan loggers.
One of the most valuable features of Janus is logging metadata about each request. To control what metadata Janus logs, override the redirectLogEvent()
method. It accepts two parameters, ctx
, a Koa context object, and a defaultEvent
, which you can modify to more easily customize what gets logged. It returns an object that Bunyan will include in a redirect log message for the request. See defaultRedirectLogEvent()
in index.js
for the structure of the defaultEvent
.
One way to override this method when invoking Janus:
const plugins = require('your-plugins');
const janus = require('@nihiliad/janus').methods({
redirectLogEvent (ctx, defaultEvent) {
return {
// your custom object
};
},
});
const app = janus({
uriFactoryPlugins: plugins,
});
app.listen(3000);
Install with npm. In package.json, include something like...
"dependencies": {
"@nihiliad/janus": "^2.0.0"
}
...where version based on the version
value in this project's package.json
. Then npm install
.
Sometimes npm install
has not installed dependencies of packages in devDependencies
, e.g., eslint
, in package.json
. If this happens, running npm install --save-dev
should fix it.
To run the linter and all unit tests:
npm test
We use standardjs for linting. To lint all files:
npx standard
To automatically fix any errors that can be fixed automatically:
npx standard --fix
To lint a single file, e.g., index.js
:
npx standard index.js
Some unit tests make HTTP requests to real web services. To run those tests, set the RUN_INTEGRATION_TESTS
environment variable to a true value. The default value is false. This works for npm test
and all commands described below.
We use tape for unit tests. To run all unit tests:
npx tape test/*.js
To run a single unit test file, e.g., test/factory.js
:
npx tape test/factory.js