UNPKG

@servicenow/sdk

Version:
264 lines (206 loc) 10.8 kB
--- tags: [events, event-registry, sysevent, gs-eventQueue, custom-queue, event-driven, script-action, email-notification, flow-designer] --- # Registering Events Guide Guide for registering custom ServiceNow events in the Event Registry (`sysevent_register`) using the Record API, including custom queue configuration for high-volume event processing. Use when a custom event name is referenced, when building event-driven applications, or when the user mentions event registry, custom events, `gs.eventQueue()`, or custom queues. ## When to Use - Use proactively as a prerequisite step whenever a custom event name is referenced. The event must be registered before it can be fired or consumed. - When building event-driven applications that need to fire or consume custom events. - When the user mentions "event registry", "register event", "custom event", `gs.eventQueue()`, or `sysevent_register`. - When the user mentions "custom queue", "event queue", or high-volume event processing. ### When to Use Event-Based Approach | Scenario | Use Event-Based? | |----------|-----------------| | Single reaction to a record change | No -- direct action is simpler | | One trigger, multiple independent reactions | Yes | | Passing contextual data to downstream consumers | Yes | | Asynchronous processing that shouldn't block the user | Yes | ## Instructions 1. **Use the Record API**: There is no dedicated Fluent plugin for `sysevent_register`. Always use `Record()` with `table: 'sysevent_register'`. 2. **Scope-aware field usage**: - **Global apps**: Set `event_name` directly. Leave `suffix` blank. - **Scoped apps**: Set **both** `suffix` AND `event_name: '<scope>.<suffix>'` explicitly. The platform does not auto-generate `event_name` on initial Record API creation. 3. **CRITICAL -- 40-character limit on `event_name`**: Values longer than 40 characters are silently truncated. Always verify `<scope>.<suffix>` fits within 40 characters. 4. **Derive `$id` from the event name**: Use a stable `$id` that maps 1-to-1 with the event name to prevent duplicate records on rebuild. 5. **Never set `derived_priority`**: It is read-only and auto-computed. 6. **Register before firing**: The event must exist before any code calls `gs.eventQueue()` or fires from Flow Designer. 7. **Always populate `fired_by`**: Document what fires the event for maintainability. 8. **Always export the Record when used with flows**: So flows can import and reference `myEvent.$id`. 9. **Use custom queues for high-volume events**: Prevents flooding the default processor and allows serial processing to avoid race conditions. ## Avoidance - **Never omit `event_name` in scoped apps** -- always set it explicitly as `<scope>.<suffix>` alongside `suffix`. - **Never omit `suffix` in scoped apps** -- it is mandatory alongside `event_name`. - **Never modify `suffix` on existing scoped events** -- changing it regenerates `event_name` and breaks all listeners. - **Never exceed 40 characters for `event_name`** -- silently truncated with no warning. - **Never use two different `$id` values for the same `event_name`** -- creates duplicate registrations causing listeners to fire twice. - **Never fire an event before registering it** -- fails silently in scoped apps. - **Never leave `fired_by` blank**. - **Never pass priority as a String** -- the field expects an integer. - **Never forget to export the event Record when referenced by a flow**. --- ## Event Registry API Reference Register events using `Record()` with `table: 'sysevent_register'`. ### Data Fields | Name | Type | Mandatory | Description | |------|------|-----------|-------------| | `event_name` | String (max 40) | -- | Primary identifier. Scoped: set as `<scope>.<suffix>`. Global: set directly. | | `suffix` | String (max 40) | Yes (scoped) | Mandatory in scoped apps alongside `event_name`. Not used in global apps. | | `description` | String (max 100) | -- | When and why the event fires. | | `table` | String (max 80) | -- | Associated ServiceNow table. | | `fired_by` | String (max 100) | -- | What code fires the event (required for traceability). | | `priority` | integer | -- | Processing order (lower = higher priority). Default: `100`. | | `queue` | String | -- | Custom queue name. Must match an existing `sysevent_queue` record. | | `caller_access` | String (choice) | -- | Cross-scope access: `''` (none), `'1'` (tracking), `'2'` (restriction). | | `derived_priority` | Float | -- | **READ-ONLY.** Never set this field. | ### Caller Access Values | Value | UI Label | Behavior | |-------|----------|----------| | `''` (default) | None | No restriction. Any caller can fire the event. | | `'1'` | Caller Tracking | Cross-scope allowed but logged. | | `'2'` | Caller Restriction | Cross-scope blocked by default; admin approval required. | --- ## Examples ### Scoped App Event Registration ```typescript fluent import { Record } from '@servicenow/sdk/core'; Record({ $id: Now.ID['x-myapp-incident-approved-event'], table: 'sysevent_register', data: { suffix: 'incident.approved', event_name: 'x_myapp.incident.approved', description: 'Fired when an incident is approved in My App', table: 'incident', fired_by: 'Business Rule: My App Incident Approvals', priority: 200, }, }); ``` ### Global App Event Registration ```typescript fluent import { Record } from '@servicenow/sdk/core'; Record({ $id: Now.ID['incident-approved-event'], table: 'sysevent_register', data: { event_name: 'incident.approved', description: 'Fired when an incident is approved', table: 'incident', fired_by: 'Business Rule: Incident Approvals', priority: 100, }, }); ``` ### Event with Caller Tracking ```typescript fluent import { Record } from '@servicenow/sdk/core'; Record({ $id: Now.ID['x-myapp-contract-expiring-event'], table: 'sysevent_register', data: { suffix: 'contract.expiring', event_name: 'x_myapp.contract.expiring', description: 'Fired when a contract is within 30 days of expiry', table: 'x_myapp_contract', fired_by: 'Business Rule: Contract Expiry Check', priority: 100, caller_access: '1', }, }); ``` ### Event-Driven Flow with Email Notification Three-file pattern: Event Registration, Flow (`fireEvent`), Email Notification. **File 1**: Register and export the event: ```typescript fluent import { Record } from '@servicenow/sdk/core'; export const employeeTerminatedEvent = Record({ $id: Now.ID['sn-myapp-employee-terminated-event'], table: 'sysevent_register', data: { suffix: 'employee.terminated', event_name: 'sn_myapp.employee.terminated', description: 'Fired when employee status changes to Terminated', table: 'sn_myapp_employee_record', fired_by: 'Flow: Notify manager on termination', priority: 100, }, }); ``` **File 2**: Flow fires the event using `employeeTerminatedEvent.$id` (resolves to sys_id). **File 3**: Notification references event by name string `'sn_myapp.employee.terminated'`. > **Critical difference**: The Flow uses `.$id` (sys_id reference). The Notification uses the event name string. Mixing them up causes errors. ### Event-Driven Business Rule with Script Action Four-file pattern: Event Registration, Business Rule (`gs.eventQueue`), ScriptAction, Email Notification. The business rule fires via `gs.eventQueue('sn_myapp.new.employee.added', current, parm1, parm2)`. The ScriptAction and EmailNotification both reference the full event name string in their `eventName` fields. --- ## Custom Queue Registration Use a custom queue when your app generates high volumes of events or when events must be processed serially. ### When to Use a Custom Queue | Scenario | Recommendation | |----------|---------------| | Low/moderate frequency (tens per hour) | Default queue is sufficient | | High volume (hundreds/thousands per batch) | Custom queue to isolate traffic | | Events that must not race each other | Custom queue with sequential mode | | Isolation from other apps' event traffic | Custom queue | ### Queue Naming Convention **Scoped apps**: `<scope_name>.<suffix>` (suffix is mandatory). **Global apps**: Plain unique name (no suffix field). **CRITICAL**: Queue names must be unique. Always query `sysevent_queue` before creating. ### Custom Queue Properties | Field | Type | Mandatory | Description | |-------|------|-----------|-------------| | `sys_class_name` | string | **Yes** | Must be `"sysevent_queue"` for correct installation. | | `queue` | string | Yes | Full queue name. | | `suffix` | string | Scoped: Yes | Portion after scope prefix. | | `poll_interval` | glide_duration | Yes | Format: `"1970-01-01 HH:mm:ss"`. | | `job_config` | string | No | `"jobs_per_node"` or `"job_count"`. | | `job_config_value` | integer | Yes | Number of concurrent jobs. | | `provider` | reference | Yes | sys_id of `sysevent_queue_provider` record. | | `processing_order` | string | No | `"parallel"` or `"sequential"`. | ### Available Providers | Provider | sys_id | Description | |----------|--------|-------------| | Event Provider | `44af4464431212108da9a574a9b8f2f5` | Default; uses Processing Framework | | NowMQ Provider | `3ccf4464431212108da9a574a9b8f2fb` | In-memory queue for high throughput | ### Custom Queue Example ```typescript fluent import { Record } from '@servicenow/sdk/core'; // Step 1: Create the queue Record({ $id: Now.ID['my_app_custom_queue'], table: 'sysevent_queue', data: { sys_class_name: 'sysevent_queue', queue: 'sn_my_app.custom_queue', suffix: 'custom_queue', description: 'General-purpose event queue for my application.', poll_interval: '1970-01-01 00:00:30', job_config: 'jobs_per_node', job_config_value: 1, provider: '44af4464431212108da9a574a9b8f2f5', automatic_processing: true, processing_order: 'parallel', }, }); // Step 2: Register the event with the queue name Record({ $id: Now.ID['sn_my_app-contract-expiring-event'], table: 'sysevent_register', data: { suffix: 'contract.expiring', event_name: 'sn_my_app.contract.expiring', description: 'Fired when a contract is within 30 days of expiry', table: 'sn_my_app_contract', fired_by: 'Business Rule: Contract Expiry Check', priority: 100, queue: 'sn_my_app.custom_queue', }, }); ``` ### Troubleshooting | Symptom | Cause | Fix | |---------|-------|-----| | Install succeeds but queue record missing | Missing `sys_class_name` | Add `sys_class_name: 'sysevent_queue'` | | Events not routing to custom queue | Queue name mismatch | Verify exact name matches | | Duplicate queue name conflict | Name already exists | Change suffix (scoped) or queue name (global) | | "Event is not defined" errors | `event_name` exceeded 40 chars | Shorten `<scope>.<suffix>` to fit within 40 |