Skip to content

feat(feathers): add defineHooks utility#2761

Closed
fratzinger wants to merge 1 commit intofeathersjs:dovefrom
fratzinger:feat/defineHooks
Closed

feat(feathers): add defineHooks utility#2761
fratzinger wants to merge 1 commit intofeathersjs:dovefrom
fratzinger:feat/defineHooks

Conversation

@fratzinger
Copy link
Copy Markdown
Member

@fratzinger fratzinger commented Sep 23, 2022

I love these define* utility function so much! Wether it's vues defineComponent feathers-pinia example, vites defineConfig feathers dove docs vitepress example or other configs.

The implementation seems simple and therefore useless but the DX while using it, is soooo nice!
It's a convenient way to provide type safety and hassle free autocompletion.

Reasons why they are nice and should be used and promoted extensively:

  1. It's nice for anonymous hooks (see screenshot). No explicit (context: HookContext<A, S>) needed.
    It also captures typos at compile time. Even for named hooks it's pretty useful. Even for a plain javascript implementation, it's an improvement!
    See:

Bildschirmfoto 2022-09-23 um 22 07 11

2. With the dove implementation: depending on the `Service` methods, we get autocompletion for custom method names for free:

image

This is brought to you by:

// see https://github.com/feathersjs/feathers/blob/dove-docs/packages/feathers/src/declarations.ts#L358
type HookMethodMap<A, S> = {
  [L in keyof S]?: SelfOrArray<HookFunction<A, S>>
} & { all?: SelfOrArray<HookFunction<A, S>> }
  1. (more like 2.1.) A custom service does only support a few methods (e.g. only create). Why should we add hooks for other NotImpleted methods of this service? Throw a compilation error and remove these hooks for the custom service!
  2. jsdocs. Imagine the builtin types of feathers have jsdoc comments. Something like this:

Bildschirmfoto 2022-09-23 um 22 50 37

You also get them in the `defineHooks` util for free:

Bildschirmfoto 2022-09-23 um 22 52 21

Summary

If you ask me, these LOC for defineHooks could be spent more wasteful. What do you think? Should we use them all over the place and add them to the cli templates and to the docs?

‼️ Last but not least (please read):

On a fresh cli generated project using this utility:
image
Sorry for german error. I think it's clear what it's about even without knowing a german word. Maybe my implementation of defineHooks is not quite right? See that it passes for authenticate but not for resolveAll. Should I open a separate issue?
Used packages:

    "@feathersjs/authentication": "^5.0.0-pre.29",
    "@feathersjs/authentication-local": "^5.0.0-pre.29",
    "@feathersjs/authentication-oauth": "^5.0.0-pre.29",
    "@feathersjs/configuration": "^5.0.0-pre.29",
    "@feathersjs/errors": "^5.0.0-pre.29",
    "@feathersjs/feathers": "^5.0.0-pre.29",
    "@feathersjs/knex": "^5.0.0-pre.29",
    "@feathersjs/koa": "^5.0.0-pre.29",
    "@feathersjs/schema": "^5.0.0-pre.29",
    "@feathersjs/socketio": "^5.0.0-pre.29",
    "@feathersjs/transport-commons": "^5.0.0-pre.29",
    "knex": "^2.3.0",
    "koa-static": "^5.0.0",
    "pg": "^8.8.0",
    "winston": "^3.8.2"

@daffl
Copy link
Copy Markdown
Member

daffl commented Sep 23, 2022

I like the idea. Wondering if the utility is necessary if we instead just type the hook map as

export const userHooks: HookMap<Application, UserService> = {
  around: []
}

Would that have the same effect? The error might have something to do with how resolveAll or usersResolvers is defined, we might have to dig a bit more.

@fratzinger
Copy link
Copy Markdown
Member Author

Mostly same effect.

Pros defineHooks:

  • Works in a plain javascript environment compared to : HookMap<Application, UserService>
  • defineHooks<Application, Service>() looks way more readable to me compared to : HookMap<Application, UserService>. I imagine the second is quite a burden for beginners to understand. HookMap looks like an internal type because it's not really descriptive, tbh.

Cons defineHooks:

  • It's not 100% clear what happens in the function. Alternative : HookMap<Application, UserService> is pretty much clear. But define* utilities feel natural fast.

@daffl
Copy link
Copy Markdown
Member

daffl commented Oct 5, 2022

In #2772 I changed the generated service and hook registration to

import { authenticate } from '@feathersjs/authentication'

import type { Application } from '../../../../declarations'
import { TestingService, getOptions } from './test.class'

export * from './test.class'

// A configure function that registers the service and its hooks via `app.configure`
export const testing = (app: Application) => {
  // Register our service on the Feathers application
  app.use('path/to/test', new TestingService(getOptions(app)), {
    // A list of all methods this service exposes externally
    methods: ['find', 'get', 'create', 'update', 'patch', 'remove'],
    // You can add additional custom events to be sent to clients here
    events: []
  })
  // Initialize hooks
  app.service('path/to/test').hooks({
    around: {
      all: [authenticate('jwt')]
    },
    before: {
      all: []
    },
    after: {
      all: []
    },
    error: {
      all: []
    }
  })
}

// Add this service to the service type index
declare module '../../../../declarations' {
  interface ServiceTypes {
    'path/to/test': TestingService
  }
}

This will infer the correct service types automatically. Do you think we'd still need this utility? My suggestion would be to go with that (since with schemas we'll probably have a lot less hooks to worry about) and type it explicitly when someone still wants to extract it into a separate file.

@fratzinger
Copy link
Copy Markdown
Member Author

Thanks for the explanation. It's good to me. I see me adding this utility in our code base anytime soon, but that's okay. Closing this for now.

@fratzinger fratzinger closed this Oct 26, 2022
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.

2 participants