From b2bcfe759e1a7e1afabb13bca30773cc3fa1370b Mon Sep 17 00:00:00 2001 From: Dillon Redding Date: Sat, 10 Sep 2022 16:41:25 -0500 Subject: [PATCH] updating and standardizing README --- README.md | 288 ++++++++++++++++----------------------------------- package.json | 2 +- 2 files changed, 89 insertions(+), 201 deletions(-) diff --git a/README.md b/README.md index 06167ae..4b2f83e 100644 --- a/README.md +++ b/README.md @@ -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: [ { @@ -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: [ { @@ -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 © Siren.js](LICENSE) diff --git a/package.json b/package.json index 1b387f8..4c3e595 100644 --- a/package.json +++ b/package.json @@ -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" ],