Entity Querying

The real power behind Nymph is the entity querying system.

Factory Method

The Entity class' factory method can take a GUID as an argument. You can provide other factory functions that can take things as well. For example, the User class in Tilmeld has a factoryUsername method that takes a username. The method will return a new entity if the queried entity is not found. You can determine if it was found by checking that its GUID is not null.

// FoobarBaz expects a GUID.
const baz = await FoobarBaz.factory(guid);
if (baz.guid == null) {
  console.error("Can't find the Foobar Baz!");
}

// Tilmeld's User class expects a GUID or a username.
const cronUser = await User.factoryUsername('cron');
if (cronUser.guid == null) {
  console.error("Can't find the cron user!");
}

Nymph's Query Language

The powerful way of querying entities is Nymph's getEntities and getEntity methods. The first argument is an Options object.

Option Type Default Description
class typeof Entity Entity The class used to create each entity. It must have a factory static method that returns a new instance.
limit number undefined The limit of entities to be returned. Not needed when using getEntity, as it always returns only one.
offset number 0 The offset from the first matching entity, in order, to start retrieving.
reverse boolean false If true, entities will be retrieved from newest to oldest/largest to smallest (with regard to sort).
sort 'cdate' | 'mdate' | string 'cdate' How to sort the entities. Should be "cdate", "mdate", or the name of a property.
return 'entity' | 'guid' | 'count' 'entity' What to return, the entities with their data, just the GUIDs, or just a count.
source string undefined Will be 'client' if the query came from a REST request or the PubSub server. (Mainly used in Tilmeld for access control.)
skipCache boolean false If true, Nymph will skip the cache and retrieve the entity from the DB.
skipAc boolean false If true, Tilmeld will not filter returned entities according to access controls. (If Tilmeld is installed.) (This is always set to false by the REST endpoint and PubSub server.)

Every argument following the Options is a Selector object. They contain clauses and a type. An entity must match each selector to be returned. There are four selector types, and they are defined on the type property.

Type Name Description
& And All clauses in the selector must match.
| Or At least one clause in the selector must match.
!& Not And All clauses in the selector must not match.
!| Not Or At least one clause in the selector must not match.

The other properties of the Selector are clauses. Clauses use the form name: value, or name: [value1, value2, ...]. They can be negated by prepending a bang (!) to the name, like '!name': value. A clause that has multiple values is considered as multiple clauses in terms of matching for "or" selectors.

Property Description Example Works On
guid The entity's GUID is equal. {type: '&', guid: '790229ae527f1511b3120b71'} entity.guid = '790229ae527f1511b3120b71'
tag The entity has the tag. {type: '&', tag: 'foobar'} entity.$addTag('foobar')
defined The named property is not undefined. {type: '&', defined: 'foo'} entity.foo = 0
truthy The named property evaluates to true. {type: '&', truthy: 'foo'} entity.foo = 1
equal The named property is defined and equals the value (their JSON strings are identical). {type: '&', equal: ['foo', 0]} entity.foo = 0
contain The named property contains the value (its JSON string is found within the property's JSON string). {type: '&', array: ['foo', 'bar']} entity.foo = ['bar', 'baz']
match The named property matches. Uses POSIX RegExp. Case sensitive. Must *not* be surrounded by any delimiters. {type: '&', match: ['foo', 'bar.*z']} entity.foo = 'foobarbaz'
imatch The named property matches. Uses POSIX RegExp. Case insensitive. Must *not* be surrounded by any delimiters. {type: '&', imatch: ['foo', 'BaR.*Z']} entity.foo = 'foobarbaz'
like The named property matches. Uses % for variable length wildcard and _ for single character wildcard. Case sensitive. {type: '&', like: ['foo', 'f%bar_az']} entity.foo = 'foobarbaz'
ilike The named property matches. Uses % for variable length wildcard and _ for single character wildcard. Case insensitive. {type: '&', ilike: ['foo', 'F%bAr_aZ']} entity.foo = 'foobarbaz'
gt The named property is greater than the value. {type: '&', gt: ['foo', 5]} entity.foo = 6
gte The named property is greater than or equal to the value. {type: '&', gte: ['foo', 6]} entity.foo = 6
lt The named property is less than the value. {type: '&', lt: ['foo', 7]} entity.foo = 6
lte The named property is less than or equal to the value. {type: '&', lte: ['foo', 6]} entity.foo = 6
ref The named property is the entity or contains the entity. {type: '&', ref: ['foo', '790229ae527f1511b3120b71']} entity.foo = await Entity.factory('790229ae527f1511b3120b71')
qref The named property is an entity that matches the query or contains an entity that matches the query. {type: '&', qref: ['foo', [{class: Entity}, {type: '&', equal: ['name', 'Foobar']}]]} entity.foo = await nymph.getEntity({class: Entity}, {type: '&', equal: ['name', 'Foobar']})
selector A selector. (Keep in mind, you can also use an array of these, just like any other clause.) {type: '&', selector: {type: '|', tag: ['foo', 'bar']}} entity.$addTag('bar')

The clauses "equal", "contain", "gt", "gte", "lt", and "lte" can also accept a third element. If value is null and the third element is a string, the third element will be used with Locutus' strtotime function to set the value to a relative timestamp. For example, the following selector will look for all entities that were created in the last day.

{
  type: '&',
  gte: ['cdate', null, '-1 day']
}

Querying Examples

So putting it all together, you can specify the options and selectors to find the exact entities you want.

Get the first FoobarBaz entity.

const entity = await nymph.getEntity({ class: FoobarBaz });

Get the latest FoobarBaz entity.

const entity = await nymph.getEntity({
  class: FoobarBaz,
  reverse: true
});

Get all baz tagged entities, using the FoobarBaz class.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz
  },
  {
    type: '&',
    tag: 'baz'
  }
);

