Skip to content

feat: Chainable @hooks().params() decorator with hookMixin support#3650

Closed
marshallswain wants to merge 7 commits intov6from
hooks-chainable-params
Closed

feat: Chainable @hooks().params() decorator with hookMixin support#3650
marshallswain wants to merge 7 commits intov6from
hooks-chainable-params

Conversation

@marshallswain
Copy link
Member

@marshallswain marshallswain commented Jan 31, 2026

Summary

Adds a chainable @hooks().params() decorator syntax that allows defining custom parameter names for hook context on service methods. When registered as a Feathers service, hookMixin respects these custom params instead of overriding them with defaults.

Usage

class NotificationService {
  @(hooks([
    async (ctx, next) => {
      console.log(ctx.userId)   // Custom param
      console.log(ctx.message)  // Custom param  
      console.log(ctx.app)      // Feathers context still available
      await next()
    }
  ]).params('userId', 'message'))
  async notify(userId: string, message: string) {
    return { sent: true }
  }
}

const app = feathers().use('notifications', new NotificationService(), {
  methods: ['notify']
})

await app.service('notifications').notify('user123', 'Hello!')

Chainable Methods

  • .params(...names) - Define context parameter names
  • .props(properties) - Add static properties to context
  • .defaults(initializer) - Provide default values via function

All can be chained:

@(hooks([])
  .params('id', 'data')
  .props({ serviceName: 'messages' })
  .defaults(() => ({ timestamp: Date.now() })))

Changes

  • packages/feathers/src/hooks/base.ts - Added clone() method to HookManager
  • packages/feathers/src/hooks/hooks.ts - Added ChainableHookDecorator interface and createChainableDecorator()
  • packages/feathers/src/hooks.ts - Modified hookMixin() to detect and respect custom params from decorators
  • packages/feathers/src/hooks/decorator.test.ts - Added 11 new tests
  • website/content/api/hooks-chainable-params.md - Documentation (pending integration into docs rewrite)

Why

Previously, custom service methods were limited to the default hook context params (data, params, id). If you had a method like notify(userId, message), hooks would receive ctx.data and ctx.params instead of ctx.userId and ctx.message.

This change allows:

  • Custom methods to have meaningful, descriptive context properties
  • Hooks to work naturally with any method signature
  • Better developer experience when building services with non-CRUD methods

Based on this comment.

Adds support for a cleaner chainable syntax when defining hook params:

  @hooks([]).params('id', 'data')
  async myMethod(id: string, data: any) {}

Instead of the more verbose:

  @hooks(middleware([]).params('id', 'data'))
  async myMethod(id: string, data: any) {}

The ChainableHookDecorator interface supports:
- .params(...params) - define context param names
- .props(props) - add static context properties
- .defaults(fn) - add default context values

These can be chained in any order and combination.
When a service method is decorated with @hooks([]).params(...), hookMixin
now respects those custom params instead of overriding them with defaults.

This enables the use of custom method signatures:

  class MyService {
    @(hooks([]).params('userId', 'message'))
    async notify(userId: string, message: string) {}
  }

When registered with feathers, the notify method will have context.userId
and context.message available instead of the default context.data/params.

Changes:
- hookMixin checks for existing params from decorated methods
- For decorated methods, Feathers context (app, path, service, method)
  is added to the existing Context prototype instead of re-wrapping
- Added 5 tests for hookMixin respecting decorator params
Pending integration into main hooks documentation during v6-documentation rewrite.
- Add clone() method to HookManager for creating modified copies
- Refactor createChainableDecorator to use clone() instead of manual property copying
- Create createFeathersContextProps() helper to define Feathers context props once
- Add propsFromDescriptors() helper to convert PropertyDescriptorMap to HookContextData
- Tighten decorator types: use AnyFunction instead of Function, specify decorator context types
- Simplify hookMixin by using the shared helper functions
- Extract shared statusCodeDescriptor to avoid duplication
- Split into createFeathersProps() for HookContextData (standard path)
- Split into createFeathersDescriptors() for PropertyDescriptorMap (decorated path)
- Remove propsFromDescriptors() conversion function - no longer needed
Use clone().params/props/defaults() instead of directly accessing
internal _params/_props/_defaults properties.
@marshallswain marshallswain changed the base branch from dove to v6 January 31, 2026 06:39
@marshallswain marshallswain deleted the hooks-chainable-params branch January 31, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant