Resource Provider plugins

Overview

The Signal K server Resource API provides a common set operations for clients to interact with routes, waypoints, charts, etc but it does NOT provide the ability to persist or retrieve resources to / from storage.

This functionality needs to be provided by one or more server plugins that interface with the Resource API to facilitate the storage and retrieval of resource data.

These plugins are called Provider Plugins.

Resource API architecture:

This de-coupling of request handling and data storage provides the flexibility to persist resource data in a variety of different storage types as well as Internet based services.

Note: Signal K server comes with the resources-provider-plugin pre-installed which persists resource data to the local file system.

Resources API:

The Resources API handles all client requests received via the /signalk/v2/api/resources path, before passing on the request to registered provider plugin(s).

The Resources API performs the following operations when a request is received:

  1. Checks for registered provider(s) for the resource type (i.e. route, waypoint, etc.)
  2. Checks that the required ResourceProvider methods are defined for the requested operation (i.e. POST, PUT, GET, DELETE)
  3. Performs an access control check
  4. POST and PUT requests for Standard (Signal K defined) resource types are checked for validity of the submitted:
  • resource id
  • resource data against the OpenAPI definition.

Only after successful completion of all these operations is the request passed on to the registered provider plugin(s).


Provider plugins:

A resource provider plugin is a Signal K server plugin that implements the Resource Provider Interface which:

  • Tells server the resource type(s) provided for by the plugin (i.e. route, waypoint, etc.)
  • Registers the methods used to action requests passed from the server and perform the writing, retrieval and deletion of resources from storage.

Note: multiple providers can be registered for a resource type (e.g. 2 x chart providers)

The ResourceProvider interface is defined as follows in @signalk/server-api:

interface ResourceProvider {
  type: ResourceType
  methods: ResourceProviderMethods
}

where:

  • type: The resource type provided for by the plugin. These can be either Standard (Signal K defined) or Custom (user defined) resource types (e.g. 'routes', 'fishingZones')

  • methods: An object implementing the ResourceProviderMethods interface defining the functions to which resource requests are passed by the SignalK server. Note: The plugin MUST implement each method, even if that operation is NOT supported by the plugin!

The ResourceProviderMethods interface is defined as follows in @signalk/server-api:

interface ResourceProviderMethods {
  listResources: (query: { [key: string]: any }) => Promise<{[id: string]: any}>
  getResource: (id: string, property?: string) => Promise<object>
  setResource: (
    id: string,
    value: { [key: string]: any }
  ) => Promise<void>
  deleteResource: (id: string) => Promise<void>
}

Note: The Resource Provider is responsible for implementing the methods and returning data in the required format!

Provider Methods:

listResources(query): This method is called when a request is made for resource entries that match a specific criteria.

Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters.

  • query: Object contining key | value pairs repesenting the parameters by which to filter the returned entries. e.g. {region: 'fishing_zone'}

returns: Promise<{[id: string]: any}>

Example: Return waypoints within the bounded area with lower left corner at E5.4 N25.7 & upper right corner E6.9 & N31.2:

GET /signalk/v2/api/resources/waypoints?bbox=[5.4,25.7,6.9,31.2]

ResourceProvider method invocation:

listResources(
  {
    bbox: '5.4,25.7,6.9,31.2'
  }
);

Returns:

{
  "07894aba-f151-4099-aa4f-5e5773734b69": {
    "name":"my Point",
    "description":"A Signal K waypoint",
    "distance":124226.65183615577,
    "feature":{
      "type":"Feature",
      "geometry":{
        "type":"Point",
        "coordinates":[5.7,26.4]
      },
      "properties":{}
    },
    "timestamp":"2023-01-01T05:02:54.561Z",
    "$source":"resources-provider"
  },
  "0c894aba-d151-4099-aa4f-be5773734e99": {
    "name":"another point",
    "description":"Another Signal K waypoint",
    "distance":107226.84,
    "feature":{
      "type":"Feature",
      "geometry":{
        "type":"Point",
        "coordinates":[6.1,29.43]
      },
      "properties":{}
    },
    "timestamp":"2023-01-01T05:02:54.561Z",
    "$source":"resources-provider"
  }
}

getResource(id, property?): This method is called when a request is made for a specific resource entry with the supplied id. If property is supplied then the value of the resource property is returned. If there is no resource associated with the id the call should return Promise.reject.

  • id: String containing the target resource entry id. (e.g. '07894aba-f151-4099-aa4f-5e5773734b99')
  • property (optional): Name of resource property for which to return the value (in dot notation). e.g. feature.geometry.coordinates

returns: Promise<object>

Example resource request:

GET /signalk/v2/api/resources/routes/07894aba-f151-4099-aa4f-5e5773734b99

ResourceProvider method invocation:

getResource(
  '07894aba-f151-4099-aa4f-5e5773734b99'
);

Returns:

{
  "name":"myRoute",
  "description":"A Signal K route",
  "distance":124226.65183615577,
  "feature":{
    "type":"Feature",
    "geometry":{
      "type":"LineString",
      "coordinates":[[-8,-8],[-8.5,-8],[-8.5,-8.4],[-8.7,-8.3]]
    },
    "properties":{}
  },
  "timestamp":"2023-01-01T05:02:54.561Z",
  "$source":"resources-provider"
}

