# Events

## Event Object

Events are non-extensible, script-only accessible un-managed objects that can trigger events and notifications delayed or on a recurring schedule.

When fired, events move into the `processing` state, and then into the `completed` or `failed` state once handled.

## Object Properties

* `count` { Number *readOnly* } If recurring, this property will hold the number of times the event was triggered, whether manually or through scheduling.
* `err` { Any *readOnly* } A fault that is recorded if the event handler failed for any reason, threw an error, etc.
* `expiresAt` { Date *indexed*, *removable* } If set, the event will not trigger once expired and will eventually be garbage collected and deleted by cortex.
* `if` { Expression *removable* } An optional expression that triggers the event if it evaluates to a *truey* value (`!!if()`).
* `key` { String *indexed*, *unique*, *removable* } An optional unique key up to 512 characters to uniquely identify the event.
* `parent` { ObjectID *readOnly* } Set to the `_id` of a triggering recurring event.
* `parentKey` { String *readOnly*, *indexed* } Set to the `key` of a triggering recurring event.
* `principal` { ObjectID } An account or service account used as the calling principal. Can be set using keys and email addresses.
* `retention` { Number = *never* } The retention policy bits. Only affect non-recurring events and children of recurring events.
  * never = 0 - delete the event after it triggers.
  * failed = 1 - retain events that fail.
  * completed = 2 - retain events that complete.
  * skipped = 4 - retain events that were skipped via conditional.         &#x20;
* `schedule` { String } A cron format recurrence schedule. Once set, the `state` is set to `scheduled` and the `start` property is set to the next time in the schedule. Recurring events cannot be changed to non-recurring events and vice versa.
* `start` { Date = *new Date()* } The date to trigger the event.
* `started` { Date *readOnly* } The date the event processing was started (or last started int he case of a recurring event).
* `state` { Number *indexed*, *readOnly* } The current state
  * scheduled = 0 - event is recurring.
  * pending = 1 - event is scheduled to run.
  * processing = 2 - event is currently processing.
  * completed = 3 - event triggered successfully.
  * failed = 4 - an error occurred during processing and was stored in `err`.&#x20;
  * skipped = 5 - skipped because a condition was not met.

## Object Types

### Script

A `script` event trigger an `@on` runtime event based on the `event` and `param` properties, mirroring the `script.fire()` functionality. The `param` value becomes the `script.arguments` and is also passed as the first argument to the event handler.

* `event` { String } The event name that matches a registered runtime event (`@on`).&#x20;
* `param` { Any } An object passed to the event handler as the first argument.&#x20;

```javascript
const { on } = require('decorators')                          

class Events {           

  @on('ns__test')
  static nsTest(param, runtime) {
    // runtime.source === 'event' when fried from an event instance
  }
}
```

### Notification

A `notification` event sends a notification based on the `name`, `variables` and `options` properties, mirroring the `require('notifications').send(...)` script functionality.

Because notifications can have multiple endpoints, if more than one error is produced, any additional endpoint errors are added as child faults to the first error.

* `name` { String } Notification name. Can be null or missing for custom notifications.&#x20;
* `variables` { Any } Template variables.
* `options` { Any } Notification options.&#x20;

### Driver

A `driver` event runs a db operation based on the `options` and `privileged` properties, mirroring the `require('db.util').createOperation(options, [options]).execute()` script functionality.

Supported operations are: `deleteMany`, `deleteOne`, `insertMany`, `insertOne`, `patchMany`, `patchOne`, `updateMany`, `updateOne`.

* `options` { Object } Operation options from `operation.getOptions()`.&#x20;
* `privileged` { Boolean = *false* } If true, privileged options (those only available in-script)

  will be included (skipAcl, roles, grant, bypassCreateAcl).

### Console

For testing purposes, a `console` event outputs `param` as if `console.log(param)` were called from a script. Console logging is not available in production environments.

* `param` { Any } Max 8KB message to be output.&#x20;

## Constants

```javascript
return consts.events

/*
{
  "retention": {
    "never": 0,
    "failed": 1,
    "completed": 2,        
    "skipped": 4
  },
  "states": {
    "scheduled": 0,    
    "pending": 1,
    "processing": 2,
    "completed": 3,
    "failed": 4,
    "skipped": 5
    }
  }
*/
```

