1.3.0 • Published 2 months ago

sl-triggers v1.3.0

Weekly downloads
-
License
ISC
Repository
-
Last release
2 months ago

Table of contents

  1. Datasource
  2. Create and subscribe for trigger
  3. Event processing
  4. Triggers API

Datasource

sequenceDiagram   
    participant triggers
    participant adapter
    participant datasource               
    datasource->>adapter: sends raw data
    adapter->>adapter: converts raw data into standard events
    adapter->>triggers: pushes standard events to  http interface        
  • datasource streams events to adapter
  • adapter being aware of the logic of the datasource converts raw data into internal standard events.
  • adapter pushes standard events into http interface of triggers

Each standard event represents an entity from this table: https://docs.google.com/spreadsheets/d/1YBeSAcBpC596bcqI0M_mkXIqGHSMgbGh4myCF4Er89k/edit#gid=452767957

All events extend the base standard event

interface Event {
    // unique raw event identifier from datasource
    id: string 
    datasource: "sportradar"
    // scope of event
    scope: "game"
    // unique scope identifier, e.g. d8539eb6-3e27-40c8-906f-9cd1736321d8, adapter takes it from datasource raw data
    scopeId: string
    // event name
    name: string    
    // event value
    value: string | number
    // timestamp, unixtime millis
    timestamp: number
}

Example of events derived from Event

interface GameLevelEvent extends Event {
    name: "game.level" 
    value: "game.start" | "game.quarter.start" | "game.half.start" | "game.quarter.end" | "game.half.end" | "game.end" | "game.under_review"
}

interface GameHomePointsEvent extends Event {
    name: "game.home_points"    
    value: number
}

interface GameAwayPointsEvent extends Event {
    name: "game.away_points"    
    value: number
}

the event objects will be like

{   
    datasource: "sportradar",
    scope: "game",
    scopeId: "d8539eb6-3e27-40c8-906f-9cd1736321d8",    
    id: "ab362b76-b89d-4cd8-80e3-340b048f98c8",
    name: "game.level",
    value: "game.start",
    timestamp: 1686054201993
} as GameLevelEvent

{
   datasource: "sportradar",
   scope: "game",
   scopeId: "d8539eb6-3e27-40c8-906f-9cd1736321d8",
   id: "ab362b76-b89d-4cd8-80e3-340b048f98c8",
   name: "game.home_points",         
   value: 100,
   timestamp: 1686054201993
} as GameHomePointsEvent

Create and subscribe for trigger

To use service one should create trigger and subscribe for trigger to be notified of its activation. It is assumed that Studio should be used for this purpose.

sequenceDiagram       
    participant glossary
    participant studio            
    participant triggers
    studio->>glossary: get list of NBA games, players etc
    glossary->>studio: list of reference data
    studio->>triggers: get list of triggers for game_id
    triggers->>studio: list of triggers <br/>[ { trigger_id, conditions, description } ]
    studio->>triggers: create new trigger if none suits
    triggers->>studio: trigger object { trigger_id }
    studio->>triggers: subscribe service <br/>{ trigger_id, route, <br/>payload, service: "polls", <br/>entity: "question", entity_id }
    triggers->>studio: ok, subscribed
  • studio fetches datasource nomenclature from glossary to show/create triggers; e.g. "sl-sports" can be used as glossary, since it has sync with sportradar;
  • studio matches "game.id" of interest from glossary and stores it as scope and scopeId somewhere inside the moderation object;
  • studio fetches a list of existing triggers by using { datasource, scope, scopeId};
  • if none of the existing triggers suits, studio creates new trigger with certain conditions;
  • studio subscribes the destination service (e.g. "polls") for a trigger; to do so studio presents following arguments:
    • callback route, e.g. "studio.questions.activate"
    • callback payload to be passed into the route, e.g. { id } with question id

Trigger is the main entity inside service triggers

