UNPKG

@lokalise/node-api

Version:
1 lines 154 kB
{"version":3,"file":"main.mjs","names":["record","dataRecord","Jwt","JwtModel","TeamUserBillingDetails","BillingDetailsModel"],"sources":["../src/models/base_model.ts","../src/models/branch.ts","../src/lokalise/pkg.ts","../src/models/api_error.ts","../src/http_client/base.ts","../src/models/paginated_result.ts","../src/models/cursor_paginated_result.ts","../src/collections/base_collection.ts","../src/collections/branches.ts","../src/models/comment.ts","../src/collections/comments.ts","../src/models/contributor.ts","../src/collections/contributors.ts","../src/models/file.ts","../src/models/queued_process.ts","../src/utils/logger.ts","../src/collections/files.ts","../src/models/glossary_term.ts","../src/collections/glossary_terms.ts","../src/models/jwt.ts","../src/collections/jwt.ts","../src/models/key.ts","../src/collections/keys.ts","../src/models/language.ts","../src/collections/languages.ts","../src/models/order.ts","../src/collections/orders.ts","../src/models/payment_card.ts","../src/collections/payment_cards.ts","../src/models/permission_template.ts","../src/collections/permission_templates.ts","../src/models/project.ts","../src/collections/projects.ts","../src/collections/queued_processes.ts","../src/models/screenshot.ts","../src/collections/screenshots.ts","../src/models/segment.ts","../src/collections/segments.ts","../src/models/snapshot.ts","../src/collections/snapshots.ts","../src/models/task.ts","../src/collections/tasks.ts","../src/models/team_user_billing_details.ts","../src/collections/team_user_billing_details.ts","../src/models/team_user.ts","../src/collections/team_users.ts","../src/models/team.ts","../src/collections/teams.ts","../src/models/translation_provider.ts","../src/collections/translation_providers.ts","../src/models/translation_status.ts","../src/collections/translation_statuses.ts","../src/models/translation.ts","../src/collections/translations.ts","../src/models/user_group.ts","../src/collections/user_groups.ts","../src/models/webhook.ts","../src/collections/webhooks.ts","../src/lokalise/base_client.ts","../src/lokalise/lokalise_api.ts","../src/lokalise/lokalise_api_oauth.ts","../src/models/ota/ota_bundle.ts","../src/ota_collections/ota_collection.ts","../src/ota_collections/ota_bundle_management.ts","../src/ota_collections/ota_bundle_publishing.ts","../src/models/ota/ota_bundle_archive.ts","../src/ota_collections/ota_bundles.ts","../src/models/ota/ota_freeze_period.ts","../src/ota_collections/ota_freeze_periods.ts","../src/models/ota/ota_sdk_token.ts","../src/ota_collections/ota_sdk_tokens.ts","../src/models/ota/ota_statistics.ts","../src/ota_collections/ota_usage_statistics.ts","../src/lokalise/lokalise_api_ota.ts","../src/lokalise/lokalise_ota_bundles.ts","../src/oauth2/auth_request.ts","../src/oauth2/lokalise_auth.ts","../src/models/auth_error.ts"],"sourcesContent":["export abstract class BaseModel<\n\tT extends Record<string, unknown> = Record<string, unknown>,\n> {\n\tconstructor(params: Partial<T>) {\n\t\tObject.assign(this, params);\n\t}\n}\n","import type { Branch as BranchInterface } from \"../interfaces/branch.js\";\nimport { BaseModel } from \"./base_model.js\";\n\nexport class Branch extends BaseModel implements BranchInterface {\n\tdeclare branch_id: number;\n\tdeclare name: string;\n\tdeclare created_at: string;\n\tdeclare created_at_timestamp: number;\n\tdeclare created_by: number;\n\tdeclare created_by_email: string;\n}\n","import { readFile } from \"node:fs/promises\";\n\n/**\n * Returns the relative path to the package.json file.\n * Adjust this if your directory structure changes.\n */\nfunction pkgPath(): string {\n\treturn \"../../package.json\";\n}\n\n/**\n * Attempts to read and parse the local package.json file to retrieve the version.\n * If the file cannot be read or parsed, returns \"unknown\".\n *\n * @returns {Promise<string>} The package version string or \"unknown\" if unavailable.\n */\nexport async function getVersion(): Promise<string> {\n\ttry {\n\t\tconst data = await readFile(new URL(pkgPath(), import.meta.url));\n\t\tconst pkg = JSON.parse(data.toString()) as { version?: string };\n\t\treturn String(pkg.version);\n\t} catch {\n\t\treturn \"unknown\";\n\t}\n}\n","import type { IApiError } from \"../interfaces/api_error.js\";\n\n/**\n * Represents an API error with a specific code and optional details.\n */\nexport class ApiError extends Error implements IApiError {\n\t/**\n\t * The error code representing the type of API error.\n\t */\n\tcode: number;\n\n\t/**\n\t * Additional details about the error (optional).\n\t */\n\tdetails?: Record<string, string | number | boolean>;\n\n\t/**\n\t * Creates an instance of ApiError.\n\t *\n\t * @param {string} message - The error message.\n\t * @param {number} code - The error code.\n\t * @param {Record<string, string | number | boolean>} [details] - Additional details about the error.\n\t */\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: number,\n\t\tdetails?: Record<string, string | number | boolean>,\n\t) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t\tif (details) {\n\t\t\tthis.details = details;\n\t\t}\n\t}\n\n\t/**\n\t * Returns a string representation of the error, including code and details.\n\t *\n\t * @returns The formatted error message.\n\t */\n\toverride toString(): string {\n\t\tlet baseMessage = `LokaliseError: ${this.message}`;\n\t\tbaseMessage += ` (Code: ${this.code})`;\n\n\t\tif (this.details) {\n\t\t\tconst formattedDetails = Object.entries(this.details)\n\t\t\t\t.map(([key, value]) => `${key}: ${value}`)\n\t\t\t\t.join(\", \");\n\n\t\t\tbaseMessage += ` | Details: ${formattedDetails}`;\n\t\t}\n\t\treturn baseMessage;\n\t}\n}\n","import type { ClientData } from \"../interfaces/client_data.js\";\nimport { getVersion } from \"../lokalise/pkg.js\";\nimport { ApiError } from \"../models/api_error.js\";\nimport type { HttpMethod } from \"../types/http_method.js\";\n\nexport type ApiResponse = {\n\tjson: Record<string, unknown>;\n\theaders: Headers;\n};\n\n/**\n * Represents a single API request to the Lokalise API.\n * Handles URL construction, request initiation, response processing, and error handling.\n */\nexport class ApiRequest {\n\t/**\n\t * The default base URL for the Lokalise API.\n\t */\n\tprotected static readonly urlRoot = \"https://api.lokalise.com/api2/\";\n\n\t/**\n\t * The resolved response from the API request.\n\t */\n\tpublic response?: ApiResponse;\n\n\t/**\n\t * Query and path parameters used to construct the request URL.\n\t * This object is modified during URL construction, removing parameters used in path segments.\n\t */\n\tpublic params: Record<string, unknown> = {};\n\n\t/**\n\t * Constructs a new ApiRequest instance.\n\t * @param params - Query and/or path parameters.\n\t */\n\tconstructor(params: Record<string, unknown>) {\n\t\t// Copy params to avoid modifying the original object\n\t\tthis.params = { ...params };\n\t}\n\n\tpublic static async create(\n\t\turi: string,\n\t\tmethod: HttpMethod,\n\t\tbody: object | object[] | null,\n\t\tparams: Record<string, unknown>,\n\t\tclientData: ClientData,\n\t): Promise<ApiRequest & { response: ApiResponse }>;\n\n\t/**\n\t * Static async factory method to create an ApiRequest instance with a fully resolved response.\n\t * @param uri - The endpoint URI (versioned path expected).\n\t * @param method - The HTTP method (GET, POST, PUT, DELETE, etc).\n\t * @param body - The request payload, if applicable.\n\t * @param params - Query and/or path parameters.\n\t * @param clientData - Authentication and configuration data for the request.\n\t * @returns A promise that resolves to a fully constructed ApiRequest instance with the `response` set.\n\t */\n\tpublic static async create(\n\t\turi: string,\n\t\tmethod: HttpMethod,\n\t\tbody: object | object[] | null,\n\t\tparams: Record<string, unknown>,\n\t\tclientData: ClientData,\n\t): Promise<ApiRequest> {\n\t\tconst apiRequest = new ApiRequest(params);\n\t\tapiRequest.response = await apiRequest.createPromise(\n\t\t\turi,\n\t\t\tmethod,\n\t\t\tbody,\n\t\t\tclientData,\n\t\t);\n\t\treturn apiRequest;\n\t}\n\n\t/**\n\t * Creates the request promise by composing the URL, building headers, and executing the fetch.\n\t * @param uri - The endpoint URI.\n\t * @param method - The HTTP method.\n\t * @param body - The request payload.\n\t * @param clientData - Client configuration and auth data.\n\t * @returns A promise resolving to an ApiResponse or rejecting with an ApiError.\n\t */\n\tprotected async createPromise(\n\t\turi: string,\n\t\tmethod: HttpMethod,\n\t\tbody: object | object[] | null,\n\t\tclientData: ClientData,\n\t): Promise<ApiResponse> {\n\t\tconst url = this.composeURI(`/${clientData.version}/${uri}`);\n\t\tconst prefixUrl = clientData.host ?? ApiRequest.urlRoot;\n\t\tconst headers = await this.buildHeaders(clientData, method, body);\n\n\t\tconst options: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\t...(method !== \"GET\" && body ? { body: JSON.stringify(body) } : {}),\n\t\t};\n\n\t\tconst target = new URL(url, prefixUrl);\n\t\tconst stringifiedParams: Record<string, string> = Object.fromEntries(\n\t\t\tObject.entries(this.params)\n\t\t\t\t.filter(([, value]) => value !== undefined && value !== null)\n\t\t\t\t.map(([key, value]) => [key, String(value)]),\n\t\t);\n\t\ttarget.search = new URLSearchParams(stringifiedParams).toString();\n\n\t\treturn this.fetchAndHandleResponse(\n\t\t\ttarget,\n\t\t\toptions,\n\t\t\tclientData.requestTimeout,\n\t\t);\n\t}\n\n\t/**\n\t * Executes the fetch request and handles network-level errors.\n\t * Applies a request timeout if specified.\n\t * @param target - The fully constructed request URL.\n\t * @param options - The fetch request options.\n\t * @param requestTimeout - Optional timeout in milliseconds.\n\t * @returns A promise resolving to an ApiResponse or rejecting with an ApiError.\n\t */\n\tprotected async fetchAndHandleResponse(\n\t\ttarget: URL,\n\t\toptions: RequestInit,\n\t\trequestTimeout = 0,\n\t): Promise<ApiResponse> {\n\t\tconst signal =\n\t\t\trequestTimeout > 0 ? AbortSignal.timeout(requestTimeout) : undefined;\n\n\t\ttry {\n\t\t\tconst response = await fetch(target, {\n\t\t\t\t...options,\n\t\t\t\t...(signal ? { signal } : {}),\n\t\t\t});\n\n\t\t\treturn this.processResponse(response);\n\t\t} catch (err) {\n\t\t\tif (err instanceof Error) {\n\t\t\t\tif (err.name === \"TimeoutError\") {\n\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\tnew ApiError(`Request timed out after ${requestTimeout}ms`, 408, {\n\t\t\t\t\t\t\treason: \"timeout\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew ApiError(err.message, 500, { reason: \"network or fetch error\" }),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn Promise.reject(\n\t\t\t\tnew ApiError(\"An unknown error occurred\", 500, {\n\t\t\t\t\treason: String(err),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Processes the fetch response.\n\t * Attempts to parse JSON unless the status is 204 (No Content).\n\t * @param response - The raw fetch Response object.\n\t * @returns A promise resolving to an ApiResponse if successful, or rejecting with ApiError otherwise.\n\t */\n\tprotected async processResponse(response: Response): Promise<ApiResponse> {\n\t\tlet responseJSON: unknown = null;\n\n\t\ttry {\n\t\t\tif (response.status !== 204) {\n\t\t\t\tresponseJSON = await response.json();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew ApiError((error as Error).message, response.status, {\n\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\treason: \"JSON parsing error\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tif (response.ok) {\n\t\t\treturn {\n\t\t\t\tjson: responseJSON as Record<string, unknown>,\n\t\t\t\theaders: response.headers,\n\t\t\t};\n\t\t}\n\n\t\treturn Promise.reject(this.getErrorFromResp(responseJSON));\n\t}\n\n\t/**\n\t * Derives an ApiError instance from the response JSON, which may follow various patterns.\n\t * @param respJson - The parsed JSON response from the server.\n\t * @returns An ApiError representing the server error.\n\t */\n\tprotected getErrorFromResp(respJson: unknown): ApiError {\n\t\tif (!respJson || typeof respJson !== \"object\") {\n\t\t\treturn new ApiError(\"An unknown error occurred\", 500, {\n\t\t\t\treason: \"unexpected response format\",\n\t\t\t});\n\t\t}\n\n\t\tconst errorObj = respJson as Record<string, unknown>;\n\n\t\t// Top-level error format: { message: string, statusCode: number, error: string }\n\t\tif (\n\t\t\ttypeof errorObj.message === \"string\" &&\n\t\t\ttypeof errorObj.statusCode === \"number\" &&\n\t\t\ttypeof errorObj.error === \"string\"\n\t\t) {\n\t\t\treturn new ApiError(errorObj.message, errorObj.statusCode, {\n\t\t\t\treason: errorObj.error,\n\t\t\t});\n\t\t}\n\n\t\t// Nested error object: { error: { message, code, details } }\n\t\tif (errorObj.error && typeof errorObj.error === \"object\") {\n\t\t\tconst {\n\t\t\t\tmessage = \"Unknown error\",\n\t\t\t\tcode = 500,\n\t\t\t\tdetails,\n\t\t\t} = errorObj.error as Record<string, unknown>;\n\t\t\tconst safeDetails: Record<string, string | number | boolean> =\n\t\t\t\ttypeof details === \"object\" && details !== null\n\t\t\t\t\t? (details as Record<string, string | number | boolean>)\n\t\t\t\t\t: { reason: \"server error without details\" };\n\n\t\t\treturn new ApiError(\n\t\t\t\tString(message),\n\t\t\t\ttypeof code === \"number\" ? code : 500,\n\t\t\t\tsafeDetails,\n\t\t\t);\n\t\t}\n\n\t\t// Alternative top-level fields: { message: string, code?: number, errorCode?: number, details?: any }\n\t\tif (\n\t\t\ttypeof errorObj.message === \"string\" &&\n\t\t\t(typeof errorObj.code === \"number\" ||\n\t\t\t\ttypeof errorObj.errorCode === \"number\")\n\t\t) {\n\t\t\tconst statusCode =\n\t\t\t\ttypeof errorObj.code === \"number\" ? errorObj.code : errorObj.errorCode;\n\t\t\tconst rawDetails = errorObj.details;\n\t\t\tconst safeDetails: Record<string, string | number | boolean> =\n\t\t\t\ttypeof rawDetails === \"object\" && rawDetails !== null\n\t\t\t\t\t? (rawDetails as Record<string, string | number | boolean>)\n\t\t\t\t\t: { reason: \"server error without details\" };\n\t\t\treturn new ApiError(errorObj.message, statusCode as number, safeDetails);\n\t\t}\n\n\t\t// Fallback if no known error format matches\n\t\treturn new ApiError(\"An unknown error occurred\", 500, {\n\t\t\treason: \"unhandled error format\",\n\t\t\tdata: JSON.stringify(respJson),\n\t\t});\n\t}\n\n\t/**\n\t * Builds the request headers, including authentication, compression, and JSON headers as needed.\n\t * @param clientData - Client configuration and auth data.\n\t * @param method - The HTTP method.\n\t * @param body - The request payload.\n\t * @returns A promise resolving to the constructed Headers.\n\t */\n\tprotected async buildHeaders(\n\t\tclientData: ClientData,\n\t\tmethod: HttpMethod,\n\t\tbody: object | object[] | null,\n\t): Promise<Headers> {\n\t\tconst userAgent =\n\t\t\tclientData.userAgent?.trim() || `node-lokalise-api/${await getVersion()}`;\n\t\tconst headers = new Headers({\n\t\t\tAccept: \"application/json\",\n\t\t\t\"User-Agent\": userAgent,\n\t\t});\n\n\t\t// Auth header can be either just the token or \"<tokenType> <token>\"\n\t\theaders.append(\n\t\t\tclientData.authHeader,\n\t\t\tclientData.tokenType.length > 0\n\t\t\t\t? `${clientData.tokenType} ${clientData.token}`\n\t\t\t\t: clientData.token,\n\t\t);\n\n\t\tif (clientData.enableCompression) {\n\t\t\theaders.append(\"Accept-Encoding\", \"gzip,deflate\");\n\t\t}\n\n\t\tif (method !== \"GET\" && body) {\n\t\t\theaders.append(\"Content-Type\", \"application/json\");\n\t\t}\n\n\t\treturn headers;\n\t}\n\n\t/**\n\t * Composes the final URI by replacing placeholders of the form `/{!:{paramName}}`\n\t * with the corresponding parameter values.\n\t * @param rawUri - The raw URI template.\n\t * @returns The final composed URI string.\n\t * @throws Error if a required parameter is missing.\n\t */\n\tprotected composeURI(rawUri: string): string {\n\t\tconst regexp = /\\{(!?):(\\w+)\\}/g;\n\t\tconst uri = rawUri.replace(regexp, this.mapUriParams());\n\t\treturn uri.endsWith(\"/\") ? uri.slice(0, -1) : uri;\n\t}\n\n\t/**\n\t * Returns a function that maps URI parameters from placeholders.\n\t * @returns A function used as a replacement callback in `composeURI`.\n\t * @throws Error if a required parameter is missing.\n\t */\n\tprotected mapUriParams(): (\n\t\tsubstring: string,\n\t\tisMandatory: string,\n\t\tparamName: string,\n\t) => string {\n\t\treturn (\n\t\t\t_substring: string,\n\t\t\tisMandatory: string,\n\t\t\tparamName: string,\n\t\t): string => {\n\t\t\tif (this.params[paramName] != null) {\n\t\t\t\tconst paramValue = String(this.params[paramName]);\n\t\t\t\t// Remove the parameter so it doesn't appear as a query parameter\n\t\t\t\tdelete this.params[paramName];\n\t\t\t\treturn paramValue;\n\t\t\t}\n\t\t\tif (isMandatory === \"!\") {\n\t\t\t\tthrow new Error(`Missing required parameter: ${paramName}`);\n\t\t\t}\n\t\t\treturn \"\";\n\t\t};\n\t}\n}\n","import type { PaginatedResult as PaginatedResultInterface } from \"../interfaces/paginated_result.js\";\n\nexport class PaginatedResult<T> implements PaginatedResultInterface {\n\ttotalResults: number;\n\ttotalPages: number;\n\tresultsPerPage: number;\n\tcurrentPage: number;\n\tresponseTooBig: boolean;\n\titems: T[];\n\n\tconstructor(items: T[], headers: Headers) {\n\t\tthis.totalResults = this.safeParseInt(\n\t\t\theaders.get(\"x-pagination-total-count\"),\n\t\t);\n\t\tthis.totalPages = this.safeParseInt(headers.get(\"x-pagination-page-count\"));\n\t\tthis.resultsPerPage = this.safeParseInt(headers.get(\"x-pagination-limit\"));\n\t\tthis.currentPage = this.safeParseInt(headers.get(\"x-pagination-page\"));\n\t\tthis.responseTooBig = headers.has(\"x-response-too-big\");\n\t\tthis.items = items;\n\t}\n\n\thasNextPage(): boolean {\n\t\treturn this.currentPage > 0 && this.currentPage < this.totalPages;\n\t}\n\n\thasPrevPage(): boolean {\n\t\treturn this.currentPage > 1;\n\t}\n\n\tisLastPage(): boolean {\n\t\treturn !this.hasNextPage();\n\t}\n\n\tisFirstPage(): boolean {\n\t\treturn !this.hasPrevPage();\n\t}\n\n\tnextPage(): number {\n\t\tif (this.isLastPage()) {\n\t\t\treturn this.currentPage;\n\t\t}\n\t\treturn this.currentPage + 1;\n\t}\n\n\tprevPage(): number {\n\t\tif (this.isFirstPage()) {\n\t\t\treturn this.currentPage;\n\t\t}\n\t\treturn this.currentPage - 1;\n\t}\n\n\tprivate safeParseInt(str: string | null): number {\n\t\tif (!str || Number.isNaN(Number(str))) {\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn Number.parseInt(str, 10);\n\t}\n}\n","import type { CursorPaginatedResult as CursorPaginatedResultInterface } from \"../interfaces/cursor_paginated_result.js\";\nimport { PaginatedResult } from \"./paginated_result.js\";\n\nexport class CursorPaginatedResult<T>\n\textends PaginatedResult<T>\n\timplements CursorPaginatedResultInterface\n{\n\tnextCursor: string | null;\n\n\tconstructor(items: T[], headers: Headers) {\n\t\tsuper(items, headers);\n\n\t\tthis.nextCursor = headers.get(\"x-pagination-next-cursor\");\n\t}\n\n\thasNextCursor(): boolean {\n\t\treturn this.nextCursor !== null;\n\t}\n}\n","import { ApiRequest, type ApiResponse } from \"../http_client/base.js\";\nimport type { BulkResult } from \"../interfaces/bulk_result.js\";\nimport type { ClientData } from \"../interfaces/client_data.js\";\nimport { CursorPaginatedResult } from \"../models/cursor_paginated_result.js\";\nimport { PaginatedResult } from \"../models/paginated_result.js\";\nimport type { HttpMethod } from \"../types/http_method.js\";\n\ntype ResolveHandler<T> = (json: Record<string, unknown>, headers: Headers) => T;\ntype ApiRequestWithResponse = ApiRequest & { response: ApiResponse };\n\n/**\n * An abstract base class that provides generic CRUD (Create, Read, Update, Delete) operations\n * and handling for pagination, cursor pagination, and bulk operations. Other \"collection\" classes\n * should extend this class and provide specific implementations for resource endpoints.\n *\n * Expected usage:\n * - Subclasses define `rootElementName` and/or `rootElementNameSingular` to indicate the JSON fields\n * that contain the desired data.\n * - `elementClass` and optionally `secondaryElementClass` should be overridden to map raw JSON\n * objects to strongly typed model instances.\n * - `endpoint` and `prefixURI` should be set as static properties in subclasses to specify resource paths.\n */\nexport abstract class BaseCollection<ElementType, SecondaryType = ElementType> {\n\t/**\n\t * Client data containing authentication and configuration details.\n\t * Provided by a `BaseClient` or similar client instance.\n\t */\n\treadonly clientData: ClientData;\n\n\t/**\n\t * Static endpoint property that subclasses can define to indicate the API endpoint\n\t * for this collection. If not set, ensure `prefixURI` or `uri` parameters are passed.\n\t */\n\tprotected static endpoint: string | null;\n\n\t/**\n\t * Static prefixURI property that subclasses can define to indicate a base path.\n\t * If `uri` is not passed explicitly, this prefix is used to construct the request URL.\n\t */\n\tprotected static prefixURI: string | null;\n\n\t/**\n\t * Constructs a new BaseCollection instance.\n\t * @param clientData - Client data for making authenticated requests.\n\t */\n\tconstructor(clientData: ClientData) {\n\t\tthis.clientData = clientData;\n\t}\n\n\t/**\n\t * Abstract getter that must be implemented by subclasses.\n\t * Should return a class constructor that maps a JSON object to an `ElementType` instance.\n\t */\n\tprotected abstract get elementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => ElementType;\n\n\t/**\n\t * Getter that must be overridden by subclasses to return the root element name\n\t * for array-based JSON responses.\n\t * @throws Error if not defined by the subclass.\n\t */\n\tprotected get rootElementName(): string {\n\t\tthrow new Error(\n\t\t\t\"rootElementName is not defined. Subclasses must override `rootElementName`.\",\n\t\t);\n\t}\n\n\t/**\n\t * Getter that may be overridden by subclasses to return the root element name\n\t * for single-item JSON responses.\n\t * @throws Error if not defined by the subclass.\n\t */\n\tprotected get rootElementNameSingular(): string | null {\n\t\tthrow new Error(\n\t\t\t\"rootElementNameSingular is not defined. Subclasses must override `rootElementNameSingular`.\",\n\t\t);\n\t}\n\n\t/**\n\t * Getter that may be overridden by subclasses if a secondary model type is returned.\n\t * By default, this throws an error. If needed, override it in the subclass.\n\t */\n\tprotected get secondaryElementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => SecondaryType {\n\t\tthrow new Error(\n\t\t\t\"Secondary elements are not supported by this collection. Override `secondaryElementClass` if needed.\",\n\t\t);\n\t}\n\n\t/**\n\t * Getter that must be overridden if `secondaryElementClass` is used.\n\t * Returns the JSON property name for the secondary element.\n\t * @throws Error if not defined by the subclass that uses secondary elements.\n\t */\n\tprotected get secondaryElementNameSingular(): string {\n\t\tthrow new Error(\n\t\t\t\"secondaryElementNameSingular is not defined. Subclasses must override this if secondary elements are used.\",\n\t\t);\n\t}\n\n\t/**\n\t * Perform a GET request that expects a list of items.\n\t * @param params Optional query or request parameters.\n\t * @returns A promise resolving to either a paginated result or an array of ElementType.\n\t */\n\tprotected doList(\n\t\tparams: Record<string, unknown>,\n\t): Promise<PaginatedResult<ElementType> | ElementType[]> {\n\t\treturn this.createPromise(\"GET\", params, this.populateArrayFromJson, null);\n\t}\n\n\t/**\n\t * Perform a GET request that expects a cursor-paginated list of items.\n\t * @param params Optional query or request parameters.\n\t * @returns A promise resolving to a CursorPaginatedResult of ElementType.\n\t */\n\tprotected doListCursor(\n\t\tparams: Record<string, unknown>,\n\t): Promise<CursorPaginatedResult<ElementType>> {\n\t\treturn this.createPromise(\n\t\t\t\"GET\",\n\t\t\tparams,\n\t\t\tthis.populateArrayFromJsonCursor,\n\t\t\tnull,\n\t\t);\n\t}\n\n\t/**\n\t * Perform a GET request to retrieve a single item by its ID.\n\t * @param id The ID of the item to retrieve.\n\t * @param params Optional query or request parameters.\n\t * @returns A promise resolving to a single ElementType instance.\n\t */\n\tprotected doGet(\n\t\tid: string | number,\n\t\tparams: Record<string, unknown> = {},\n\t): Promise<ElementType> {\n\t\treturn this.createPromise(\n\t\t\t\"GET\",\n\t\t\t{ ...params, id },\n\t\t\tthis.populateObjectFromJsonRoot,\n\t\t\tnull,\n\t\t);\n\t}\n\n\t/**\n\t * Perform a DELETE request to remove a single item by its ID.\n\t * @param id The ID of the item to delete.\n\t * @param params Optional query or request parameters.\n\t * @returns A promise resolving to JSON representing the deletion result.\n\t */\n\tprotected doDelete<T = Record<string, unknown> | Record<string, unknown>[]>(\n\t\tid: string | number,\n\t\tparams: Record<string, unknown> = {},\n\t): Promise<T> {\n\t\treturn this.createPromise(\n\t\t\t\"DELETE\",\n\t\t\t{ ...params, id },\n\t\t\tthis.returnBareJSON,\n\t\t\tnull,\n\t\t) as Promise<T>;\n\t}\n\n\t/**\n\t * Perform a POST request to create a new resource.\n\t * @param body The object or array of objects to send in the request body.\n\t * @param params Optional query or request parameters.\n\t * @param resolveFn Optional custom resolve handler to parse the response.\n\t * @returns A promise resolving to an ElementType or SecondaryType instance.\n\t */\n\tprotected doCreate(\n\t\tbody: object | object[] | null,\n\t\tparams: Record<string, unknown> = {},\n\t\tresolveFn = this.populateObjectFromJson,\n\t): Promise<ElementType | SecondaryType> {\n\t\treturn this.createPromise(\"POST\", params, resolveFn, body);\n\t}\n\n\t/**\n\t * Perform a POST request to create multiple resources at once.\n\t * @param body The object or array of objects to send in the request body.\n\t * @param params Optional query or request parameters.\n\t * @param resolveFn Optional custom resolve handler to parse the response array.\n\t * @returns A promise resolving to an array of ElementType.\n\t */\n\tprotected doCreateArray(\n\t\tbody: object | object[] | null,\n\t\tparams: Record<string, unknown>,\n\t\tresolveFn: ResolveHandler<ElementType[]> = this.populateArray,\n\t): Promise<ElementType[]> {\n\t\treturn this.createPromise(\"POST\", params, resolveFn, body);\n\t}\n\n\t/**\n\t * Perform an UPDATE (PUT/PATCH) request to modify an existing resource by its ID.\n\t * @param id The ID of the item to update.\n\t * @param body The updated fields to send in the request body.\n\t * @param params Optional query or request parameters.\n\t * @param resolveFn Optional custom resolve handler to parse the response object.\n\t * @param method The HTTP method to use, typically PUT or PATCH.\n\t * @returns A promise resolving to the updated ElementType instance.\n\t */\n\tprotected doUpdate(\n\t\tid: string | number,\n\t\tbody: Record<string, unknown> | null,\n\t\tparams: Record<string, unknown>,\n\t\tresolveFn = this.populateObjectFromJsonRoot,\n\t\tmethod: HttpMethod = \"PUT\",\n\t): Promise<ElementType> {\n\t\treturn this.createPromise(method, { ...params, id }, resolveFn, body);\n\t}\n\n\t/**\n\t * Parse a JSON response that contains a single item under a known root element name.\n\t * @param json The raw JSON object returned by the API.\n\t * @param headers The response headers.\n\t * @returns The parsed ElementType instance.\n\t * @throws Error if the expected root element name is missing.\n\t */\n\tprotected populateObjectFromJsonRoot(\n\t\tjson: Record<string, unknown>,\n\t\theaders: Headers,\n\t): ElementType {\n\t\tlet jsonData: Record<string, unknown> = json;\n\n\t\tconst rootElementName = this.rootElementNameSingular;\n\t\tif (rootElementName) {\n\t\t\tconst picked = jsonData[rootElementName];\n\n\t\t\tif (!this.isRecord(picked)) {\n\t\t\t\tthrow new Error(`Missing property '${rootElementName}' in JSON object`);\n\t\t\t}\n\n\t\t\tjsonData = picked;\n\t\t}\n\n\t\treturn this.populateObjectFromJson(jsonData, headers) as ElementType;\n\t}\n\n\t/**\n\t * Parse a JSON response that contains a secondary item under a known secondary root element name.\n\t * @param json The raw JSON object returned by the API.\n\t * @param headers The response headers.\n\t * @returns The parsed SecondaryType instance.\n\t * @throws Error if the expected secondary element name is missing.\n\t */\n\tprotected populateSecondaryObjectFromJsonRoot(\n\t\tjson: Record<string, unknown>,\n\t\theaders: Headers,\n\t): SecondaryType {\n\t\tconst root = this.secondaryElementNameSingular;\n\t\tconst record = json as Record<string, unknown>;\n\n\t\tconst itemJson = record[root];\n\t\tif (typeof itemJson !== \"object\" || itemJson === null) {\n\t\t\tthrow new Error(\n\t\t\t\t`Missing expected secondary property '${root}' in JSON response.`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.populateObjectFromJson(\n\t\t\titemJson as Record<string, unknown>,\n\t\t\theaders,\n\t\t\ttrue,\n\t\t) as SecondaryType;\n\t}\n\n\t/**\n\t * Parse a JSON response that contains a secondary item.\n\t * @param json The raw JSON object returned by the API.\n\t * @param headers The response headers.\n\t * @returns The parsed SecondaryType instance.\n\t */\n\tprotected populateSecondaryObjectFromJson(\n\t\tjson: Record<string, unknown>,\n\t\theaders: Headers,\n\t): SecondaryType {\n\t\treturn this.populateObjectFromJson(json, headers, true) as SecondaryType;\n\t}\n\n\t/**\n\t * Parse a JSON response that contains an array of items along with bulk result details.\n\t * @param json The raw JSON object returned by the API.\n\t * @param headers The response headers.\n\t * @returns A BulkResult object containing items and potential errors.\n\t * @throws Error if the expected root element is missing or not an array.\n\t */\n\tprotected populateArrayFromJsonBulk(\n\t\tjson: Record<string, unknown>,\n\t\theaders: Headers,\n\t): BulkResult<ElementType> {\n\t\tconst root = this.rootElementName;\n\t\tconst jsonArray = json[root];\n\n\t\tif (!Array.isArray(jsonArray)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Expected an array under '${root}' but received: ${typeof jsonArray}`,\n\t\t\t);\n\t\t}\n\n\t\tconst items: ElementType[] = jsonArray.map(\n\t\t\t(obj) => this.populateObjectFromJson(obj, headers) as ElementType,\n\t\t);\n\n\t\tconst errors = Array.isArray(json.errors)\n\t\t\t? (json.errors as BulkResult[\"errors\"])\n\t\t\t: [];\n\n\t\treturn {\n\t\t\terrors,\n\t\t\titems,\n\t\t};\n\t}\n\n\t/**\n\t * Parse a JSON response that contains an array of items.\n\t * If pagination headers are detected, returns a PaginatedResult.\n\t * Otherwise, returns a plain array of ElementType.\n\t * @param json The raw JSON object returned by the API.\n\t * @param headers The response headers.\n\t */\n\tprotected populateArrayFromJson(\n\t\tjson: Record<string, unknown>,\n\t\theaders: Headers,\n\t): PaginatedResult<ElementType> | ElementType[] {\n\t\tconst array = this.populateArray(json, headers);\n\t\treturn this.isPaginated(headers)\n\t\t\t? new PaginatedResult<ElementType>(array, headers)\n\t\t\t: array;\n\t}\n\n\t/**\n\t * Parse a JSON response that contains an array of items.\n\t * This method returns a plain array and does not consider pagination.\n\t * @param json The raw JSON object returned by the API.\n\t * @param headers The response headers.\n\t */\n\tprotected populateArray(\n\t\tjson: Record<string, unknown>,\n\t\theaders: Headers,\n\t): ElementType[] {\n\t\tconst root = this.rootElementName;\n\t\tconst jsonArray = json[root];\n\n\t\tif (!Array.isArray(jsonArray)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Expected an array under '${root}' but received: ${typeof jsonArray}`,\n\t\t\t);\n\t\t}\n\n\t\treturn jsonArray.map(\n\t\t\t(obj: Record<string, unknown>) =>\n\t\t\t\tthis.populateObjectFromJson(obj, headers) as ElementType,\n\t\t);\n\t}\n\n\t/**\n\t * Parse a JSON response that contains a cursor-paginated array of items.\n\t * @param json The raw JSON object returned by the API.\n\t * @param headers The response headers.\n\t */\n\tprotected populateArrayFromJsonCursor(\n\t\tjson: Record<string, unknown>,\n\t\theaders: Headers,\n\t): CursorPaginatedResult<ElementType> {\n\t\tconst root = this.rootElementName;\n\t\tconst jsonArray = json[root];\n\n\t\tif (!Array.isArray(jsonArray)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Expected an array under '${root}' for cursor pagination but received: ${typeof jsonArray}`,\n\t\t\t);\n\t\t}\n\n\t\tconst items = jsonArray.map(\n\t\t\t(obj: Record<string, unknown>) =>\n\t\t\t\tthis.populateObjectFromJson(obj, headers) as ElementType,\n\t\t);\n\n\t\treturn new CursorPaginatedResult<ElementType>(items, headers);\n\t}\n\n\t/**\n\t * Parse a JSON object into either an ElementType or a SecondaryType instance.\n\t * @param json The raw JSON object returned by the API.\n\t * @param _headers The response headers (if needed).\n\t * @param secondary If true, use the secondaryElementClass instead of elementClass.\n\t */\n\tprotected populateObjectFromJson(\n\t\tjson: Record<string, unknown>,\n\t\t_headers: Headers,\n\t\tsecondary = false,\n\t): ElementType | SecondaryType {\n\t\tconst cls = secondary ? this.secondaryElementClass : this.elementClass;\n\t\treturn new cls(json);\n\t}\n\n\t/**\n\t * Return the raw JSON as-is.\n\t * @param json The raw JSON object or array returned by the API.\n\t * @param _headers The response headers (if needed).\n\t */\n\tprotected returnBareJSON<T>(json: unknown, _headers: Headers): T {\n\t\treturn json as T;\n\t}\n\n\t/**\n\t * Convert a single object into an array if it's not already an array.\n\t * @param raw_body The raw request body.\n\t */\n\tprotected objToArray(\n\t\traw_body: Record<string, unknown> | Record<string, unknown>[],\n\t): Record<string, unknown>[] {\n\t\treturn Array.isArray(raw_body) ? raw_body : [raw_body];\n\t}\n\n\t/**\n\t * Create a Promise that sends an HTTP request and resolves with a parsed response.\n\t * @param method The HTTP method (GET, POST, PUT, DELETE, etc.).\n\t * @param params Query or request parameters.\n\t * @param resolveFn A function to resolve and parse the JSON response.\n\t * @param body The request body, if applicable.\n\t * @param uri An explicit URI to use for the request. If not provided, prefixURI is used.\n\t */\n\tprotected async createPromise<T>(\n\t\tmethod: HttpMethod,\n\t\tparams: Record<string, unknown>,\n\t\tresolveFn: ResolveHandler<T>,\n\t\tbody: object | object[] | null,\n\t\turi: string | null = null,\n\t): Promise<T> {\n\t\tconst request = await this.prepareRequest(method, body, params, uri);\n\t\treturn resolveFn.call(\n\t\t\tthis,\n\t\t\trequest.response.json,\n\t\t\trequest.response.headers,\n\t\t);\n\t}\n\n\t/**\n\t * Prepare the API request by creating a new ApiRequest instance using the static async factory method.\n\t * @param method The HTTP method.\n\t * @param body The request body.\n\t * @param params The request parameters.\n\t * @param uri An explicit URI for the request or null.\n\t */\n\tprotected async prepareRequest(\n\t\tmethod: HttpMethod,\n\t\tbody: object | object[] | null,\n\t\tparams: Record<string, unknown>,\n\t\turi: string | null,\n\t): Promise<ApiRequestWithResponse> {\n\t\treturn await ApiRequest.create(\n\t\t\tthis.getUri(uri),\n\t\t\tmethod,\n\t\t\tbody,\n\t\t\tparams,\n\t\t\tthis.clientData,\n\t\t);\n\t}\n\n\t/**\n\t * Determine the URI for the request. If uri is not provided, use prefixURI.\n\t * @param uri An explicit URI or null.\n\t * @throws Error if no URI or prefixURI is provided.\n\t */\n\tprotected getUri(uri: string | null): string {\n\t\tconst childClass = this.constructor as typeof BaseCollection;\n\t\tconst resolvedUri = uri ?? childClass.prefixURI;\n\t\tif (!resolvedUri) {\n\t\t\tthrow new Error(\n\t\t\t\t\"No URI or prefixURI provided. Ensure the subclass defines a static prefixURI or pass a URI explicitly.\",\n\t\t\t);\n\t\t}\n\t\treturn resolvedUri;\n\t}\n\n\tprotected isResponseTooBig(headers: Headers): boolean {\n\t\treturn headers.has(\"x-response-too-big\");\n\t}\n\n\t/**\n\t * Determine if the response headers indicate pagination.\n\t * @param headers The response headers.\n\t */\n\tprivate isPaginated(headers: Headers): boolean {\n\t\treturn (\n\t\t\theaders.has(\"x-pagination-total-count\") &&\n\t\t\theaders.has(\"x-pagination-page\")\n\t\t);\n\t}\n\n\t/**\n\t * Runtime type guard for narrowing `unknown` to `Record<string, unknown>`.\n\t *\n\t * @param value The value to test.\n\t */\n\tprivate isRecord(value: unknown): value is Record<string, unknown> {\n\t\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n\t}\n}\n","import type { PaginatedResult } from \"../interfaces/paginated_result.js\";\nimport { Branch } from \"../models/branch.js\";\nimport type {\n\tBranchDeleted,\n\tBranchMerged,\n\tBranchParams,\n\tMergeBranchParams,\n} from \"../types/branches.js\";\nimport type {\n\tProjectOnly,\n\tProjectWithPagination,\n} from \"../types/common_get_params.js\";\nimport { BaseCollection } from \"./base_collection.js\";\n\nexport class Branches extends BaseCollection<Branch> {\n\tprotected static override prefixURI =\n\t\t\"projects/{!:project_id}/branches/{:id}\";\n\n\tprotected get elementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => Branch {\n\t\treturn Branch;\n\t}\n\n\tprotected override get rootElementName(): string {\n\t\treturn \"branches\";\n\t}\n\n\tprotected override get rootElementNameSingular(): string | null {\n\t\treturn \"branch\";\n\t}\n\n\tlist(\n\t\trequest_params: ProjectWithPagination,\n\t): Promise<PaginatedResult<Branch>> {\n\t\treturn this.doList(request_params) as Promise<PaginatedResult<Branch>>;\n\t}\n\n\tcreate(\n\t\tbranch_params: BranchParams,\n\t\trequest_params: ProjectOnly,\n\t): Promise<Branch> {\n\t\treturn this.doCreate(\n\t\t\tbranch_params,\n\t\t\trequest_params,\n\t\t\tthis.populateObjectFromJsonRoot,\n\t\t);\n\t}\n\n\tget(\n\t\tbranch_id: string | number,\n\t\trequest_params: ProjectOnly,\n\t): Promise<Branch> {\n\t\treturn this.doGet(branch_id, request_params);\n\t}\n\n\tupdate(\n\t\tbranch_id: string | number,\n\t\tbranch_params: BranchParams,\n\t\trequest_params: ProjectOnly,\n\t): Promise<Branch> {\n\t\treturn this.doUpdate(branch_id, branch_params, request_params);\n\t}\n\n\tdelete(\n\t\tbranch_id: string | number,\n\t\trequest_params: ProjectOnly,\n\t): Promise<BranchDeleted> {\n\t\treturn this.doDelete<BranchDeleted>(branch_id, request_params);\n\t}\n\n\tmerge(\n\t\tbranch_id: string | number,\n\t\trequest_params: ProjectOnly,\n\t\tbody: MergeBranchParams = {},\n\t): Promise<BranchMerged> {\n\t\tconst params = {\n\t\t\t...request_params,\n\t\t\t...{ id: branch_id },\n\t\t};\n\n\t\treturn this.createPromise<BranchMerged>(\n\t\t\t\"POST\",\n\t\t\tparams,\n\t\t\tthis.returnBareJSON<BranchMerged>,\n\t\t\tbody,\n\t\t\t\"projects/{!:project_id}/branches/{:id}/merge\",\n\t\t);\n\t}\n}\n","import type { Comment as CommentInterface } from \"../interfaces/comment.js\";\nimport { BaseModel } from \"./base_model.js\";\n\nexport class Comment extends BaseModel implements CommentInterface {\n\tdeclare comment_id: number;\n\tdeclare key_id: number;\n\tdeclare comment: string;\n\tdeclare added_by: number;\n\tdeclare added_by_email: string;\n\tdeclare added_at: string;\n\tdeclare added_at_timestamp: number;\n}\n","import type { PaginatedResult } from \"../interfaces/paginated_result.js\";\nimport { Comment } from \"../models/comment.js\";\nimport type {\n\tCommentData,\n\tCommentDeleted,\n\tKeyProjectPagination,\n\tProjectAndKey,\n} from \"../types/comments.js\";\nimport type { ProjectWithPagination } from \"../types/common_get_params.js\";\nimport { BaseCollection } from \"./base_collection.js\";\n\nexport class Comments extends BaseCollection<Comment> {\n\tprotected static override prefixURI =\n\t\t\"projects/{!:project_id}/keys/{!:key_id}/comments/{:id}\";\n\n\tprotected get elementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => Comment {\n\t\treturn Comment;\n\t}\n\n\tprotected override get rootElementName(): string {\n\t\treturn \"comments\";\n\t}\n\n\tprotected override get rootElementNameSingular(): string | null {\n\t\treturn \"comment\";\n\t}\n\n\tlist(\n\t\trequest_params: KeyProjectPagination,\n\t): Promise<PaginatedResult<Comment>> {\n\t\treturn this.doList(request_params) as Promise<PaginatedResult<Comment>>;\n\t}\n\n\tcreate(\n\t\tcomment_params: CommentData | CommentData[],\n\t\trequest_params: ProjectAndKey,\n\t): Promise<Comment[]> {\n\t\tconst body = { comments: this.objToArray(comment_params) };\n\n\t\treturn this.doCreateArray(body, request_params);\n\t}\n\n\tget(\n\t\tcomment_id: string | number,\n\t\trequest_params: ProjectAndKey,\n\t): Promise<Comment> {\n\t\treturn this.doGet(comment_id, request_params);\n\t}\n\n\tdelete(\n\t\tcomment_id: string | number,\n\t\trequest_params: ProjectAndKey,\n\t): Promise<CommentDeleted> {\n\t\treturn this.doDelete(comment_id, request_params);\n\t}\n\n\tlist_project_comments(\n\t\tparams: ProjectWithPagination,\n\t): Promise<PaginatedResult<Comment>> {\n\t\treturn this.createPromise(\n\t\t\t\"GET\",\n\t\t\tparams,\n\t\t\tthis.populateArrayFromJson,\n\t\t\tnull,\n\t\t\t\"projects/{!:project_id}/comments\",\n\t\t) as Promise<PaginatedResult<Comment>>;\n\t}\n}\n","import type { Contributor as ContributorInterface } from \"../interfaces/contributor.js\";\nimport { BaseModel } from \"./base_model.js\";\n\nexport class Contributor extends BaseModel implements ContributorInterface {\n\tdeclare user_id: number;\n\tdeclare email: string;\n\tdeclare fullname: string;\n\tdeclare created_at: string;\n\tdeclare created_at_timestamp: number;\n\tdeclare is_admin: boolean; //deprecated\n\tdeclare is_reviewer: boolean; //deprecated\n\tdeclare languages: Array<{\n\t\tlang_id: number;\n\t\tlang_iso: string;\n\t\tlang_name: string;\n\t\tis_writable: boolean;\n\t}>;\n\tdeclare admin_rights: string[];\n\tdeclare role_id: number;\n\tdeclare uuid?: string;\n}\n","import type { PaginatedResult } from \"../interfaces/paginated_result.js\";\nimport { Contributor } from \"../models/contributor.js\";\nimport type {\n\tProjectOnly,\n\tProjectWithPagination,\n} from \"../types/common_get_params.js\";\nimport type {\n\tContributorCreateData,\n\tContributorDeleted,\n\tContributorUpdateData,\n} from \"../types/contributors.js\";\nimport { BaseCollection } from \"./base_collection.js\";\n\nexport class Contributors extends BaseCollection<Contributor> {\n\tprotected static override prefixURI =\n\t\t\"projects/{!:project_id}/contributors/{:id}\";\n\n\tprotected get elementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => Contributor {\n\t\treturn Contributor;\n\t}\n\n\tprotected override get rootElementName(): string {\n\t\treturn \"contributors\";\n\t}\n\n\tprotected override get rootElementNameSingular(): string | null {\n\t\treturn \"contributor\";\n\t}\n\n\tlist(\n\t\trequest_params: ProjectWithPagination,\n\t): Promise<PaginatedResult<Contributor>> {\n\t\treturn this.doList(request_params) as Promise<PaginatedResult<Contributor>>;\n\t}\n\n\tcreate(\n\t\tcontributor_params: ContributorCreateData | ContributorCreateData[],\n\t\trequest_params: ProjectOnly,\n\t): Promise<Contributor[]> {\n\t\tconst body = { contributors: this.objToArray(contributor_params) };\n\n\t\treturn this.doCreateArray(body, request_params);\n\t}\n\n\tget(\n\t\tcontributor_id: string | number,\n\t\trequest_params: ProjectOnly,\n\t): Promise<Contributor> {\n\t\treturn this.doGet(contributor_id, request_params);\n\t}\n\n\tme(request_params: ProjectOnly): Promise<Contributor> {\n\t\treturn this.doGet(\"me\", request_params);\n\t}\n\n\tupdate(\n\t\tcontributor_id: string | number,\n\t\tcontributor_params: ContributorUpdateData,\n\t\trequest_params: ProjectOnly,\n\t): Promise<Contributor> {\n\t\treturn this.doUpdate(contributor_id, contributor_params, request_params);\n\t}\n\n\tdelete(\n\t\tcontributor_id: string | number,\n\t\trequest_params: ProjectOnly,\n\t): Promise<ContributorDeleted> {\n\t\treturn this.doDelete(contributor_id, request_params);\n\t}\n}\n","import type { File as FileInterface } from \"../interfaces/file.js\";\nimport { BaseModel } from \"./base_model.js\";\n\nexport class File extends BaseModel implements FileInterface {\n\tdeclare file_id: number;\n\tdeclare filename: string;\n\tdeclare key_count: number;\n}\n","import type { QueuedProcess as QueuedProcessInterface } from \"../interfaces/queued_process.js\";\nimport type { QueuedProcessDetails } from \"../types/queued_process_details.js\";\nimport { BaseModel } from \"./base_model.js\";\n\nexport class QueuedProcess extends BaseModel implements QueuedProcessInterface {\n\tdeclare process_id: string;\n\tdeclare type: string;\n\tdeclare status: string;\n\tdeclare message: string;\n\tdeclare created_by: number;\n\tdeclare created_by_email: string;\n\tdeclare created_at: string;\n\tdeclare created_at_timestamp: number;\n\tdeclare details: QueuedProcessDetails;\n}\n","/**\n * Emits a warning to the console unless `silent` is true.\n *\n * @param silent - If true, suppresses the log output.\n * @param args - The items to log, passed to `console.warn`.\n */\nexport function warn(silent: boolean, ...args: unknown[]): void {\n\tif (silent) return;\n\n\tconsole.warn(...args);\n}\n","import type { PaginatedResult } from \"../interfaces/paginated_result.js\";\nimport { File } from \"../models/file.js\";\nimport { QueuedProcess } from \"../models/queued_process.js\";\nimport type { ProjectOnly } from \"../types/common_get_params.js\";\nimport type {\n\tDownloadBundle,\n\tDownloadFileParams,\n\tFileDeleted,\n\tListFileParams,\n\tUploadFileParams,\n} from \"../types/files.js\";\nimport { warn } from \"../utils/logger.js\";\nimport { BaseCollection } from \"./base_collection.js\";\n\nexport class Files extends BaseCollection<File, QueuedProcess> {\n\tprotected static override prefixURI = \"projects/{!:project_id}/files/{:id}\";\n\n\tprotected get elementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => File {\n\t\treturn File;\n\t}\n\n\tprotected override get rootElementName(): string {\n\t\treturn \"files\";\n\t}\n\n\tprotected override get secondaryElementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => QueuedProcess {\n\t\treturn QueuedProcess;\n\t}\n\n\tprotected override get secondaryElementNameSingular(): string {\n\t\treturn \"process\";\n\t}\n\n\tprotected override returnBareJSON<T>(\n\t\tjson: Record<string, unknown> | Record<string, unknown>[],\n\t\theaders: Headers,\n\t): T {\n\t\tif (this.isResponseTooBig(headers)) {\n\t\t\twarn(\n\t\t\t\tthis.clientData.silent,\n\t\t\t\t\"\\x1b[33m\\x1b[1mWarning:\\x1b[0m Project too big for sync export. Please use our async export lokaliseApi.files().async_download() method.\",\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\t...super.returnBareJSON<T>(json, headers),\n\t\t\tresponseTooBig: this.isResponseTooBig(headers),\n\t\t};\n\t}\n\n\tlist(request_params: ListFileParams): Promise<PaginatedResult<File>> {\n\t\treturn this.doList(request_params) as Promise<PaginatedResult<File>>;\n\t}\n\n\tupload(project_id: string, upload: UploadFileParams): Promise<QueuedProcess> {\n\t\treturn this.createPromise(\n\t\t\t\"POST\",\n\t\t\t{ project_id },\n\t\t\tthis.populateSecondaryObjectFromJsonRoot,\n\t\t\tupload,\n\t\t\t\"projects/{!:project_id}/files/upload\",\n\t\t);\n\t}\n\n\tdownload(\n\t\tproject_id: string,\n\t\tdownload: DownloadFileParams,\n\t): Promise<DownloadBundle> {\n\t\treturn this.createPromise(\n\t\t\t\"POST\",\n\t\t\t{ project_id },\n\t\t\tthis.returnBareJSON<DownloadBundle>,\n\t\t\tdownload,\n\t\t\t\"projects/{!:project_id}/files/download\",\n\t\t);\n\t}\n\n\tasync_download(\n\t\tproject_id: string,\n\t\tdownload: DownloadFileParams,\n\t): Promise<QueuedProcess> {\n\t\treturn this.createPromise(\n\t\t\t\"POST\",\n\t\t\t{ project_id },\n\t\t\tthis.populateSecondaryObjectFromJson,\n\t\t\tdownload,\n\t\t\t\"projects/{!:project_id}/files/async-download\",\n\t\t);\n\t}\n\n\tdelete(\n\t\tfile_id: string | number,\n\t\trequest_params: ProjectOnly,\n\t): Promise<FileDeleted> {\n\t\treturn this.doDelete(file_id, request_params);\n\t}\n}\n","import type { GlossaryTerm as GlossaryTermInterface } from \"../interfaces/glossary_term.js\";\nimport { BaseModel } from \"./base_model.js\";\n\nexport class GlossaryTerm extends BaseModel implements GlossaryTermInterface {\n\tdeclare id: number;\n\tdeclare projectId: string;\n\tdeclare term: string;\n\tdeclare description: string;\n\tdeclare caseSensitive: boolean;\n\tdeclare translatable: boolean;\n\tdeclare forbidden: boolean;\n\tdeclare translations: Array<{\n\t\tlangId: number;\n\t\tlangName: string;\n\t\tlangIso: string;\n\t\ttranslation: string;\n\t\tdescription: string;\n\t}>;\n\tdeclare tags: string[];\n\tdeclare createdAt: string;\n\tdeclare updatedAt: string | null;\n}\n","import type { BulkResult } from \"../interfaces/bulk_result.js\";\nimport type { CursorPaginatedResult } from \"../interfaces/cursor_paginated_result.js\";\nimport { GlossaryTerm } from \"../models/glossary_term.js\";\nimport type { ProjectOnly } from \"../types/common_get_params.js\";\nimport type {\n\tCreateTermsParams,\n\tListTermsParams,\n\tTermsDeleted,\n\tUpdateTermsParams,\n} from \"../types/glossary_terms.js\";\nimport { BaseCollection } from \"./base_collection.js\";\n\nexport class GlossaryTerms extends BaseCollection<GlossaryTerm> {\n\tprotected static override prefixURI =\n\t\t\"projects/{!:project_id}/glossary-terms/{:id}\";\n\n\tprotected get elementClass(): new (\n\t\tjson: Record<string, unknown>,\n\t) => GlossaryTerm {\n\t\treturn GlossaryTerm;\n\t}\n\n\tprotected override get rootElementName(): string {\n\t\treturn \"data\";\n\t}\n\n\tprotected override get rootElementNameSingular(): string | null {\n\t\treturn \"data\";\n\t}\n\n\tget(\n\t\tterm_id: