Creating Your Own Event Context (JOB Event)
Let’s walk through a simplified, step-by-step example of creating a custom event context for a fictional "JOB" event. We’ll show how to:
- Define event and store interfaces
- Create the event context initialization and usage functions
- Write composables using the
store
APIs (init
,get
,set
) to manage typed data
This example will help you understand how to build your own event adapters on top of @wooksjs/event-core
.
1. Define the Event and Store Interfaces
First, we describe the event and context store structures. The event will represent a job being processed, and the store will hold job-related state such as job status, results, and metadata.
import type { TGenericEvent } from '@wooksjs/event-core'
/**
* Event data for the JOB event.
* - `type: 'JOB'` identifies the event type.
* - `jobId` is a unique identifier for the job.
* - `input` is the data this job should process.
*/
interface TJobEventData extends TGenericEvent {
type: 'JOB'
jobId: string
input: unknown
}
/**
* Store interface for JOB event.
* We'll keep a `job` object where we store details about the job:
* - `status` (e.g., 'pending', 'running', 'completed')
* - `result` to store the output of the job
* - `metadata` as a nested object containing arbitrary details
*/
interface TJobContextStore {
job?: {
status?: 'pending' | 'running' | 'completed'
result?: unknown
metadata?: Record<string, string | number>
}
}
We defined TJobEventData
and TJobContextStore
. Note that job
is an object, allowing us to further manage fields inside it using init
, get
, set
on store('job')
.
2. Create Context Creation and Usage Functions
Next, we create a function to initialize the job context and a helper function to access it.
import { createAsyncEventContext, useAsyncEventContext, TEventOptions } from '@wooksjs/event-core'
/**
* Creates a new JOB event context. Call this when a new job event arrives.
* @param data The event data (jobId and input)
* @param options Event-level configuration (e.g., logging)
*/
function createJobContext(data: Omit<TJobEventData, 'type'>, options: TEventOptions) {
return createAsyncEventContext<TJobContextStore, TJobEventData>({
event: {
...data,
type: 'JOB',
},
options,
})
}
/**
* Accesses the current JOB event context.
* Throws an error if the current context is not a JOB event.
*/
function useJobContext() {
return useAsyncEventContext<TJobContextStore, TJobEventData>('JOB')
}
Now we have a createJobContext()
function to spin up a context when a job starts and useJobContext()
to retrieve it in any composable or handler.
Usage Example:
const runInContext = createJobContext({ jobId: 'abc123', input: { foo: 'bar' } }, {})
runInContext(() => {
// Inside this callback, we have access to the JOB context
})
3. Create Composables Using the store
API
With useJobContext()
, we can now create composables that manipulate the store. Since job
is an object, we will use init
, get
, and set
to manage its fields.
Composable: Managing Job Status
function useJobStatus() {
const { store } = useJobContext()
const jobStore = store('job')
/**
* Initialize the job status if not defined. This ensures we don't run expensive logic twice.
* Here, we just default it to 'pending' if not set.
*/
function getStatus() {
return jobStore.init('status', () => 'pending')
}
function setStatus(status: 'pending' | 'running' | 'completed') {
jobStore.set('status', status)
}
return { getStatus, setStatus }
}
getStatus()
usesinit('status', ...)
to set the status to 'pending' if it’s not already defined.setStatus()
usesset('status', ...)
to update the status.
Composable: Storing and Retrieving Job Results
We can store the job result once the processing is done:
function useJobResult() {
const { store } = useJobContext()
const jobStore = store('job')
function getResult() {
return jobStore.get('result')
}
function setResult(res: unknown) {
jobStore.set('result', res)
}
return { getResult, setResult }
}
getResult()
simply retrieves the currentresult
without initializing anything.setResult()
saves the result.
Composable: Managing Metadata
We can store arbitrary key-value pairs in metadata
:
function useJobMetadata() {
const { store } = useJobContext()
const jobStore = store('job')
/**
* Initialize metadata as an empty object if not defined.
*/
function ensureMetadata() {
return jobStore.init('metadata', () => ({} as Record<string, string | number>))
}
function getMetadata(key: string) {
const metadata = ensureMetadata()
return metadata[key]
}
function setMetadata(key: string, value: string | number) {
const metadata = ensureMetadata()
metadata[key] = value
jobStore.set('metadata', metadata)
}
return { getMetadata, setMetadata }
}
ensureMetadata()
ensuresmetadata
is initialized once.getMetadata(key)
andsetMetadata(key, value)
manipulate fields inmetadata
.
Bringing It All Together
Imagine we receive a JOB event:
const runInContext = createJobContext({ jobId: 'abc123', input: { foo: 'bar' } }, {})
runInContext(() => {
const { getStatus, setStatus } = useJobStatus()
const { setResult } = useJobResult()
const { setMetadata, getMetadata } = useJobMetadata()
console.log(getStatus()) // 'pending' (initialized just now)
setStatus('running')
console.log(getStatus()) // 'running'
// After processing the job:
setResult({ success: true })
setStatus('completed')
setMetadata('processedAt', Date.now())
console.log(getMetadata('processedAt')) // Some timestamp
})
Here, we’ve demonstrated:
- Typing the event and store: We explicitly defined
TJobEventData
andTJobContextStore
. - Creating context functions:
createJobContext()
anduseJobContext()
ensure type-safe and context-specific operations. - Using
init
,get
,set
: We lazily initialize values (likestatus
,metadata
), retrieve them, and update them as the job progresses.
This pattern can be applied to any type of event in your application.