{"version":3,"sources":["../../src/client-builder.ts"],"sourcesContent":["import {\n  Manifest,\n  ManifestOptions,\n  GlobalConfigs,\n  Method,\n  ResourceTypeConstraint,\n} from './manifest'\nimport { Response } from './response'\nimport { Request, RequestContext } from './request'\nimport type { MiddlewareDescriptor, RequestGetter, ResponseGetter } from './middleware/index'\nimport { Gateway } from './gateway/index'\nimport type { Params } from './types'\n\nexport type AsyncFunction = (params?: Params, context?: RequestContext) => Promise<Response>\n\nexport type AsyncFunctions<HashType> = {\n  [Key in keyof HashType]: AsyncFunction\n}\n\nexport type Client<ResourcesType> = {\n  [ResourceKey in keyof ResourcesType]: AsyncFunctions<ResourcesType[ResourceKey]>\n}\n\ninterface RequestPhaseFailureContext {\n  middleware: string | null\n  returnedInvalidRequest: boolean\n  abortExecution: boolean\n}\n\nconst isFactoryConfigured = <T>(factory: () => T | null): factory is () => T => {\n  if (!factory || !factory()) {\n    return false\n  }\n  return true\n}\n\n/**\n * @typedef ClientBuilder\n * @param {Object} manifestDefinition - manifest definition with at least the `resources` key\n * @param {Function} GatewayClassFactory - factory function that returns a gateway class\n */\nexport class ClientBuilder<Resources extends ResourceTypeConstraint> {\n  public Promise: PromiseConstructor\n  public manifest: Manifest<Resources>\n  public GatewayClassFactory: () => typeof Gateway\n  public maxMiddlewareStackExecutionAllowed: number\n\n  constructor(\n    manifestDefinition: ManifestOptions<Resources>,\n    GatewayClassFactory: () => typeof Gateway | null,\n    configs: GlobalConfigs\n  ) {\n    if (!manifestDefinition) {\n      throw new Error(`[Mappersmith] invalid manifest (${manifestDefinition})`)\n    }\n\n    if (!isFactoryConfigured(GatewayClassFactory)) {\n      throw new Error('[Mappersmith] gateway class not configured (configs.gateway)')\n    }\n\n    if (!configs.Promise) {\n      throw new Error('[Mappersmith] Promise not configured (configs.Promise)')\n    }\n\n    this.Promise = configs.Promise\n    this.manifest = new Manifest(manifestDefinition, configs)\n    this.GatewayClassFactory = GatewayClassFactory\n    this.maxMiddlewareStackExecutionAllowed = configs.maxMiddlewareStackExecutionAllowed\n  }\n\n  public build() {\n    const client: Client<Resources> = { _manifest: this.manifest } as never\n\n    this.manifest.eachResource((resourceName: keyof Resources, methods) => {\n      client[resourceName] = this.buildResource(resourceName, methods)\n    })\n\n    return client\n  }\n\n  private buildResource<T, K extends keyof T = keyof T>(resourceName: K, methods: Method[]) {\n    type Resource = AsyncFunctions<T[K]>\n    const initialResourceValue: Partial<Resource> = {}\n\n    const resource = methods.reduce((resource, method) => {\n      const resourceMethod = (requestParams?: Params, context?: RequestContext) => {\n        const request = new Request(method.descriptor, requestParams, context)\n        // `resourceName` can be `PropertyKey`, making this `string | number | Symbol`, therefore the string conversion\n        // to stop type bleeding.\n        return this.invokeMiddlewares(String(resourceName), method.name, request)\n      }\n      return {\n        ...resource,\n        [method.name]: resourceMethod,\n      }\n    }, initialResourceValue)\n\n    // @hint: This type assert is needed as the compiler cannot be made to understand that the reduce produce a\n    // non-partial result on a partial input. This is due to a shortcoming of the type signature for Array<T>.reduce().\n    // @link: https://github.com/microsoft/TypeScript/blob/v3.7.2/lib/lib.es5.d.ts#L1186\n    return resource as Resource\n  }\n\n  private invokeMiddlewares(resourceName: string, resourceMethod: string, initialRequest: Request) {\n    const middleware = this.manifest.createMiddleware({ resourceName, resourceMethod })\n    const GatewayClass = this.GatewayClassFactory()\n    const gatewayConfigs = this.manifest.gatewayConfigs\n    const requestPhaseFailureContext: RequestPhaseFailureContext = {\n      middleware: null,\n      returnedInvalidRequest: false,\n      abortExecution: false,\n    }\n\n    const getInitialRequest = () => this.Promise.resolve(initialRequest)\n    const chainRequestPhase = (next: RequestGetter, middleware: MiddlewareDescriptor) => () => {\n      const abort = (error: Error) => {\n        requestPhaseFailureContext.abortExecution = true\n        throw error\n      }\n\n      return this.Promise.resolve()\n        .then(() => middleware.prepareRequest(next, abort))\n        .then((request: unknown) => {\n          if (request instanceof Request) {\n            return request\n          }\n\n          // FIXME: Here be dragons: prepareRequest is typed as Promise<Response | void>\n          // but this code clearly expects it can be something else... anything.\n          // Hence manual cast to `unknown` above.\n          requestPhaseFailureContext.returnedInvalidRequest = true\n          const typeValue = typeof request\n          const prettyType =\n            typeValue === 'object' || typeValue === 'function'\n              ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                (request as any).name || typeValue\n              : typeValue\n\n          throw new Error(\n            `[Mappersmith] middleware \"${middleware.__name}\" should return \"Request\" but returned \"${prettyType}\"`\n          )\n        })\n        .catch((e) => {\n          requestPhaseFailureContext.middleware = middleware.__name || null\n          throw e\n        })\n    }\n\n    const prepareRequest = middleware.reduce(chainRequestPhase, getInitialRequest)\n    let executions = 0\n\n    const executeMiddlewareStack = () =>\n      prepareRequest()\n        .catch((e) => {\n          const { returnedInvalidRequest, abortExecution, middleware } = requestPhaseFailureContext\n          if (returnedInvalidRequest || abortExecution) {\n            throw e\n          }\n\n          const error = new Error(\n            `[Mappersmith] middleware \"${middleware}\" failed in the request phase: ${e.message}`\n          )\n          error.stack = e.stack\n          throw error\n        })\n        .then((finalRequest) => {\n          executions++\n\n          if (executions > this.maxMiddlewareStackExecutionAllowed) {\n            throw new Error(\n              `[Mappersmith] infinite loop detected (middleware stack invoked ${executions} times). Check the use of \"renew\" in one of the middleware.`\n            )\n          }\n\n          const renew = executeMiddlewareStack\n          const chainResponsePhase =\n            (previousValue: ResponseGetter, currentValue: MiddlewareDescriptor) => () => {\n              // Deliberately putting this on two separate lines - to get typescript to not return \"any\"\n              const nextValue = currentValue.response(previousValue, renew, finalRequest)\n              return nextValue\n            }\n          const callGateway = () => new GatewayClass(finalRequest, gatewayConfigs).call()\n          const execute = middleware.reduce(chainResponsePhase, callGateway)\n          return execute()\n        })\n\n    return new this.Promise<Response>((resolve, reject) => {\n      executeMiddlewareStack()\n        .then((response) => resolve(response))\n        .catch(reject)\n    })\n  }\n}\n\nexport default ClientBuilder\n"],"mappings":"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","names":["resource","middleware"]}