Get the five last created bar and baz tagged entities.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz,
    reverse: true,
    limit: 5
  },
  {
    type: '&',
    tag: ['bar', 'baz']
  }
);

Get the five last modified bar and baz tagged entities.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz,
    reverse: true,
    limit: 5,
    sort: 'mdate'
  },
  {
    type: '&',
    tag: ['bar', 'baz']
  }
);

Get the third page of sorted by name, baz tagged entities (if pages are 5 entities long).

const entities = await nymph.getEntities(
  {
    class: FoobarBaz,
    limit: 5,
    offset: 10,
    sort: 'name'
  },
  {
    type: '&',
    tag: ['baz']
  }
);

Get baz tagged entities with names.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz
  },
  {
    type: '&',
    tag: 'baz',
    defined: 'name'
  }
);

Get baz tagged entities without names.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz
  },
  {
    type: '&',
    tag: 'baz',
    '!defined': 'name'
  }
);

Get baz tagged entities without names or bar tagged entities with names.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz
  },
  {
    type: '|',
    selector: [
      {
        type: '&',
        tag: 'baz',
        '!defined': 'name'
      },
      {
        type: '&',
        tag: 'bar',
        defined: 'name'
      }
    ]
  }
);

Get baz tagged entities with either first names or last names.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz
  },
  {
    type: '&',
    tag: 'baz'
  },
  {
    type: '|',
    defined: ['firstName', 'lastName']
  }
);

Get baz tagged entities created in the last day.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz
  },
  {
    type: '&',
    tag: 'baz',
    gt: ['cdate', null, '-1 day']
  }
);

Get baz tagged entities with names, who either make only up to 8 dollars pay or are under 22.

const entities = await nymph.getEntities(
  {
    class: FoobarBaz
  },
  { type: '&', tag: 'baz', defined: 'name' },
  {
    type: '!|', // at least one must be false
    gte: ['age', 22],
    gt: ['pay', 8]
  }
);

Get baz tagged entities named Clark, James, Chris, Christopher, Jake, or Jacob.

const entities = await nymph.getEntities(
  { class: FoobarBaz },
  { type: '&', tag: 'baz' },
  {
    type: '|',
    equal: [
      ['firstName', 'Clark'],
      ['firstName', 'James']
    ],
    match: [
      ['firstName', '^Chris(topher)?$'],
      ['firstName', '^Ja(ke|cob)$']
    ]
  }
);

Get baz tagged entities that belong to any user named "John" or "James".

const entities = await nymph.getEntities(
  { class: FoobarBaz },
  {
    type: '&',
    tag: 'baz',
    qref: [
      'user',
      [
        { class: User },
        {
          type: '|',
          like: [
            ['name', 'John %'],
            ['name', 'James %']
          ]
        }
      ]
    ]
  }
);
Previous: Creating Entities Next: Subscribing to Queries