0.2.0 • Published 4 years ago

@valaatech/treco-rest-api-mappings v0.2.0

Weekly downloads
-
License
MIT
Repository
-
Last release
4 years ago

ValOS Treco REST API

1. Introduction

This document contains the specification of the Treco ValOS REST API.

This library contains mappings for @valos/rest-api-spindle between Treco ValOS resource model and the public Treco ValOS REST API.

2. Endpoint entry points:

2.1. Endpoint name and version prefix:

base= /treco/v1/

All relative references returned in data sets are based on this path.

2.2. Top level REST routes

events events/<valosId> individuals individuals/<valosId> organizations organizations/<valosId> services services/<valosId> interests interests/<valosId>

3. valosId

valosId string identifies a Resource and is provided by ValOS. It is (note: will be, shortly) cryptographically tied to its creator and the targeted event log and as such cannot be naively created by the client. Character set of valosId is constrained so that it can be used in URI path, query or fragment parts without url-encode:

valos-id = primary-part [ "!" secondary-part ] primary-part = *( unreserved ) secondary-part = prefix *( pchar / "!" ) prefix = *( unreserved / pct-encoded / "$" / "@" ) ":"

4. GET requests

4.1 Nested properties can be requested based on the Treco schema like so:

-> GET /events/f00b-dada-0077/offering -> GET /events/f00b-dada-0077?relations=offering,seeking&properties=name,visible

4.2. Relation sequence results and HATEOAS

Properties, objects and regular sequences are expanded fully in the result set except Relation targets. They are the primary resource cross-link primitive and replaced with HATEOAS id/href, like so:

-> GET /individuals/812dff37-5ba7-42d8-a8d3-a5039d8cf0f9/?properties=name,visible,contact,description&relations=interests,seeking,offering

<-

{
  "name": "Ville Ilkkala",
  "visible": true,
  "description": "Overexcited about everything, from cats to quantum physics",
  "contact": {
    "email": "ville@valaa.com",
    "linkedin": "https://www.linkedin.com/in/ville-ilkkala-1809b319/",
    "website": "https://valaa.com"
  },
  "interests": [
    { "$V": {
      "target": "05aac8ca-7c86-41f0-8bd0-7f6543b80b9e",
      "rel": "INTEREST",
      "href": "/interests/05aac8ca-7c86-41f0-8bd0-7f6543b80b9e"
    } },
    { "$V": {
      "target": "05aac8ca-7c86-41f0-8bd0-f00d15",
      "rel": "INTEREST",
      "href": "/interests/05aac8ca-7c86-41f0-8bd0-f00d15"
    } }
  ],
  "offering": []
}

4.3. Pagination of sequence results:

-> GET <route>?offset=<startIndex> -> GET <route>?limit=<maxResultCount>

4.4. Filtering and sorting sequence results

-> GET <route>?id=<valosId-1>,<valosId-2>,...,<valosId-n>

TODO: Filtering and sorting is needed for pagination.

4.5. Roundtrip and bandwidth consumption minimization via pruned recursive results a la GraphQL

A general solution is provided eventually but is likely not a REST API. For REST I'd like to evaluate practical solutions for specific use cases, based on simpler catch-all solutions, as some variant of following examples (different semantics)

-> GET /organizations/f00b-dada-0077?expandedRelations=offering,seeking&relations=owned/events -> GET /organizations/f00b-dada-0077?relations=offering,seeking,owned/events&depth=1 etc.

5. DELETE, PUT, PATCH requests

TODO. The fact the valosId cannot be created on the client side makes creation of resources using PUT inconvenient. Idempotence is invaluable, however, so I see couple potential options:

  1. an API for querying for a list of pre-generated id's that can be directly used as part of PUT requests. These would have to be used in order and thus if the same user has multiple applications open there are race conditions. Upside: idempotence with clean REST PUT with symmetry to GET Downside: brittle and racy.

  2. PUT for a resource creation operation, where uuid can be used to identify the creation operation, but where the final resource has a different id (which returned as part of the 200/204 response) Upside: simple and reliable. Downside: A bit non-RESTy. Definitely no REST symmetry with GET. Has roundtrips as client needs to wait for the response.

  3. POST with idempotence. Essentially the same as with no. 2, but with different verb. Upside: More RESTy in that POST has no symmetry expectations. Downside: POST smells because it's not idempotent. But this is idempotent, so false advertising.

