@siren-js/core
    TypeScript icon, indicating that this package has built-in type declarations

    0.3.2 • Public • Published

    Siren.js Core

    Node Package Build Status Code Coverage License Contributing

    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.

    Installation

    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 section below.

    Usage

    The following example demonstrates one way of building the example entity from the Siren spec. It uses the Entity class which takes an object representing a Siren entity.

    import * as Siren from '@siren-js/core';
    
    const order = getOrderFromDB(orderNumber);
    
    const entity = new Siren.Entity({
      class: ['order'],
      properties: {
        orderNumber: order.orderNumber,
        itemCount: order.items.length,
        status: order.orderStatus
      },
      entities: [
        {
          class: ['items', 'collection'],
          rel: ['http://x.io/rels/order-items'],
          href: `http://api.x.io/orders/${order.orderNumber}/items`
        },
        {
          class: ['info', 'customer'],
          rel: ['http://x.io/rels/customer'],
          properties: {
            customerId: order.customer.userId,
            name: order.customer.fullName
          },
          links: [
            {
              rel: ['self'],
              href: `http://api.x.io/customers/${order.customer.userId}`
            }
          ]
        }
      ],
      actions: [
        {
          name: 'add-item',
          title: 'Add Item',
          method: 'POST',
          href: `http://api.x.io/orders/${order.orderNumber}/items`,
          type: 'application/x-www-form-urlencoded',
          fields: [
            { name: 'orderNumber', type: 'hidden', value: `${order.orderNumber}` },
            { name: 'productCode', type: 'text' },
            { name: 'quantity', type: 'number' }
          ]
        }
      ],
      links: [
        {
          rel: ['self'],
          href: `http://api.x.io/orders/${order.orderNumber}`
        },
        {
          rel: ['previous'],
          href: `http://api.x.io/orders/${order.orderNumber - 1}`
        },
        {
          rel: ['next'],
          href: `http://api.x.io/orders/${order.orderNumber + 1}`
        }
      ]
    });

    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:

    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
      ]
    });

    Now constructing the full entity is a little easier.

    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

    Component Lookup

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

    entity.getActionByName('add-item');
    //=> same as addItemAction
    
    addItemAction.getFieldByName('quantity');
    //=> same as quantityField

    The Entity class also has methods for looking up sub-entities and links by rel and class, as well as actions by class.

    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');
    //=> []

    The Action class has a method for looking up fields by class that works similarly.

    Generating Siren

    To generate a Siren representation, use JSON.stringify().

    const siren = JSON.stringify(entity);

    Parsing Siren

    To parse a string as a Siren representation, use JSON.parse() and pass the result to the Entity constructor.

    new Siren.Entity(JSON.parse(siren));
    //=> same as entity

    Extensions

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

    const link = new Siren.Link(['profile'], 'http://api.example.com/profile', {
      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.

    // 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.

    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).

    Contributing

    If you would like to contribute anything from a bug report to a code change, see our contribution guidelines.

    Install

    npm i @siren-js/core

    DownloadsWeekly Downloads

    16

    Version

    0.3.2

    License

    MIT

    Unpacked Size

    86.6 kB

    Total Files

    24

    Last publish

    Collaborators

    • avatar