Lanetix interface library.

Usage no npm install needed!

<script type="module">
  import lanetixInterfaces from 'https://cdn.skypack.dev/@lanetix/interfaces';



An interface is a statement of conditions a Lanetix organization, construed as a collection of immutable entities (e.g. record and workflows types), may or may not fulfill. It is being developed first and foremost to provide both a machine- and human- readable description of an apps.lanetix.com Ext or Package's prerequisites.

This repository should include:

  • Flowtype declarations for...
    • interfaces
    • parameters (data structures which satisfy an interface for a given organization)
    • manifests (an interface along with handler and capability declarations)
  • Documentation of interface/parameter/manifest semantics.
  • A library which can...
    • Validate that a given JSON structure is an interface (conforms to flowtype).
    • Given an interface, a description of an organization's entities, and parameters for that interface, determine whether the parameters satisfy the interface, and if not, determine which values (if any) could be additionally bound so as to satisfy it.


Suggestion is a two phase process.


In the first phase, given an interface and a list of an organization's complete record type definitions, we generate all valid mappings of organization record types to LxRecordType parameters in the interface with the function generateAllValidRecordTypeMappings, which has the signature:

(rtds: CRTD[], int: ExtInt) => Array<{ [lxRecordName: string]: string }>

Each element in the returned array is a record type mapping (distinct from an ExtMapping, which includes mappings for fields) that would satisfy the interface. For instance, if this function returned the following, it would mean that A (the LxRecord argument of the interface) could be mapped to b, which case B and C would have to be mapped to c and a respectively:

  {"A": "a", "B": "b", "C": "c"},
  {"A": "b", "B": "c", "C": "a"},
  {"A": "c", "B": "a", "C": "b"}

How is this list of RTMappings generated? In short, both the interface and record type list are reduced to directed graphs represented as adjacency matrices. Each record type in an organization's list of record types is treated as a vertex, and each picker is treated as an edge; this constitutes the "target graph". Likewise, each LxRecord argument to an interface is a vertex, and each picker on its lens is treated as an edge; this constitutes the "pattern graph".

Our goal of finding valid mappings is then reduced to the problem of finding occurrences of the pattern graph in the target graph, i.e. finding subgraph isomorphisms of the the pattern graph onto the target graph. This is accomplished with the Ullman subgraph isomorphism algorithm, using bwestergard's JS implementation.

Ullman's algorithm constructs a search tree, each node of which is a matrix representing potential mappings from the pattern to the target graph. Starting with the unit matrix (representing the presupposition all mappings are possible a priori), it prunes away all of those possibilities that are not valid isomorphisms. But here, we can optimize.

Because we know that, for a given field type (e.g. Text) that occurs in the list of an LxRecord argument required fields (its "lens"), there must be at least as many fields of that type in a record type that would be mapped to it, we can initialize the Ullman algorithm with an adjacency matrix from which mismatches of this kind have been removed. If, for example, an LxRecord requires two Decimal fields and three Text fields, the edge from it to a record type with one Decimal field and no Text fields would be eliminated in this first pass, before the Ullman search commences. We foresee this dramatically shrinking the search space in real-world use cases.

Note: at present, all LxRecord pickers are treated as equivalent during this initial matrix generation stage despite, e.g., referring to several different LxRecords. Further optimization is possible here.


The second phase of suggestion is executed repeatedly as part of an interaction with the user (an admin "mapping the interface"). This is done with the remainingPossibilities function, which has the following signature:

  rtds: CRTD[],
  int: ExtInt,
  allRTMappings: Array<{ [lxRecordName: string]: string }>,
  partialMapping: ExtMapping
) => MappingPossibilities

Where MappingPossibilities is defined as:

type MappingPossibility =
  +record: string[],
  +fields?: {
    [fieldName: string]: string[]

type MappingPossibilities = {
  [lxRecordName: string]: MappingPossibility

allRTMappings is a list of RTMappings genrated in the last step. This only needs to be done once per mapping process, and can be reused in each subsequent call to remainingPossibilities.

Note the partialMapping argument. This will initially be an empty dictionary of parameters. As the user maps LxRecordType params, they will affect the records and fields suggested in the MappingPossibilities remainingPossibilities returns.

A MappingPossibility for a given LxRecord only contains suggestions of the records that could be mapped until mapping occurs, at which point it will contain suggestions for the fields. The suggestion sets, at the record and field level, are always complete: they are only valid mappings and they are all of the valid mappings, given the mapping decisions made thus far, represented in partialMapping.

In order for this former condition to hold, it is necessary to infer mappings of LxRecord arguments from mappings of picker fields which refer to those arguments. Using the kanban interface as an example: if the owning_project picker field (which refers to the project LxRecord) on the card LxRecordType is mapped to a picker field on the organization idea record type that is constrained to reference the innovation record type, this might result in automatic mapping of the project LxRecord to innovation.

Eventually, the user will have mapped everything, at which point the mapping should pass test administered by the verify function.