interface Trigger {
   // uuid generated for each trigger
   id: string
   // human-readable name just for description purposes
   name: string
   // human-readable name just for description purposes
   description: string
   datasource: "sportradar"
   scope: "game"
   scopeId: string   
   conditions: TriggerCondition[]
}
interface TriggerCondition {
   // uuid generated for each condition
   id: string
   event: string   
   // comparison operation, should be used to compare "current" and "target" and return a boolean
   compare: "eq","lt","gt","le","ge"   
   // type of condition
   type: "set-and-compare"
   // target value of the event, threshold value to compare with
   target: string | number
   // current value read from event
   current: string | number
   // true when compare(target, current) == true
   activated: boolean
   // log of events consumed by condition
   log: Map<string, Event>
}

example of trigger object

{
   id: "{uuid}",
   name: "home points 30+",
   description: "should trigger when home points reach 30 or more",
   datasource: "sportradar",
   scope: "game",
   scopeId: "d8539eb6-3e27-40c8-906f-9cd1736321d8",
   conditions: [
      {
         id: "{uuid}",
         event: "game.home_points",
         type: "set-and-compare",
         compare: "ge",
         target: 30,         
         current: 10,
         log: {
            "event_1-{uuid}": {
               datasource: "sportradar",
               scope: "game",
               scopeId: "d8539eb6-3e27-40c8-906f-9cd1736321d8",
               id: "ab362b76-b89d-4cd8-80e3-340b048f98c8",
               name: "game.home_points",         
               value: 0,
               timestamp: 1686054201993
            },
            "event_2-{uuid}": {
               datasource: "sportradar",
               scope: "game",
               scopeId: "d8539eb6-3e27-40c8-906f-9cd1736321d8",
               id: "ab362b76-b89d-4cd8-80e3-340b048f98c8",
               name: "game.home_points",         
               value: 10,
               timestamp: 1686054201994
            },
         }
      }
   ]
}

When trigger is created, it is stored in collection "triggers" as redis hash.

triggers/{trigger-id as uuid}:   
   id: "{uuid}",
   name: "home points 30+"
   description: "should trigger when home points reach 30 or more"
   datasource: "sportradar"
   scope: "game"
   scopeId: "d8539eb6-3e27-40c8-906f-9cd1736321d8"

Trigger conditions are stored into separate collection "triggers/conditions" as redis hash.

triggers/{trigger-id as uuid}/conditions/{condition-id as uuid}:
   id: "{uuid}"
   event: "game.home_points"
   compare: "ge"
   target: 30
   type: "set-and-compare"
   current: 0

Every event consumed by trigger condition is stored in the log collection as redis hash

triggers/{trigger-id as uuid}/conditions/{condition-id as uuid}/logs:
   "event_id {uuid}": "{ JSON document of the event }"
   ... as many rows as events received while condition is listening...

The list of conditions are linked to a trigger as redis set.

triggers/{trigger-id}/conditions:      
   - "condition-id-1 as {uuid}"
   - "condition-id-2 as {uuid}"
   ...

Trigger is added into event subscribers as redis set.

events/{event-name}/subscribers:      
   - "trigger-id-1 as {uuid}"
   - "trigger-id-2 as {uuid}"
   ...

When trigger is created it can be subscribed. Trigger subscribers are notified upon trigger activation. To create a subscription studio passes subscription object in the request "triggers.subscribe"

interface TriggerSubsription {
   // uuid generated on create
   id: string
   // linked trigger
   triggerId: string  
   // name of destination service, e.g. "polls"
   service: string
   // entity, e.g. "question"
   entity: string
   // entity id
   entityId: string
   route: string
   payload: object
}

Trigger subscription is stored as redis hash.

triggers/{trigger-id as uuid}/subscriptions/{subscription-id as uuid}:
   id: "subscription {uuid}"
   triggerId: "trigger {uuid}"
   service: "polls"
   entity: "question"
   entityId: "123"
   route: "studio.questions.activate"
   payload: "{ JSON of payload object }"