Example resource property value request:

GET /signalk/v2/api/resources/routes/07894aba-f151-4099-aa4f-5e5773734b99/feature/geometry/type

ResourceProvider method invocation:

getResource(
  '07894aba-f151-4099-aa4f-5e5773734b99',
  'feature.geometry.type'
);

Returns:

{
  "value": "LineString",
  "timestamp":"2023-01-01T05:02:54.561Z",
  "$source":"resources-provider"
}

setResource(id, value): This method is called when a request is made to save / update a resource entry with the supplied id. The supplied data is a complete resource record.

  • id: String containing the id of the resource entry created / updated. e.g. '07894aba-f151-4099-aa4f-5e5773734b99'

  • value: Resource data to be stored.

returns: Promise<void>

Example PUT resource request:

PUT /signalk/v2/api/resources/routes/07894aba-f151-4099-aa4f-5e5773734b99 {resource_data}

ResourceProvider method invocation:

setResource(
  '07894aba-f151-4099-aa4f-5e5773734b99',
  {
    name: 'test route', 
    distance': 8000, 
    feature: {
      type: 'Feature', 
      geometry: {
        type: 'LineString',
        coordinates: [[138.5, -38.6], [138.7, -38.2], [138.9, -38.0]]
      },
      properties:{}
    }
  }
);

Example POST resource request:

POST /signalk/v2/api/resources/routes {resource_data}

ResourceProvider method invocation:

setResource(
  '<server_generated_id>',
  {
    name: 'test route', 
    distance': 8000, 
    feature: {
      type: 'Feature', 
      geometry: {
        type: 'LineString',
        coordinates: [[138.5, -38.6], [138.7, -38.2], [138.9, -38.0]]
      },
      properties:{}
    }
  }
);

deleteResource(id): This method is called when a request is made to remove the specific resource entry with the supplied resource id.

  • id: String containing the target resource entry id. e.g. '07894aba-f151-4099-aa4f-5e5773734b99'

returns: Promise<void>

Example resource request:

DELETE /signalk/v2/api/resources/routes/07894aba-f151-4099-aa4f-5e5773734b99

ResourceProvider method invocation:

deleteResource(
  '07894aba-f151-4099-aa4f-5e5773734b99'
);

Registering as a Resource Provider:

To register a plugin as a provider for one or more resource types with the SignalK server, it must call the server's registerResourceProvider function for each resource type being serviced during plugin startup.

The function has the following signature:

app.registerResourceProvider(resourceProvider: ResourceProvider)

where:

  • resourceProvider: is a reference to a ResourceProvider object containing the resource type and methods to receive the requests.

Note: More than one plugin can be registered as a provider for a resource type.

Example: Plugin registering as a routes & waypoints provider.

import { ResourceProvider } from '@signalk/server-api'

module.exports = function (app) {

  const plugin = {
    id: 'mypluginid',
    name: 'My Resource Providerplugin'
  }

  const routesProvider: ResourceProvider = {
      type: 'routes',
      methods: {
        listResources: (params) => { 
          fetchRoutes(params)
          ... 
        },
        getResource: (id, property?) => { 
          getRoute(id, property)
          ... 
        },
        setResource: (id, value )=> { 
          saveRoute(id, value)
          ... 
        },
        deleteResource: (id) => { 
          deleteRoute(id, value)
          ...
        }
      }
    }

  const waypointsProvider: ResourceProvider = {
      type: 'waypoints',
      methods: {
        listResources: (params) => { 
          fetchWaypoints(params)
          ... 
        },
        getResource: (id, property?) => { 
          getWaypoint(id, property)
          ... 
        },
        setResource: (id, value )=> { 
          saveWaypoint(id, value)
          ... 
        },
        deleteResource: (id) => { 
          deleteWaypoint(id, value)
          ...
        }
      }
    }

  plugin.start = function(options) {
    ...
    try {
      app.registerResourceProvider(routesProvider)
      app.registerResourceProvider(waypointsProvider)
    }
    catch (error) {
      // handle error
    }
  }

  return plugin
}

Methods

A Resource Provider plugin must implement ALL methods to service the requests passed from the server.

Each method should return a Promise on success and throw on error, if a request is not serviced or is not implemented.

Example:

// SignalK server plugin 
module.exports = function (app) {

  const plugin = {
    id: 'mypluginid',
    name: 'My Resource Providerplugin',
    start: options => { 
      ... 
      app.registerResourceProvider({
        type: 'waypoints',
        methods: {
          listResources: (params) => { 
            return new Promise( (resolve, reject) => {
              ...
              if (ok) {
                resolve(resource_list)
              } else {
                reject( new Error('Error fetching resources!'))
              }
            })
          },
          getResource: (id, property?) => { 
            return new Promise( (resolve, reject) => {
              ...
              if (ok) {
                resolve(resource_list)
              } else {
                reject( new Error('Error fetching resource with supplied id!'))
              }
            })
          },
          setResource: (id, value )=> { 
            throw( new Error('Not implemented!')) 
          },
          deleteResource: (id) => { 
            throw( new Error('Not implemented!')) 
          }
        }
      })
    }

  }
}