Skip to main content

Bob Wrapper

What is the Bob Wrapper?

The Bob Wrapper is a higher-order function pattern used to encapsulate business logic within the Bob Event Engine. It provides a standardized way to:

  1. Inject Dependencies: It passes a BobRequest object (bob) containing all necessary context (user session, database access, utilities) to your logic.
  2. Standardize Error Handling: It ensures every action utilizes a consistent try/catch block and returns a standardized result object.
  3. Ensure Consistency: By enforcing a strict structure, it makes code readable, maintainable, and predictable across the entire codebase.

Essentially, "wrapping" your logic means ensuring it "speaks the same language" as the rest of the system.


How to Create a New Event Hook

Follow these steps to create a new event hook using the Bob Wrapper.

1. Define the Class with @LogicHook Decorator

Create a class decorated with @LogicHook (following Naming Conventions):

import { BobRequest, returnEventResult } from '@logic-bee/event-engine'
import { NaoQueryOptions } from '@logic-bee/flow-query'
import { LogicHook } from '@logic-bee/logic-hooks'

@LogicHook({
name: 'My Action Name',
slug: 'my-action-name',
path: 'my-collection/my-action-name',
hookCollection: 'my-collection',
version: 1,
concurrency: 'parallel',
timeout: 30000,
idempotent: false,
pattern: 'hook',
patternScore: 0
})
export class MyCollectionMyActionName {
public static execute() {
// ...
}
}

2. Return the Async Wrapper

The execute() method must return an async function that accepts bob as an argument.

return async (bob: BobRequest<MyDataType>) => {
// ...
}

3. Initialize Standard Variables

Inside the wrapper, initialize ok, error, and data variables.

let ok = true,
error: any = null,
data

4. Implement the Try/Catch Block

Wrap all logic in a try/catch block.

try {
/**START_CODE_LOGIC*/

// ... your logic here

/**END_CODE_LOGIC*/
} catch (err) {
error = err
ok = false
}

5. Return the Result

Finally, return the result using returnEventResult.

return returnEventResult(bob, { ok, error, data })

The BobRequest API

The BobRequest object (commonly named bob) is your gateway to the system.

Key Properties & Methods

Property/MethodDescription
bob.dataPayloadAccess the input data passed to the event.
bob.flowUserThe current user's session and context.
bob.flowGlobalGlobal access to FlowCollections for database operations.
bob.dbSession()Returns the current database transaction session.
bob.validator.joi()Access the Joi validation library for input validation.
bob.executeRequestOrThrow()Execute another internal action or CFP query.
bob.replaceDataPayload()Update the data payload for chain processing.
bob.aiAnalytics()Access AI analytics for document summarization and analysis.

returnEventResult

A helper function that formats the response.

  • Input: bob, { ok: boolean, error: any, data: any }
  • Output: A standardized response object expected by the Event Engine.

Examples

1. Basic "Hello World" Hook

A simple hook that returns a static string.

import { BobRequest, returnEventResult } from '@logic-bee/event-engine'
import { LogicHook } from '@logic-bee/logic-hooks'

@LogicHook({
name: 'Say Hello',
slug: 'say-hello',
path: 'my-collection/say-hello',
hookCollection: 'my-collection',
version: 1,
concurrency: 'parallel',
timeout: 30000,
idempotent: false,
pattern: 'hook',
patternScore: 0
})
export class MyCollectionSayHello {
public static execute() {
return async (bob: BobRequest<any>) => {
let ok = true, error: any = null, data
try {
/**START_CODE_LOGIC*/

ok = true
data = { message: "Hello from Bob!" }

/**END_CODE_LOGIC*/
} catch (err) {
error = err
ok = false
}
return returnEventResult(bob, { ok, error, data })
}
}
}

2. Input Validation

A hook that validates input data using Joi.

import { BobRequest, returnEventResult } from '@logic-bee/event-engine'
import { NaoQueryOptions } from '@logic-bee/flow-query'
import { SuperJoi } from '@logic-bee/utils'
import { LogicHook } from '@logic-bee/logic-hooks'

@LogicHook({
name: 'Create Item',
slug: 'create-item',
path: 'inventory/create-item',
hookCollection: 'inventory',
version: 1,
concurrency: 'parallel',
timeout: 30000,
idempotent: false,
pattern: 'hook',
patternScore: 0
})
export class InventoryCreateItem {
public static execute() {
return async (bob: BobRequest<any>) => {
let ok = true, error: any = null, data
try {
/**START_CODE_LOGIC*/

// -->Validate: input data
bob.validator.joi().validateData(
bob.dataPayload,
SuperJoi.object({
data: SuperJoi.object({
name: SuperJoi.string().required(),
quantity: SuperJoi.number().min(1).required()
})
}),
bob.flowUser.getUserInfo()
)

// Logic continues only if validation passes...
ok = true
data = { created: true }

/**END_CODE_LOGIC*/
} catch (err) {
error = err
ok = false
}
return returnEventResult(bob, { ok, error, data })
}
}
}