To match subscription to trigger its ID is stored in redis set collection under the trigger-id key.

triggers/{trigger-id as uuid}/subscriptions:
   - "subscription-id-1 as {uuid}"
   - "subscription-id-2 as {uuid}"
   ...

Also, to search for subscriptions by entity, entity_id or service we should use RedisSearch extension.

Event processing

sequenceDiagram
    participant service    
    participant triggers
    participant adapter    
    adapter->>triggers: pushes standard events to  http interface    
    triggers->>triggers: get list of triggers subscribed on standard event
    loop For every trigger
        triggers->>triggers: apply event on the trigger
    end    
    triggers->>triggers: trigger is activated if all conditions activated
    loop For every subscriber
        triggers->>service: send message to "route" { payload }
    end       
    triggers->>triggers: remove activated triggers

diagram description:

  • adapter pushes standard events into http interface of triggers

When event "game.home_points" is received from adapter,

{
   datasource: "sportradar",
   scope: "game",
   scopeId: "d8539eb6-3e27-40c8-906f-9cd1736321d8",
   id: "ab362b76-b89d-4cd8-80e3-340b048f98c8",
   name: "game.home_points",         
   value: 10,
   timestamp: 1686054201993
}

it has a list of ids of subscribed triggers

SMEMBERS events/game.home_points/subscribers

We fetch a trigger by ID and apply event on the trigger. To do so, we proceed with the flow:

  • get a list of conditions for the trigger

    SMEMBERS triggers/{trigger-id}/conditions
  • for each condition one should run lua script according to condition's "type" field, e.g. for { type: "set-and-compare" } run lua script which should:

    • set current value to 10 replacing the old one
    • compare current and target values and store bool result into activated , e.g condition.activated = condition.target >= condition.current
    • put event JSON into log collection
    • return condition state activated in the result

There are as many Lua scripts as conditions types, e.g:

  • set-and-compare: to store current value passed by the event and compare with target value
  • incr-and-compare: to increment some accumulator and compare with target value

Collect results of all conditions inside the trigger.

If all conditions are activated then the trigger becomes activated. And here we should notify all trigger subsribers about it.

we take a list of subsription IDs from

SMEMBERS triggers/{trigger-id as uuid}/subscriptions

get the subscription by ID

HGETALL triggers/{trigger-id as uuid}/subscriptions/{subscription-id as uuid}

e.g.

{   
   id: "{uuid}"   
   triggerId: "{uuid}"   
   service: "polls",   
   entity: "question",   
   entityId: "123",
   route: "studio.questions.activate",
   payload: { id: 123 }
}

and send message to route = "studio.questions.activate" with payload = { id: 123 }

After all subscribers are notified we should do a clean up and remove trigger and related entities.

Triggers API

"triggers.create"

Creates trigger.

interface Trigger {
   // uuid generated for each trigger
   id: string
   // human-readable name just for description purposes
   name: string
   // human-readable name just for description purposes
   description: string
   datasource: "sportradar"
   scope: "game"
   scopeId: string   
   conditions: TriggerCondition[]
}

"triggers.list"

List of triggers by {scope, scopeId} or other fields. RedisSearch is used to match hash fields in trigger object.

"triggers.subscribe"

Subscribes for trigger.

interface TriggerSubsription {
   // uuid generated on create
   id: string
   // linked trigger
   triggerId: string  
   // name of destination service, e.g. "polls"
   service: string
   // entity, e.g. "question"
   entity: string
   // entity id
   entityId: string
   route: string
   payload: object
}

"triggers.subscriptions.list"

Fetches a list of subscriptions by entity, entityId, triggerId or other hash fields.

RedisSearch is used to match hash fields.

1.4.0-rc.11

2 months ago

1.4.0-rc.9

2 months ago

1.4.0-rc.10

2 months ago

1.4.0-rc.8

2 months ago

1.4.0-rc.7

3 months ago

1.4.0-rc.6

3 months ago

