{"version":3,"file":"loggerLink.mjs","names":[],"sources":["../../src/links/loggerLink.ts"],"sourcesContent":["/// <reference lib=\"dom.iterable\" />\n\n// `dom.iterable` types are explicitly required for extracting `FormData` values,\n// as all implementations of `Symbol.iterable` are separated from the main `dom` types.\n// Using triple-slash directive makes sure that it will be available,\n// even if end-user `tsconfig.json` omits it in the `lib` array.\n\nimport { observable, tap } from '@trpc/server/observable';\nimport type {\n  AnyRouter,\n  InferrableClientTypes,\n} from '@trpc/server/unstable-core-do-not-import';\nimport type { TRPCClientError } from '../TRPCClientError';\nimport type { Operation, OperationResultEnvelope, TRPCLink } from './types';\n\ntype ConsoleEsque = {\n  log: (...args: any[]) => void;\n  error: (...args: any[]) => void;\n};\n\ntype EnableFnOptions<TRouter extends InferrableClientTypes> =\n  | {\n      direction: 'down';\n      result:\n        | OperationResultEnvelope<unknown, TRPCClientError<TRouter>>\n        | TRPCClientError<TRouter>;\n    }\n  | (Operation & {\n      direction: 'up';\n    });\ntype EnabledFn<TRouter extends AnyRouter> = (\n  opts: EnableFnOptions<TRouter>,\n) => boolean;\n\ntype LoggerLinkFnOptions<TRouter extends AnyRouter> = Operation &\n  (\n    | {\n        /**\n         * Request result\n         */\n        direction: 'down';\n        result:\n          | OperationResultEnvelope<unknown, TRPCClientError<TRouter>>\n          | TRPCClientError<TRouter>;\n        elapsedMs: number;\n      }\n    | {\n        /**\n         * Request was just initialized\n         */\n        direction: 'up';\n      }\n  );\n\ntype LoggerLinkFn<TRouter extends AnyRouter> = (\n  opts: LoggerLinkFnOptions<TRouter>,\n) => void;\n\ntype ColorMode = 'ansi' | 'css' | 'none';\n\nexport interface LoggerLinkOptions<TRouter extends AnyRouter> {\n  logger?: LoggerLinkFn<TRouter>;\n  enabled?: EnabledFn<TRouter>;\n  /**\n   * Used in the built-in defaultLogger\n   */\n  console?: ConsoleEsque;\n  /**\n   * Color mode\n   * @default typeof window === 'undefined' ? 'ansi' : 'css'\n   */\n  colorMode?: ColorMode;\n\n  /**\n   * Include context in the log - defaults to false unless `colorMode` is 'css'\n   */\n  withContext?: boolean;\n}\n\nfunction isFormData(value: unknown): value is FormData {\n  if (typeof FormData === 'undefined') {\n    // FormData is not supported\n    return false;\n  }\n  return value instanceof FormData;\n}\n\nconst palettes = {\n  css: {\n    query: ['72e3ff', '3fb0d8'],\n    mutation: ['c5a3fc', '904dfc'],\n    subscription: ['ff49e1', 'd83fbe'],\n  },\n  ansi: {\n    regular: {\n      // Cyan background, black and white text respectively\n      query: ['\\x1b[30;46m', '\\x1b[97;46m'],\n      // Magenta background, black and white text respectively\n      mutation: ['\\x1b[30;45m', '\\x1b[97;45m'],\n      // Green background, black and white text respectively\n      subscription: ['\\x1b[30;42m', '\\x1b[97;42m'],\n    },\n    bold: {\n      query: ['\\x1b[1;30;46m', '\\x1b[1;97;46m'],\n      mutation: ['\\x1b[1;30;45m', '\\x1b[1;97;45m'],\n      subscription: ['\\x1b[1;30;42m', '\\x1b[1;97;42m'],\n    },\n  },\n} as const;\n\nfunction constructPartsAndArgs(\n  opts: LoggerLinkFnOptions<any> & {\n    colorMode: ColorMode;\n    withContext?: boolean;\n  },\n) {\n  const { direction, type, withContext, path, id, input } = opts;\n\n  const parts: string[] = [];\n  const args: any[] = [];\n\n  if (opts.colorMode === 'none') {\n    parts.push(direction === 'up' ? '>>' : '<<', type, `#${id}`, path);\n  } else if (opts.colorMode === 'ansi') {\n    const [lightRegular, darkRegular] = palettes.ansi.regular[type];\n    const [lightBold, darkBold] = palettes.ansi.bold[type];\n    const reset = '\\x1b[0m';\n\n    parts.push(\n      direction === 'up' ? lightRegular : darkRegular,\n      direction === 'up' ? '>>' : '<<',\n      type,\n      direction === 'up' ? lightBold : darkBold,\n      `#${id}`,\n      path,\n      reset,\n    );\n  } else {\n    // css color mode\n    const [light, dark] = palettes.css[type];\n    const css = `\n    background-color: #${direction === 'up' ? light : dark};\n    color: ${direction === 'up' ? 'black' : 'white'};\n    padding: 2px;\n  `;\n\n    parts.push(\n      '%c',\n      direction === 'up' ? '>>' : '<<',\n      type,\n      `#${id}`,\n      `%c${path}%c`,\n      '%O',\n    );\n    args.push(\n      css,\n      `${css}; font-weight: bold;`,\n      `${css}; font-weight: normal;`,\n    );\n  }\n\n  if (direction === 'up') {\n    args.push(withContext ? { input, context: opts.context } : { input });\n  } else {\n    args.push({\n      input,\n      result: opts.result,\n      elapsedMs: opts.elapsedMs,\n      ...(withContext && { context: opts.context }),\n    });\n  }\n\n  return { parts, args };\n}\n\n// maybe this should be moved to it's own package\nconst defaultLogger =\n  <TRouter extends AnyRouter>({\n    c = console,\n    colorMode = 'css',\n    withContext,\n  }: {\n    c?: ConsoleEsque;\n    colorMode?: ColorMode;\n    withContext?: boolean;\n  }): LoggerLinkFn<TRouter> =>\n  (props) => {\n    const rawInput = props.input;\n    const input = isFormData(rawInput)\n      ? Object.fromEntries(rawInput)\n      : rawInput;\n\n    const { parts, args } = constructPartsAndArgs({\n      ...props,\n      colorMode,\n      input,\n      withContext,\n    });\n\n    const fn: 'error' | 'log' =\n      props.direction === 'down' &&\n      props.result &&\n      (props.result instanceof Error ||\n        ('error' in props.result.result && props.result.result.error))\n        ? 'error'\n        : 'log';\n\n    c[fn].apply(null, [parts.join(' ')].concat(args));\n  };\n\n/**\n * @see https://trpc.io/docs/v11/client/links/loggerLink\n */\nexport function loggerLink<TRouter extends AnyRouter = AnyRouter>(\n  opts: LoggerLinkOptions<TRouter> = {},\n): TRPCLink<TRouter> {\n  const { enabled = () => true } = opts;\n\n  const colorMode =\n    opts.colorMode ?? (typeof window === 'undefined' ? 'ansi' : 'css');\n  const withContext = opts.withContext ?? colorMode === 'css';\n  const {\n    logger = defaultLogger({ c: opts.console, colorMode, withContext }),\n  } = opts;\n\n  return () => {\n    return ({ op, next }) => {\n      return observable((observer) => {\n        // ->\n        if (enabled({ ...op, direction: 'up' })) {\n          logger({\n            ...op,\n            direction: 'up',\n          });\n        }\n        const requestStartTime = Date.now();\n        function logResult(\n          result:\n            | OperationResultEnvelope<unknown, TRPCClientError<TRouter>>\n            | TRPCClientError<TRouter>,\n        ) {\n          const elapsedMs = Date.now() - requestStartTime;\n\n          if (enabled({ ...op, direction: 'down', result })) {\n            logger({\n              ...op,\n              direction: 'down',\n              elapsedMs,\n              result,\n            });\n          }\n        }\n        return next(op)\n          .pipe(\n            tap({\n              next(result) {\n                logResult(result);\n              },\n              error(result) {\n                logResult(result);\n              },\n            }),\n          )\n          .subscribe(observer);\n      });\n    };\n  };\n}\n"],"mappings":"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"}