method-hooks.service.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { Injectable } from '@nestjs/common';
  2. import { Span } from '@opentelemetry/api';
  3. import { Type } from '@vendure/common/lib/shared-types';
  4. import { getInstrumentedClassTarget, Logger } from '@vendure/core';
  5. import { MethodHookConfig } from '../types';
  6. /**
  7. * Extracts only the method names from a class type T
  8. */
  9. export type MethodNames<T> = {
  10. // eslint-disable-next-line @typescript-eslint/ban-types
  11. [K in keyof T]: T[K] extends Function ? K : never;
  12. }[keyof T];
  13. export type Unwrap<T> = T extends Promise<infer U> ? U : T;
  14. type MethodType<T, K extends keyof T> = T[K] extends (...args: any[]) => any ? T[K] : never;
  15. export interface InstrumentedMethodHooks<T, Method extends MethodNames<T>> {
  16. pre?: (input: { instance: T; args: Parameters<MethodType<T, Method>>; span: Span }) => void;
  17. post?: (input: {
  18. instance: T;
  19. args: Parameters<MethodType<T, Method>>;
  20. result: Unwrap<ReturnType<MethodType<T, Method>>>;
  21. span: Span;
  22. }) => void;
  23. }
  24. export type MethodHooksForType<T> = {
  25. [K in MethodNames<T>]?: InstrumentedMethodHooks<T, K>;
  26. };
  27. /**
  28. * @description
  29. * Allows you to register hooks for a specific method of an instrumented class.
  30. * These hooks allow extra telemetry actions to be performed on the method.
  31. *
  32. * They can then be passed to the {@link TelemetryPlugin} via the {@link TelemetryPluginOptions}.
  33. *
  34. * @example
  35. * ```typescript
  36. * const productServiceHooks = registerMethodHooks(ProductService, {
  37. * findOne: {
  38. * // This will be called before the method is executed
  39. * pre: ({ args: [ctx, productId], span }) => {
  40. * span.setAttribute('productId', productId);
  41. * },
  42. * // This will be called after the method is executed
  43. * post: ({ result, span }) => {
  44. * span.setAttribute('found', !!result);
  45. * },
  46. * },
  47. * });
  48. * ```
  49. *
  50. * @since 3.3.0
  51. * @docsCategory core plugins/TelemetryPlugin
  52. */
  53. export function registerMethodHooks<T>(target: Type<T>, hooks: MethodHooksForType<T>): MethodHookConfig<T> {
  54. return {
  55. target,
  56. hooks,
  57. };
  58. }
  59. @Injectable()
  60. export class MethodHooksService {
  61. private hooksMap = new Map<any, { [methodName: string]: InstrumentedMethodHooks<any, any> }>();
  62. registerHooks<T>(target: Type<T>, hooks: MethodHooksForType<T>): void {
  63. const instrumentedClassTarget = getInstrumentedClassTarget(target);
  64. if (!instrumentedClassTarget) {
  65. Logger.error(`Cannot register hooks for non-instrumented class: ${target.name}`);
  66. return;
  67. }
  68. const existingHooks = this.hooksMap.get(instrumentedClassTarget);
  69. const combinedHooks = {
  70. ...existingHooks,
  71. ...hooks,
  72. };
  73. this.hooksMap.set(instrumentedClassTarget, combinedHooks);
  74. }
  75. getHooks<T>(target: T, methodName: string): InstrumentedMethodHooks<T, any> | undefined {
  76. return this.hooksMap.get(target)?.[methodName];
  77. }
  78. }