npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@financial-times/tc-api-rest-handlers

v0.6.9

Published

Treecreeper&tm; functions for handling CRUD actions via a RESTful interface.

Downloads

192

Readme

@financial-times/tc-api-rest-handlers

Treecreeper library for handling CRUD actions via a RESTful interface.

API

Handlers

The library exports the following functions: headHandler, getHandler, deleteHandler, postHandler, patchHandler, absorbHandler,

The functions do not implement any handling of requests and responses, but work on a more event driven model - receiving and returning objects - for ease of reuse in various environments. Therefore the RESTful url scheme is implied rather than implemented. This design is both to improve testability and to make it possible, in future, to deploy treecreeper to event driven architectures such as AWS Lambda.

All of these functions are factories that return the actual handlers. Each of the factories accepts an options object with a single property, documentStore, which should be an instance of tc-api-s3-document-store or similar

URL scheme

Although not implemented, the handlers are intended to be used to handle RESTful urls similar to the following:

  • /:type/:code - all handlers apart from absorb
  • /:type/:code/absorb/:absorbedCode - absorb

Input

With the exception of absorbHandler they all return handlers which accept the following input:

{
    type, // the type of record to interact with
    code, // the unique code of the record to interact with. This is the record's id, but for historical reasons it is called `code`
    body, // [optional] the data to write to the record
    query, // [optional] options for writing to the record
    metadata // contextual data used for logging, and written to the data for audit purposes
}

absorbHandler's input is the same, but accepts an additional property absorbedCode. body is only used by patch and post.

The input can be generated from a http request (or equivalent object) by setting the url parameters as top level properties, and setting the request body and query string as nested objects within the input. Metadata is an object containing contextual information about the request.

Output

Each handler returns objects of the following structure

{
    status, // http status code describing the type of response
    body // data to be returned to the user
}

headHandler does not return body

Error output

With the exception of internal server errors, all errors thrown are instances of HttpErrors and have a status and message property. status is a http status.

Body structure

Both input and output body has structure similar to that below. The treecreeper schema powering the application defines exactly which properties are allowed

{
	"someString": "A property stored on the record", // property
	"children": ["child1", "child2"], // relationship to multiple records
	"favouriteChild": "child1" // relationship to a single record
}
  • Any properties of a record are represented by strings, booleans and numbers assigned to properties of the body
  • Relationships to other records are represented as arrays of strings, or single strings if the relatonship is {many|one}-to-one. Each string is the code of the related record. As the schema defines which types a given relationship can point to, no information about the related type is contained in the body.
  • Output body will always contain a code property. This is optional in input body because code will always be given by the top level property of the input object which is usually derived from the RESTful url.
  • codes are immutable. When writing, if the code in the body does not match the one in the RESTful url, an error will be thrown
  • Input body can be used to delete individual properties or relationships
    • Use null as the value to remove the property or to delete all relationships of the given type
    • Use a ! before the property name of a relationship to target individual relationships for deletion e.g. "!children": ["child1"]

Metadata

The metadata object is typically constructed from information sent to the application in headers. Three properties are recognised:

  • clientId - the id of a particular application calling the handler, lowercase and may be hyphenated
  • clientUserId - the id of a user calling the api, lowercase, . delimited
  • requestId - unique id for the current request, typically a uuid.

requestId is required and must be unique per handler call. To use Treecreeper's field-locking capability a clientId must be sent.

Query options

| name | relevant handlers | value | meaning | | ------------------ | ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | upsert | post, patch | false or missing | If the body has any relationships that reference any records that do not yet exist, a 400 error will be thrown | | | | true | If the body has any relationships that reference any records that do not yet exist, records will be created for them. These records will only contain a code property | | relationshipAction | patch | missing | If the body defines any relationships, a 400 error will be thrown | | | | merge | Existing relationships will be kept, and ones defined in the body will also be created | | | | replace | Existing relationships will be replaced by those specified in the body | | richRelationship | get | true | If the body has any relationships, it provides more information about the relationships than just their code | | force | delete | true | Allows records to be deleted even if attached to other records | | efficientWrite | patch | true | Increases the efficiency of writes to large records but will only respond with a subgraph for the record. |

Field locking

When writing using the post or patch handlers, it is possible, by means of the lockFields query option, to prevent fields from being modified by other clients. The clientId specified in metadata is used to specify which client the field is locked by. The unlockFields option can be used to unlock any field, regardless of which client locked it.

| name | relevant handlers | value | meaning | | ------------ | ----------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | lockFields | post, patch | propertyName1,propertyName2,... | Will lock all properties listed with the current clientId, regardless of whether a value is sent for them in the request body | | | | all | Will lock all properties sent in the body with the current clientId | | unlockFields | patch | propertyName1,propertyName2,... | Will unlock all properties listed | | | | all | Will remove all property locking settings from the record |

absorbHandler

This is used to merge one record, B, into another, A, using the following merge logic.

  • If a property is not defined on A but is defined on B, then the property will be copied from B to A
  • Any relationships between B and a third record, C, will become relationships between A and C
  • Any relationships between A and B will be removed
  • Finally A will still exist, but B will not

Events

The library also exports an event emitter, emitter, which fires events when any changes to the underlying data are made

Each event has the following structure:

{
	action, // see availableEvents, below, for the full list
		code, // code of the record updated
		type, // type of the updated record
		updatedProperties, // list of properties - including relationship properties - updated
		timestamp; // timestamp of the event
}

The event fired will also have the same name as action e.g.

emitter.on('UPDATE', ({ action }) => {
	console.log(action); /// 'UPDATE'
});

A list of all the event types available, currently UPDATE, DELETE, CREATE, is exported as availableEvents

Example

The following creates a connected record and then deletes it, encountering some user errors along the way

const {
	postHandler,
	deleteHandler,
	patchHandler,
} = require('@financial-times/tc-api-rest-handlers');

const post = postHandler();
const patch = patchHandler();
const delete = deleteHandler();

// metadata objects in input omited for brevity
post({
	type: 'DogBreed',
	code: 'spaniel',
	body: {
		name: 'Spaniel',
		likes: ['walkies', 'tummy-rub'],
	},
}); // upsert error

post({
	type: 'DogBreed',
	code: 'spaniel',
	body: {
		name: 'Spaniel',
		likes: ['walkies', 'tummy-rub'],
	},
	query: {
		upsert: true,
	},
}); // 200, successfully created

delete {
	type: 'DogBreed',
	code: 'spaniel',
}; // error, connected record

patch({
	type: 'DogBreed',
	code: 'spaniel',
	body: {
		likes: null,
	},
}); // 200, successfully removed relatioships

delete {
	type: 'DogBreed',
	code: 'spaniel',
}; // 204, successfully deleted