{"version":3,"file":"functions.cjs","names":["SERVICE_NAME","parseAzureRestError: ErrorParser","message: string","body: BodyInit | null"],"sources":["../src/functions.ts"],"sourcesContent":["import type { RestError } from \"@azure/core-rest-pipeline\";\nimport type {\n  HttpFunctionOptions,\n  HttpRequest,\n  HttpResponseInit,\n  SetupOptions,\n  TimerFunctionOptions,\n} from \"@azure/functions\";\nimport { createPurgeHandler, createRequestHandler } from \"@storybooker/core\";\nimport { SERVICE_NAME } from \"@storybooker/core/constants\";\nimport type {\n  ErrorParser,\n  RequestHandlerOptions,\n  StoryBookerUser,\n} from \"@storybooker/core/types\";\nimport { generatePrefixFromBaseRoute, urlJoin } from \"@storybooker/core/utils\";\nimport type { BodyInit } from \"undici\";\n\nconst DEFAULT_PURGE_SCHEDULE_CRON = \"0 0 0 * * *\";\n\nexport type * from \"@storybooker/core/types\";\n\n/**\n * Minimal representation of Azure Functions App namespace\n * to register HTTP and Timer functions.\n */\ninterface FunctionsApp {\n  http(name: string, options: HttpFunctionOptions): void;\n  setup(options: SetupOptions): void;\n  timer(name: string, options: TimerFunctionOptions): void;\n}\n\n/**\n * Options to register the storybooker router\n */\nexport interface RegisterStorybookerRouterOptions<User extends StoryBookerUser>\n  extends Omit<RequestHandlerOptions<User>, \"abortSignal\" | \"prefix\"> {\n  /**\n   * Set the Azure Functions authentication level for all routes.\n   *\n   * This is a good option to set if the service is used in\n   * Headless mode and requires single token authentication\n   * for all the requests.\n   *\n   * This setting does not affect health-check route.\n   */\n  authLevel?: \"admin\" | \"function\" | \"anonymous\";\n\n  /**\n   * Define the route on which all router is placed.\n   * Can be a sub-path of the main API route.\n   *\n   * @default ''\n   */\n  route?: string;\n\n  /**\n   * Modify the cron-schedule of timer function\n   * which purge outdated storybooks.\n   *\n   * Pass `null` to disable auto-purge functionality.\n   *\n   * @default \"0 0 0 * * *\" // Every midnight\n   */\n  purgeScheduleCron?: string | null;\n}\n\n/**\n * Register the Storybooker router with the Azure Functions App.\n *\n * - It enabled streaming responses for HTTP functions.\n * - It registers the HTTP function with provided route and auth-level.\n * - It registers the Timer function for purge if `purgeScheduleCron` is not `null`.\n *\n * @param app Azure Functions App instance\n * @param options Options for registering the router\n */\nexport function registerStoryBookerRouter<User extends StoryBookerUser>(\n  app: FunctionsApp,\n  options: RegisterStorybookerRouterOptions<User>,\n): void {\n  app.setup({ enableHttpStream: true });\n\n  const route = options.route || \"\";\n  const logger = options.logger ?? console;\n  const requestHandler = createRequestHandler({\n    ...options,\n    errorParser: options.errorParser ?? parseAzureRestError,\n    logger,\n    prefix: generatePrefixFromBaseRoute(route) || \"/\",\n  });\n\n  logger.log(\"Registering Storybooker Router (route: %s)\", route || \"/\");\n  app.http(SERVICE_NAME, {\n    authLevel: options.authLevel,\n    handler: async (httpRequest, context) => {\n      const request = transformHttpRequestToWebRequest(httpRequest);\n      const response = await requestHandler(request, {\n        logger: options.logger ?? context,\n      });\n      return transformWebResponseToHttpResponse(response);\n    },\n    methods: [\"DELETE\", \"GET\", \"PATCH\", \"POST\", \"PUT\"],\n    route: urlJoin(route, \"{**path}\"),\n  });\n\n  if (options.purgeScheduleCron !== null) {\n    const schedule = options.purgeScheduleCron || DEFAULT_PURGE_SCHEDULE_CRON;\n    const purgeHandler = createPurgeHandler({\n      database: options.database,\n      errorParser: options.errorParser ?? parseAzureRestError,\n      logger,\n      storage: options.storage,\n    });\n\n    logger.log(\"Registering Storybooker Timer-Purge (cron: %s)\", schedule);\n    app.timer(`${SERVICE_NAME}-timer_purge`, {\n      // oxlint-disable-next-line require-await\n      handler: async (_timer, context) => purgeHandler({}, { logger: context }),\n      runOnStartup: false,\n      schedule,\n    });\n  }\n}\n\nconst parseAzureRestError: ErrorParser = (error) => {\n  if (error instanceof Error && error.name === \"RestError\") {\n    const restError = error as RestError;\n    const details = (restError.details ?? {}) as Record<string, string>;\n    const message: string = details[\"errorMessage\"] ?? restError.message;\n\n    return {\n      errorMessage: `${details[\"errorCode\"] ?? restError.name} (${\n        restError.code ?? restError.statusCode\n      }): ${message}`,\n      errorStatus: restError.statusCode,\n      errorType: \"AzureRest\",\n    };\n  }\n\n  return;\n};\n\nfunction transformHttpRequestToWebRequest(httpRequest: HttpRequest): Request {\n  return new Request(httpRequest.url, {\n    // oxlint-disable-next-line no-invalid-fetch-options\n    body: (httpRequest.body as ReadableStream | null) ?? undefined,\n    // @ts-expect-error - Duplex is required for streaming but not supported in TS\n    duplex: \"half\",\n    headers: new Headers(httpRequest.headers as Headers),\n    method: httpRequest.method,\n  });\n}\n\nasync function transformWebResponseToHttpResponse(\n  response: Response,\n): Promise<HttpResponseInit> {\n  let body: BodyInit | null = null;\n  if (response.body) {\n    body = response.body as unknown as BodyInit;\n  } else {\n    body = await response.text();\n  }\n\n  return {\n    body,\n    headers: response.headers,\n    status: response.status,\n  };\n}\n"],"mappings":";;;;;;;;;AAkBA,MAAM,8BAA8B;;;;;;;;;;;AA2DpC,SAAgB,0BACd,KACA,SACM;AACN,KAAI,MAAM,EAAE,kBAAkB,MAAM,CAAC;CAErC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,8DAAsC;EAC1C,GAAG;EACH,aAAa,QAAQ,eAAe;EACpC;EACA,kEAAoC,MAAM,IAAI;EAC/C,CAAC;AAEF,QAAO,IAAI,8CAA8C,SAAS,IAAI;AACtE,KAAI,KAAKA,2CAAc;EACrB,WAAW,QAAQ;EACnB,SAAS,OAAO,aAAa,YAAY;GACvC,MAAM,UAAU,iCAAiC,YAAY;GAC7D,MAAM,WAAW,MAAM,eAAe,SAAS,EAC7C,QAAQ,QAAQ,UAAU,SAC3B,CAAC;AACF,UAAO,mCAAmC,SAAS;;EAErD,SAAS;GAAC;GAAU;GAAO;GAAS;GAAQ;GAAM;EAClD,6CAAe,OAAO,WAAW;EAClC,CAAC;AAEF,KAAI,QAAQ,sBAAsB,MAAM;EACtC,MAAM,WAAW,QAAQ,qBAAqB;EAC9C,MAAM,0DAAkC;GACtC,UAAU,QAAQ;GAClB,aAAa,QAAQ,eAAe;GACpC;GACA,SAAS,QAAQ;GAClB,CAAC;AAEF,SAAO,IAAI,kDAAkD,SAAS;AACtE,MAAI,MAAM,GAAGA,0CAAa,eAAe;GAEvC,SAAS,OAAO,QAAQ,YAAY,aAAa,EAAE,EAAE,EAAE,QAAQ,SAAS,CAAC;GACzE,cAAc;GACd;GACD,CAAC;;;AAIN,MAAMC,uBAAoC,UAAU;AAClD,KAAI,iBAAiB,SAAS,MAAM,SAAS,aAAa;EACxD,MAAM,YAAY;EAClB,MAAM,UAAW,UAAU,WAAW,EAAE;EACxC,MAAMC,UAAkB,QAAQ,mBAAmB,UAAU;AAE7D,SAAO;GACL,cAAc,GAAG,QAAQ,gBAAgB,UAAU,KAAK,IACtD,UAAU,QAAQ,UAAU,WAC7B,KAAK;GACN,aAAa,UAAU;GACvB,WAAW;GACZ;;;AAML,SAAS,iCAAiC,aAAmC;AAC3E,QAAO,IAAI,QAAQ,YAAY,KAAK;EAElC,MAAO,YAAY,QAAkC;EAErD,QAAQ;EACR,SAAS,IAAI,QAAQ,YAAY,QAAmB;EACpD,QAAQ,YAAY;EACrB,CAAC;;AAGJ,eAAe,mCACb,UAC2B;CAC3B,IAAIC,OAAwB;AAC5B,KAAI,SAAS,KACX,QAAO,SAAS;KAEhB,QAAO,MAAM,SAAS,MAAM;AAG9B,QAAO;EACL;EACA,SAAS,SAAS;EAClB,QAAQ,SAAS;EAClB"}