None of the solutions are ideal. I would be leaning towards 2. personally. But there's also the fact that the one thing where POST is considered at home is manipulation of sequences/containers... and in the context of ValOS treco model much of the resource manipulation is indeed creation of new Relation resources.

I'll have to think about this a bit more, feedback and perspectives desirable.

6. Treco ontology

Definitions use https://github.com/facebook/flow notation. Some of the types are not defined yet (the date types, some string types etc).

6.1. Common interfaces

type ValOSFields = {
  id: ValOSIdString,
  name: string, // Internal, developer oriented name of the resource.
},

type Relation<T> = {
  $V: ValOSFields && {
    target: ValOSIdString,
    rel: string,
    href: string,
  },
  // Relation objects are first class ValOS entities and can be
  // fully extended with properties (and even sub-relations).
  // These values are useful for both semantic and perf reasons:
  // they are not part of the target object and can be contextual
  // information of the relation source resource, notes, persisted
  // sorting keys, etc.
  // In addition they are/can be part of the sequence result sets
  // without having to traverse through the target ValOSIdString.
};

type TrecoDetail = {
  $V: ValOSFields,
  icon?: string, // CSS class name string
  coverImage?: string, // maybe?
  profileImage?: string, // maybe?
};

6.2. Primary Treco Content types

type TrecoRoot = {
  events: Relation<TrecoEvent>[],
  individuals: Relation<TrecoIndividual>[],
  organizations: Relation<TrecoOrganization>[],
  services: Relation<TrecoService>[],
};

type TrecoIndividual = TrecoProfile && {
  title?: string,
  company?: string,
};

type TrecoOrganization = TrecoProfile && {};

type TrecoEvent = TrecoProfile && {
  when: TrecoTime,
};

type TrecoService = TrecoProfile && {};

type TrecoProfile = TrecoDetail && {
  name: string,
  description?: string,
  visible: boolean,
  contact: {
    email?: string,
    facebook?: string,
    linkedin?: string,
    phone?: string,
    website?: string,
  },
  owned?: {
    events: Relation<Event>[],
    organizations: Relation<Organization>[],
    services: Relation<Service>[],
  },
  interests?: Relation<Interest>[],
  offering?: Relation<Tag>[],
  seeking?: Relation<Tag>[],
};

type TrecoTime = {
  // Treco time is always fully specified as a combination of unix time
  // seconds and zone. Treco time provides convenience accessors
  // 'date', 'time' and 'datetime'.

  // PUT and PATCH updates can be done using any combination of
  // mutually consistent fields.
  // PUT with limited subset of fields completes missing 'time' as
  // "00:00" and zone as "Z".
  // A PUT request which is missing date information is rejected.
  // A PATCH update on a derived field only updates the information
  // affiliated with the field.
  // Specifically this means that a PATCH on a timezone will _not_
  // alter timestamp but _will_ alter the derived field 'time' and
  // possibly 'date'.
  // Treco restricts the ISO8601 years to 0000-9999 (unixSeconds in
  // [-62125920000, 253234079999]. zone requests that would cross
  // the boundaries are rejected).

  // Primary fields
  unixSeconds: UnixEpochSeconds, // JSON request double fractions are accepted but floored out.
  zone: ISO8601ZoneExtended, // "+02", "+13:45"

  // Derived fields
  date: ISO8601DateExtended,
  // date information affiliation: floor(timestamp / 86400)

  time: ISO8601TimeExtended, // "01:43" , "23:59:59.13", "00"
  // time information affiliation: timestamp % 86400

  datetime: ISO8601DateTimeZoneExtended, // "2019-01-11T23:59:59.13+13:45"
  // datetime information affiliation: timestamp, zone
};