3. Complex Business Logic & Database Query

A hook that queries multiple collections and performs business logic (e.g., aggregating costs).

import { BobRequest, returnEventResult } from '@logic-bee/event-engine'
import { NaoQueryOptions } from '@logic-bee/flow-query'
import { naoFormatErrorById, naoUtils } from '@logic-bee/utils'
import { LogicHook } from '@logic-bee/logic-hooks'

@LogicHook({
name: 'Calculate Total Cost',
slug: 'calculate-total-cost',
path: 'sales/calculate-total-cost',
hookCollection: 'sales',
version: 1,
concurrency: 'parallel',
timeout: 30000,
idempotent: false,
pattern: 'hook',
patternScore: 0
})
export class SalesCalculateTotalCost {
public static execute() {
return async (bob: BobRequest<any>) => {
let ok = true, error: any = null, data
try {
/**START_CODE_LOGIC*/
const eventOptions = {
orderQuery: { docName: 'order', cfpPath: 'sales/orders' },
itemQuery: { docName: 'item', cfpPath: 'inventory/items' }
}

// -->Get: flow collections
const orderCollection = bob.flowGlobal.getFlowCollection(eventOptions.orderQuery)

// -->Get: the order document
const order = await orderCollection.docs.flowQuery()
.user(bob.flowUser)
.flowOptions(eventOptions.orderQuery)
.docId(bob.dataPayload.docId)
.getOne()

if (!order) {
throw naoFormatErrorById('bad_request', { reason: 'Order not found' })
}

let totalCost = 0

// -->Iterate: order lines to calculate cost
for (const line of order.data.lines) {
totalCost += (line.quantity * line.unitCost)
}

// -->Update: the order with new total
await orderCollection.docs.flowQuery()
.user(bob.flowUser)
.flowOptions(eventOptions.orderQuery)
.docId(order.docId)
.updateOne({ 'data.totalCost': totalCost }, bob.dbSession())

ok = true
data = { docIds: [order.docId], totalCost }

/**END_CODE_LOGIC*/
} catch (err) {
error = err
ok = false
}
return returnEventResult(bob, { ok, error, data })
}
}
}

4. Inter-Action Communication (Calling Other Hooks)

How to call another action from within a hook using bob.runAction.

import { BobRequest, returnEventResult } from '@logic-bee/event-engine'
import { LogicHook } from '@logic-bee/logic-hooks'

@LogicHook({
name: 'Process Order',
slug: 'process-order',
path: 'sales/process-order',
hookCollection: 'sales',
version: 1,
concurrency: 'parallel',
timeout: 30000,
idempotent: false,
pattern: 'hook',
patternScore: 0
})
export class SalesProcessOrder {
public static execute() {
return async (bob: BobRequest<any>) => {
let ok = true, error: any = null, data
try {
/**START_CODE_LOGIC*/

// 1. First, validate the inventory (Internal Action)
const validationResult = await bob.runAction('inventory.validate-stock', {
data: { ...bob.dataPayload }
})

// Check if the sub-action failed
if (!validationResult.ok) {
throw validationResult.error || new Error('Inventory validation failed')
}

// 2. If valid, process payment (External Action via CFP)
const paymentResult = await bob.executeRequestOrThrow({
cfpPath: 'finance/payments.process-transaction',
type: 'cfp'
}, {
amount: bob.dataPayload.data.totalAmount,
currency: 'USD'
})

ok = true
data = {
inventory: validationResult.data,
payment: paymentResult.data
}

/**END_CODE_LOGIC*/
} catch (err) {
error = err
ok = false
}
return returnEventResult(bob, { ok, error, data })
}
}
}

Error Handling

The Bob Wrapper relies on the standard naoFormatErrorById utility for creating user-friendly errors.

Throwing Errors

Do not throw raw Error objects if you want to present clean messages to the user.

❌ DON'T:

throw new Error("Item not found") // Returns generic 500 or ugly stack trace

✅ DO:

import { naoFormatErrorById } from '@logic-bee/utils'

throw naoFormatErrorById('bad_request', {
reason: 'Item ABC-123 was not found in the active warehouse.'
})

Common Error Types

Error TypeUse Case
bad_requestValidation failures, missing data, business logic violations
not_foundDocument lookups returning null
unauthorizedPermission issues (though usually handled by policies)

By following this pattern, the catch block in the wrapper will automatically capture the formatted error and pass it to the frontend or API response correctly.