This directory contains comprehensive, production-ready examples of ObjectQL hooks and actions that follow the specification in /docs/spec/hook.md and /docs/spec/action.md.
projects.hook.ts- Comprehensive hook implementations demonstrating all 8 hook typesprojects.action.ts- Comprehensive action implementations (record and global actions)projects.object.yml- Updated object definition with all action declarations__tests__/projects-hooks-actions.test.ts- Complete test suite validating all patterns
- ✅ Auto-assign owner from user context
- ✅ Set default values (status, budget)
- ✅ Validate required fields
- ✅ Validate field constraints (length, format)
- ✅ Check for duplicates using API
- ✅ Business rule validation
- ✅ Logging and audit trails
- ✅ Creating related records
- ✅ Sending notifications
- ✅ Trigger external workflows
- ✅ Row-level security (multi-tenancy filtering)
- ✅ Query modification
- ✅ Default sort application
- ✅ Access control based on user roles
- ✅ Adding computed fields
- ✅ Data transformation
- ✅ Enriching results
- ✅ Masking sensitive data
- ✅ State machine validation
- ✅ State transition rules
- ✅ Budget constraint checking
- ✅ Permission validation
- ✅ Using isModified() helper
- ✅ State sharing with afterUpdate
- ✅ Change-based notifications
- ✅ Updating related records
- ✅ Logging significant changes
- ✅ Accessing state from beforeUpdate
- ✅ Dependency checking
- ✅ Preventing deletion based on status
- ✅ Permission validation
- ✅ Business rule enforcement
- ✅ Cascade deletion of related records
- ✅ Cleanup of external resources
- ✅ Audit logging
- ✅ Notifications
- ✅ Fetch and validate current state
- ✅ Perform atomic updates
- ✅ Use input parameters
- ✅ Return structured results
- ✅ Error handling
- ✅ State transition validation
- ✅ Required input validation
- ✅ Business logic (budget thresholds)
- ✅ User permission checks
- ✅ Create records based on existing ones
- ✅ Copy selected fields
- ✅ Reset certain fields (status, ownership)
- ✅ Optional related data copying
- ✅ Batch creation
- ✅ Data validation
- ✅ Error collection
- ✅ Progress reporting
- ✅ Multiple data sources
- ✅ Batch updates
- ✅ Per-record validation
- ✅ Error collection
- ✅ Skip invalid records
- ✅ Data aggregation
- ✅ Statistical analysis
- ✅ Grouping and counting
- ✅ Computed metrics
- Validation Before Mutation: beforeCreate/beforeUpdate validate data before DB operations
- Side Effects After Mutation: afterCreate/afterUpdate perform notifications and related updates
- Query Modification: beforeFind adds security filters and default sorting
- Result Transformation: afterFind enriches data with computed fields
- State Sharing: Using ctx.state to pass data between before/after hooks
- Change Tracking: Using isModified() to detect field changes
- Previous Data: Accessing previousData in update/delete hooks
- Input Validation: Check required fields and constraints
- State Validation: Fetch current record and validate state
- Atomic Operations: Use api methods for safe updates
- Error Handling: Throw meaningful errors with context
- Structured Returns: Return consistent result objects
- Batch Processing: Handle multiple records with error collection
- Progress Reporting: Track success/failure counts
cd examples/starters/basic-script
npm test __tests__/projects-hooks-actions.test.tsCopy the patterns from these files and adapt them to your needs:
import { ObjectHookDefinition } from '@objectql/types';
const hooks: ObjectHookDefinition<YourType> = {
beforeCreate: async ({ data, user, api }) => {
// Your validation logic
if (!data.field) {
throw new Error('Field is required');
}
// Set defaults
data.owner = user?.id;
},
afterCreate: async ({ result, api }) => {
// Your side effects
await api.create('notifications', {
message: `New record created: ${result.name}`
});
}
};
export default hooks;- Keep hooks focused: Each hook should have a single responsibility
- Use beforeHooks for validation: Don't let invalid data reach the database
- Use afterHooks for side effects: Notifications, related updates, etc.
- Always validate input: Check user input in beforeCreate and beforeUpdate
- Use isModified(): Only react to actual changes in beforeUpdate/afterUpdate
- Share state wisely: Use ctx.state to pass data between before/after pairs
- Handle errors gracefully: Throw clear, actionable error messages
- Test thoroughly: Cover happy paths and error cases
- Document business rules: Add comments explaining why rules exist
- Keep actions idempotent: Actions should be safe to retry
- Row-Level Security: Use beforeFind to filter data by user/tenant
- Permission Checks: Validate user permissions in before hooks
- Input Sanitization: Validate and sanitize all user input
- Prevent Privilege Escalation: Don't allow users to set admin-only fields
- Audit Logging: Log security-sensitive operations in after hooks
- Minimize API calls: Batch operations when possible
- Cache computed values: Don't recalculate on every read
- Use indexes: Ensure filtered fields are indexed
- Avoid N+1 queries: Fetch related data efficiently
- Paginate large results: Don't load thousands of records at once