1.4.0-rc.2

3 months ago

1.4.0-rc.1

3 months ago

1.4.0-rc.4

3 months ago

1.4.0-rc.3

3 months ago

1.4.0-rc.5

3 months ago

1.3.1-rc.25

3 months ago

1.3.1-rc.24

3 months ago

1.3.1-rc.23

3 months ago

1.3.1-rc.9

6 months ago

1.2.1

7 months ago

1.3.1-rc.6

6 months ago

1.3.1-rc.5

6 months ago

1.3.1-rc.8

6 months ago

1.3.1-rc.7

6 months ago

1.3.1-rc.2

6 months ago

1.3.1-rc.1

6 months ago

1.3.1-rc.4

6 months ago

1.3.1-rc.3

6 months ago

1.3.1-rc.19

5 months ago

1.3.1-rc.18

5 months ago

1.3.1-rc.17

5 months ago

1.3.1-rc.16

5 months ago

1.3.1-rc.15

5 months ago

1.3.1-rc.14

5 months ago

1.3.1-rc.13

6 months ago

1.3.1-rc.12

6 months ago

1.3.1-rc.11

6 months ago

1.3.1-rc.10

6 months ago

1.3.0-rc.2

6 months ago

1.3.0-rc.3

6 months ago

1.3.0-rc.1

7 months ago

1.3.0-rc.6

6 months ago

1.3.0-rc.4

6 months ago

1.3.0-rc.5

6 months ago

1.3.0

6 months ago

1.2.1-rc.5

7 months ago

1.3.1-rc.22

5 months ago

1.3.1-rc.21

5 months ago

1.3.1-rc.20

5 months ago

1.2.0

7 months ago

1.2.0-rc.23

7 months ago

1.2.0-rc.24

7 months ago

1.2.0-rc.22

7 months ago

1.2.1-rc.4

7 months ago

1.2.1-rc.3

7 months ago

1.2.1-rc.2

7 months ago

1.2.1-rc.1

7 months ago

1.2.0-rc.21

8 months ago

1.2.0-rc.20

8 months ago

1.2.0-rc.19

8 months ago

1.2.0-rc.18

8 months ago

1.2.0-rc.17

8 months ago

1.2.0-rc.16

8 months ago

1.2.0-rc.15

8 months ago

1.2.0-rc.14

8 months ago

1.2.0-rc.13

8 months ago

1.2.0-rc.12

8 months ago

1.2.0-rc.11

8 months ago

1.2.0-rc.10

8 months ago

1.2.0-rc.9

8 months ago

1.2.0-rc.8

8 months ago

1.2.0-rc.7

9 months ago

1.2.0-rc.6

9 months ago

1.2.0-rc.5

9 months ago

1.2.0-rc.4

9 months ago

1.2.0-rc.3

9 months ago

1.2.0-rc.2

9 months ago

1.2.0-rc.1

9 months ago

1.1.0

9 months ago

1.1.0-rc.1

9 months ago

1.0.1-rc.1

9 months ago

1.0.0-rc.9

9 months ago

1.0.0-rc.8

9 months ago

1.0.0-rc.7

9 months ago

1.0.0-rc.6

9 months ago

1.0.0-rc.5

9 months ago

1.0.0-rc.4

9 months ago

1.0.0-rc.3

10 months ago

1.0.0-rc.2

10 months ago

1.0.0-rc.1

10 months ago

1.1.0-staging.12

10 months ago

1.1.0-staging.11

10 months ago

1.1.0-staging.10

10 months ago

1.1.0-staging.9

10 months ago

1.1.0-staging.8

10 months ago

1.1.0-staging.7

10 months ago

1.1.0-staging.6

10 months ago

1.1.0-staging.5

10 months ago

1.1.0-staging.4

10 months ago

1.1.0-staging.3

10 months ago

1.1.0-staging.2

10 months ago

1.1.0-staging.1

10 months ago

1.0.0

10 months ago