Overview of Pulse event delivery
@orion-js/pulse and can be used in Orionjs services or standalone Node.js/Bun processes. Unlike Kafka, it is not protocol-compatible and does not provide partitions or a globally ordered log.website·Indexed 31 days ago
https://orionjs.com/A framework for building type-safe APIs with integrated support for GraphQL, tRPC, and MongoDB. Features include a decorator-based approach to controllers, repositories, and schemas, as well as specialized tools like Echoes for event-driven microservices, Dogs for background job management, and Pulse for event processing. Supports deployment with Bun and Node.js.
@orion-js/pulse and can be used in Orionjs services or standalone Node.js/Bun processes. Unlike Kafka, it is not protocol-compatible and does not provide partitions or a globally ordered log.--node flag.app/ directory houses the core logic. The app/ directory is divided into config/ for application-wide settings, components/ for domain-specific modules, and an index.ts entry point./
├── app/
│ ├── config/ # Application configuration
│ ├── components/ # Application components
│ ├── env.d.ts # Environment file
│ └── index.ts # Application entry point
├── node_modules/
├── .env.local.yml # Local environment variables
├── biome.json # Linting/formatting
├── package.json # Dependencies and scripts
└── tsconfig.json # TypeScript configurationOrionjs provides automatic schema validation for specific integration points to ensure data integrity without manual calls. Validation occurs automatically in the following scenarios:
Orionjs uses a modular architecture based on the following building blocks:
resolve function of a job receives a second argument, the context object, which provides metadata about the current execution, including the jobId, the number of tries (attempts), and the createdAt timestamp.@RecurrentJob()
exampleJob = createRecurrentJob({
runEvery: '1h',
resolve: async (_, context) => {
console.log('Job ID:', context.jobId)
console.log('Execution attempt:', context.tries)
console.log('Scheduled at:', context.createdAt)
}
})
Follow these conventions for resolver structure and naming to maintain a clean project architecture:
Naming Conventions:
{Action}{Entity}Resolvers (e.g., GetUserResolvers, UpdateProductResolvers).createUser, listProducts).user, blogPosts).File Structure:
Place resolvers in app/{component}/controllers/resolvers/{Entity}/{Action}/index.ts.
Exporting:
Create a central controllers/resolvers/index.ts file that exports all resolver classes as a single array.
// controllers/resolvers/index.ts
import {GetUserResolvers} from './users/GetUser'
import {CreateUserResolvers} from './users/CreateUser'
export default [
GetUserResolvers,
CreateUserResolvers
]Starting with version @orion-js/dogs@4.6.2, the scheduler automatically chooses between two MongoDB indexes to optimize job acquisition based on the specific workload of the startWorkers() instance. This is particularly useful when some servers handle a small subset of job names (benefiting from a jobName-led index) while others handle most jobs (benefiting from a priority-led index).
Index Options:
{jobName: 1, priority: -1, nextRunAt: 1} (Default/Initial hint){priority: -1, nextRunAt: 1} (Alternative hint)Behavior:
explain('executionStats') to compare the median executionTimeMillis.maxParallelExecutionsPerServer filters out some job names, the system temporarily overrides the selection and uses the jobName-leading index for that specific claim.// The two indexes declared on orionjs.jobs_dogs_records
{jobName: 1, priority: -1, nextRunAt: 1}
{priority: -1, nextRunAt: 1}viewer object as the second parameter. This object contains information about the current user and session, which is essential for authorization and user-specific data fetching.@Query()
userProfile = createQuery({
returns: UserProfile,
resolve: async (_, viewer) => {
if (!viewer.user) {
throw new Error('Unauthorized')
}
return await this.userService.getProfile(viewer.user._id)
}
})next() to proceed to the route handler or return an error response to block access.// Auth middleware
const authMiddleware = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
return res.status(401).json({error: 'No token provided'})
}
try {
const user = await verifyToken(token)
req.user = user // Attach user to request
next()
} catch (error) {
return res.status(401).json({error: 'Invalid token'})
}
}
@Routes()
export class UserRoutes {
@Inject(() => UserService)
private userService: UserService
@Route()
getProfile = createRoute({
path: '/profile',
method: 'get',
middlewares: [authMiddleware],
resolve: async (req) => {
// req.user is guaranteed to exist from the authMiddleware
return {
body: await this.userService.getUserProfile(req.user._id)
}
}
})
}