Skip to content
This repository has been archived by the owner on Mar 15, 2023. It is now read-only.

Commit

Permalink
updating and standardizing README
Browse files Browse the repository at this point in the history
  • Loading branch information
dillonredding committed Sep 10, 2022
1 parent d02ef04 commit b2bcfe7
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 201 deletions.
288 changes: 88 additions & 200 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,76 @@

[![Node Package](https://img.shields.io/npm/v/@siren-js/core)](https://npmjs.org/@siren-js/core)
[![Build Status](https://img.shields.io/github/workflow/status/siren-js/core/Build%20Package)](https://github.com/siren-js/core/actions/workflows/build.yaml)
[![Code Coverage](https://img.shields.io/codecov/c/github/siren-js/core)](https://codecov.io/gh/siren-js/core)
[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg)](https://github.com/RichardLitt/standard-readme)
[![License](https://img.shields.io/github/license/siren-js/core)](LICENSE)
[![Contributing](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](CONTRIBUTING.md)

A cross-platform library that provides classes for creating objects representing
components (entities, actions, etc.) of the [Siren] hypermedia format.
The primary intent of this library is for generating or parsing Siren
representations.
Cross-platform library of classes for generating and parsing [Siren](https://github.com/kevinswiber/siren) entities.

[siren]: https://github.com/kevinswiber/siren
## Table of Contents

- [Installation](#installation)
- [Development Release](#development-release)
- [Table of Contents](#table-of-contents)
- [Install](#install)
- [Usage](#usage)
- [Component Lookup](#component-lookup)
- [Generating Siren](#generating-siren)
- [Creating an Entity](#creating-an-entity)
- [Generating an Entity](#generating-an-entity)
- [Parsing Siren](#parsing-siren)
- [Querying an Entity](#querying-an-entity)
- [Querying an Action](#querying-an-action)
- [Extensions](#extensions)
- [TypeScript](#typescript)
- [Contributing](#contributing)
- [License](#license)

## Installation
## Install

```text
npm install @siren-js/core
```

## Development Release

`@siren-js/core` is currently in the development phase (v0.x) while we work to
realize the best API for working with Siren in JavaScript. This means minor
version increments may not be backward compatible, but patch version increments
will.

In order to get to a production-ready release (v1+), we need users to try out
the library, find bugs, and give honest, constructive feedback on how we can
improve! See the [Contributing](#contributing) section below.

## Usage

The following example demonstrates one way of building the
[example entity][siren-example] from the Siren spec. It uses the `Entity` class
which takes an object representing a [Siren entity][siren-entity].
### Creating an Entity

[siren-example]: https://github.com/kevinswiber/siren#example
[siren-entity]: https://github.com/kevinswiber/siren#entity
Here's a simple example of generating an entity:

```js
import * as Siren from '@siren-js/core';

const order = getOrderFromDB(orderNumber);
const person = Siren.Entity.of({
class: ['Person'],
properties: {
givenName: 'Neville',
familyName: 'Longbottom',
birthDate: '1980-07-30'
},
links: [
{
rel: ['self'],
href: 'https://api.example.com/people/69'
}
]
});
```

Here is a more complete example building out [the order entity example from the Siren spec](https://github.com/kevinswiber/siren#example):

const entity = new Siren.Entity({
```js
const order = {
orderNumber: 42,
itemCount: 3,
status: 'pending',
customer: {
userId: 'pj123',
name: 'Peter Joseph'
}
};

const orderEntity = Siren.Entity.of({
class: ['order'],
properties: {
orderNumber: order.orderNumber,
itemCount: order.items.length,
status: order.orderStatus
itemCount: order.itemCount,
status: order.status
},
entities: [
{
Expand All @@ -72,7 +84,7 @@ const entity = new Siren.Entity({
rel: ['http://x.io/rels/customer'],
properties: {
customerId: order.customer.userId,
name: order.customer.fullName
name: order.customer.name
},
links: [
{
Expand Down Expand Up @@ -113,200 +125,76 @@ const entity = new Siren.Entity({
});
```

If you don't want to build your entity in one fell swoop (I wouldn't blame you),
you can use the other component classes: `Action`, `Field`, `Link`,
`EmbeddedEntity`, and `EmbeddedLink`. Each of these accept required
members as positional constructor arguments and optional members in a final
options object.

Here's how you might break up the above code:

```js
const orderUrl = `http://api.x.io/orders/${order.orderNumber}`;
const selfLink = new Siren.Link(['self'], orderUrl);

const itemsRel = 'http://x.io/rels/order-items';
const itemsUrl = `http://api.x.io/orders/${order.orderNumber}/items`;
const itemsLink = new Siren.EmbeddedLink([itemsRel], itemsUrl, {
class: ['items', 'collection']
});

const customerRel = 'http://x.io/rels/customer';
const customerEntity = new Siren.EmbeddedEntity([customerRel], {
class: ['info', 'customer'],
properties: {
customerId: order.customer.userId,
name: order.customer.fullName
},
links: [
{
rel: ['self'],
href: `http://api.x.io/customers/${order.customer.userId}`
}
]
});

const quantityField = new Siren.Field('quantity', {
type: 'number'
});

const addItemAction = new Siren.Action('add-item', itemsUrl, {
title: 'Add Item',
method: 'POST',
type: 'application/x-www-form-urlencoded',
fields: [
{ name: 'orderNumber', type: 'hidden', value: `${order.orderNumber}` },
{ name: 'productCode', type: 'text' },
quantityField
]
});
```
### Generating an Entity

Now constructing the full entity is a little easier.
Use the `stringify` function to convert an `Entity` into Siren JSON (`application/vnd.siren+json`).

```js
new Siren.Entity({
class: ['order'],
properties: {
orderNumber: order.orderNumber,
itemCount: order.items.length,
status: order.orderStatus
},
entities: [itemsLink, customerEntity],
actions: [addItemAction],
links: [
selfLink,
{
rel: ['previous'],
href: `http://api.x.io/orders/${order.orderNumber - 1}`
},
{
rel: ['next'],
href: `http://api.x.io/orders/${order.orderNumber + 1}`
}
]
});
//=> same as entity
const siren = Siren.stringify(person);
// => "{ "class": ["Person"], ... }"
```

### Component Lookup

The `Entity` and `Action` classes each provide a method for looking up their
actions and fields by `name`.

```js
entity.getActionByName('add-item');
//=> same as addItemAction

addItemAction.getFieldByName('quantity');
//=> same as quantityField
```
### Parsing Siren

The `Entity` class also has methods for looking up sub-entities and links by
`rel` and `class`, as well as actions by `class`.
Use the `parse` function to convert a Siren JSON string to an `Entity`.

```js
entity.getLinksByRel('self');
//=> same as [selfLink]

entity.getEntitiesByRel(itemsRel);
//=> same as [itemsLink]
entity.getEntitiesByClass('items');
//=> same as [itemsLink]

// you can pass multiple classes/rels (order doesn't matter)
entity.getEntitiesByClass('customer', 'info');
//=> same as [customerEntity]

// components' property must contain all values
entity.getEntitiesByClass('items', 'info');
//=> []
const entity = Siren.parse(siren);
// `entity` is equivalent to `person`
```

The `Action` class has a method for looking up fields by `class` that works
similarly.
### Querying an Entity

### Generating Siren

To generate a Siren representation, use `JSON.stringify()`.
The `Entity` class provides several convenience methods for finding actions or links within the entity:

```js
const siren = JSON.stringify(entity);
const addItemAction = orderEntity.findActionByName('add-item');
const customerSubEntities = orderEntity.findEntitiesByRel('customer');
const nextOrderLinks = orderEntity.findLinksByRel('next');
```

### Parsing Siren
### Querying an Action

To parse a string as a Siren representation, use `JSON.parse()` and pass the
result to the `Entity` constructor.
The `Action` class also provides convenience methods for finding fields:

```js
new Siren.Entity(JSON.parse(siren));
//=> same as entity
const productCodeField = addItemAction.findFieldByName('productCode');
```

### Extensions

The options objects of each component class allow you to extend the core Siren
spec. Need an [`hreflang`][rfc8288-3.4.1] property on your link? No problem!
Need [validation constraints][hc] on your fields? You got it!

[hc]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constraints
[rfc8288-3.4.1]: https://tools.ietf.org/html/rfc8288#section-3.4.1
Extensions are supported for every type of object. Here's an example using `min` and `max` constraints for a `Field` and [`hreflang`](https://tools.ietf.org/html/rfc8288#section-3.4.1) for a `Link`:

```js
const link = new Siren.Link(['profile'], 'http://api.example.com/profile', {
hreflang: 'en-US'
Siren.Entity.of({
actions: [
{
name: 'guess-number',
href: 'https://api.example.com/guess',
fields: [
{
name: 'guess',
type: 'number',
min: 0,
max: 100
}
]
}
]
links: [
{
rel: ['about'],
href: 'https://api.example.com/about',
hreflang: 'en-US'
}
]
});
link.hreflang;
//=> 'en-US'

const field = new Siren.Field('quantity', { min: 1, max: 10 });

const value = 15;
if (value < field.min || value > field.max) {
// this block will execute...
}
```

### TypeScript

Type declarations are included in the `@siren-js/core` package and require at
least version 3.8.2 of TypeScript. However, TypeScript users may experience
several limitations not present for JavaScript users.

For example, class properties that are nested components can be passed as plain
objects in the constructor, but not when modifying the property directly.

```ts
// this is OK
addItemAction.fields = [quantityField];

// this causes an error in TypeScript!!!
addItemAction.fields = [{ name: 'quantity' }];
```

Similarly, `Link`'s and `EmbeddedLink`'s `href` property can be given a `URL`,
which is coerced to a `string` using the `toString()` method. Again, this is
available when instantiating a class, but not when modifying the property.

```ts
const url = new URL(orderUrl);

// this is OK
const link = new Siren.Link(['self'], url);
link.href;
//=> same as orderUrl

// this causes an error in TypeScript!!!
link.href = url;
```

These limitations are caused by a requirement for getters and setters to have
the same type (see [TypeScript issue #2521][ts-2521]).
## Contributing

[ts-2521]: https://github.com/microsoft/TypeScript/issues/2521
PRs and bug reports welcome! Be sure to read our [contribution guidelines](CONTRIBUTING.md).

## Contributing
## License

If you would like to contribute anything from a bug report to a code change, see
our [contribution guidelines](CONTRIBUTING.md).
[MIT &copy; Siren.js](LICENSE)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@siren-js/core",
"version": "0.4.0",
"description": "Core Siren.js functionality",
"description": "Cross-platform library of classes for generating and parsing Siren entities",
"files": [
"/dist"
],
Expand Down

0 comments on commit b2bcfe7

Please sign in to comment.