## Scheduling

To schedule a user event, simply insert an event instance.

When a recurring event - one with a `schedule` cron - is triggered, it will insert a new event with a `pending` state with a `start` date set to the current time, and tick up the `count` property of the parent.

## Error Handling

A `err.events.failed` trigger is called when an event fails. The trigger `context` is the event instance, with an additional `params` (script.arguments) object containing the `err` object.

```javascript
const { trigger } = require('decorators')

class ErrorHandling {

  @trigger('err.events.failed')
  handleError({ context, params: { err }}) {

  }

}
```

## Limits

* `configuration.events.softLimit` ( Number = *80,000*, *readOnly* ) The number of pending events that can be inserted before the `err.events.softLimitExceeded` trigger is fired. When fired, `configuration.events.triggerSoftLimit` is set to `false` and will not trigger again until reset.
* `configuration.events.hardLimit` ( Number = *100,000*, *readOnly* ) The number of pending events that can be inserted before the `err.events.hardLimitExceeded` trigger is fired. When fired, `configuration.events.triggerHardLimit` is set to `false` and will not trigger again until reset. When this threshold is reached, a `cortex.invalidArgument.maxAllowed` Fault is thrown.
* `configuration.events.triggerSoftLimit` ( Boolean = *true* ) Initially true, this is set to false if the soft limit is triggered and a script trigger exists in the environment runtime. The trigger will not fire again until the property is reset to `true`. The trigger is *always* fired inline.
* `configuration.events.triggerHardLimit` ( Boolean = *true* ) Initially true, this is set to false if the hard limit is triggered and a script trigger exists in the environment runtime. The trigger will not fire again until the property is reset to `true`. The trigger is *always* fired inline.

```javascript
// listen for limit triggers

const { trigger } = require('decorators')

class Limits {

  @trigger('err.events.softLimitExceeded')
  onSoftLimit() {
    // time to take a look.
  }

  @trigger('err.events.hardLimitExceeded')
  onHardLimit() {
    // this is an error. events won't insert or recur.    
  }
}
```

## Examples

```javascript
const { Event } = org.objects,
      { events: { retention: { failed, skipped } } } = consts

// insert a script event to trigger as soon as possible. and retain only if failed or skipped.
Event.insertOne(
  {
    type: 'script',  
    key: 'unique.key',    
    event: 'c_room_room_event',  
    param: {
      anything: 'up to 16kb'
    },
    if: {
      $eq: ['principal._id', script.principal._id]
    },
    retention: failed | skipped
  }
).grant('update').bypassCreateAcl().execute()

// insert a script event to trigger in 24 hours
Event.insertOne(
  {
    type: 'script',  
    key: 'unique.key',    
    event: 'c_room_room_event',  
    param: {
      anything: 'up to 16kb'
    },
    start: new Date(Date.now() + 86400000)
  }
).grant('update').bypassCreateAcl().execute()

// insert a custom notification to be sent once a day at 5am UTC, running as the current principal.
Event.insertOne(
  {
    type: 'notification',
    key: 'scheduled.notif.123',
    principal: script.principal,
    schedule: '0 5 * * *',
    options: {
      endpoints: {
        email: {
          recipients: [          
            'test@example.org',
            ],
          subject: 'Medable',
          message: 'Medable rocks!', // message (plain text) is currently required.
          html: '<html><p>Medable&nbsp;rocks&#33;<p></html>'
        }
      }
    }
  }
).grant('update').bypassCreateAcl().execute()     

// insert a console logger that will run every minute and expire in 5 minutes.
// then, force it to run immediately after scheduling (any recurring event can be 
// set to run manually, but this resets the next time it will run).
Event.insertOne(
  {      
    type: 'console',       
    param: 'foo',
    key: 'scheduled will expire in 5 minutes',
    schedule: '* * * * *',
    expiresAt: new Date(Date.now() + (1000 * 60 * 5))
  }
).grant('update').bypassCreateAcl().execute()


Event.updateOne(
  {
    key: 'scheduled will expire in 5 minutes'
  },
  {
    $set: {
      start: new Date()
    }
  }
).skipAcl().grant('update').execute()
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.medable.com/cortex-api/objects/events.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
