@gpc-cli/api 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -183,7 +183,7 @@ interface ReviewsListOptions {
183
183
  translationLanguage?: string;
184
184
  }
185
185
  type VitalsMetricSet = "crashRateMetricSet" | "anrRateMetricSet" | "excessiveWakeupRateMetricSet" | "stuckBackgroundWakelockRateMetricSet" | "slowStartRateMetricSet" | "slowRenderingRateMetricSet" | "errorCountMetricSet";
186
- type ReportingDimension = "apiLevel" | "versionCode" | "deviceModel" | "deviceType" | "countryCode" | "deviceRamBucket" | "deviceSocName" | "deviceCpuMakeModel" | "deviceGlEsVersion" | "deviceVulkanVersion" | "deviceOpenGlVersion" | "deviceBrand";
186
+ type ReportingDimension = "apiLevel" | "versionCode" | "deviceModel" | "deviceType" | "countryCode" | "deviceRamBucket" | "deviceSocName" | "deviceCpuMakeModel" | "deviceGlEsVersion" | "deviceVulkanVersion" | "deviceOpenGlVersion" | "deviceBrand" | "startType";
187
187
  type ReportingAggregation = "DAILY" | "HOURLY";
188
188
  interface MetricSetQuery {
189
189
  metrics: string[];
@@ -752,6 +752,28 @@ interface InAppProductsBatchGetRequest {
752
752
  packageName: string;
753
753
  sku: string[];
754
754
  }
755
+ interface UploadProgressEvent {
756
+ /** Bytes uploaded so far */
757
+ bytesUploaded: number;
758
+ /** Total file size in bytes */
759
+ totalBytes: number;
760
+ /** Percentage 0-100 */
761
+ percent: number;
762
+ /** Bytes per second throughput */
763
+ bytesPerSecond: number;
764
+ /** Estimated seconds remaining */
765
+ etaSeconds: number;
766
+ }
767
+ interface ResumableUploadOptions {
768
+ /** Chunk size in bytes. Must be a multiple of 256 KB. Default: 8 MB */
769
+ chunkSize?: number;
770
+ /** Progress callback fired after each chunk */
771
+ onProgress?: (event: UploadProgressEvent) => void;
772
+ /** Existing session URI to resume a previous upload */
773
+ resumeSessionUri?: string;
774
+ /** Maximum resume attempts per chunk before giving up. Default: 5 */
775
+ maxResumeAttempts?: number;
776
+ }
755
777
 
756
778
  interface PlayApiClient {
757
779
  edits: {
@@ -768,7 +790,7 @@ interface PlayApiClient {
768
790
  };
769
791
  bundles: {
770
792
  list(packageName: string, editId: string): Promise<Bundle[]>;
771
- upload(packageName: string, editId: string, filePath: string): Promise<Bundle>;
793
+ upload(packageName: string, editId: string, filePath: string, uploadOptions?: ResumableUploadOptions): Promise<Bundle>;
772
794
  };
773
795
  tracks: {
774
796
  list(packageName: string, editId: string): Promise<Track[]>;
@@ -1044,6 +1066,7 @@ interface HttpClient {
1044
1066
  patch<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;
1045
1067
  delete<T>(path: string): Promise<ApiResponse<T>>;
1046
1068
  upload<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;
1069
+ uploadResumable<T>(path: string, filePath: string, contentType: string, options?: ResumableUploadOptions): Promise<ApiResponse<T>>;
1047
1070
  uploadInternal<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;
1048
1071
  download(path: string): Promise<ArrayBuffer>;
1049
1072
  }
@@ -1097,4 +1120,7 @@ declare class PlayApiError extends Error {
1097
1120
  };
1098
1121
  }
1099
1122
 
1100
- export { type Achievement, type Anomaly, type AnomalyDetectionResponse, type ApiClientOptions, type ApiResponse, type ApkInfo, type AppDetails, type AppEdit, type AppRecoveriesListResponse, type AppRecoveryAction, type AppRecoveryTargeting, type BasePlan, type BasePlanMigratePricesRequest, type Bundle, type BundleListResponse, type ConvertRegionPricesRequest, type ConvertRegionPricesResponse, type ConvertedRegionPrice, type CountryAvailability, type CreateAppRecoveryActionRequest, type CustomApp, type CustomAppsListResponse, type DataSafety, type DataSafetyDataType, type DataSafetyPurpose, type DeobfuscationFile, type DeobfuscationUploadResponse, type DeveloperComment, type DeveloperPermission, type DeviceGroup, type DeviceSelector, type DeviceTier, type DeviceTierConfig, type DeviceTierConfigsListResponse, type EnterpriseApiClient, type ErrorIssue, type ErrorIssuesResponse, type ErrorReport, type ErrorReportsResponse, type ExternalTransaction, type ExternalTransactionAmount, type ExternalTransactionRefund, type ExternallyHostedApk, type ExternallyHostedApkResponse, type GameEvent, type GamesApiClient, type GeneratedApk, type GeneratedApksPerVersion, type Grant, type GrantsListResponse, type HttpClient, type Image, type ImageType, type ImageUploadResponse, type ImagesDeleteAllResponse, type ImagesListResponse, type InAppProduct, type InAppProductListing, type InAppProductsBatchGetRequest, type InAppProductsBatchUpdateRequest, type InAppProductsBatchUpdateResponse, type InAppProductsListResponse, type InternalAppSharingArtifact, type Leaderboard, type LeaderboardScore, type Listing, type ListingsListResponse, type MetricRow, type MetricSetQuery, type MetricSetResponse, type Money, type OffersListResponse, type OneTimeOffer, type OneTimeOfferRegionalConfig, type OneTimeOffersListResponse, type OneTimeProduct, type OneTimeProductListing, type OneTimeProductsListResponse, type PagedResponse, type PaginateOptions, type PlayApiClient, PlayApiError, type ProductPurchase, type PurchaseOption, type PurchaseOptionsListResponse, RATE_LIMIT_BUCKETS, type RateLimitBucket, type RateLimiter, type RegionalBasePlanConfig, type Release, type ReleaseNote, type ReleaseStatus, type ReportBucket, type ReportType, type ReportingAggregation, type ReportingApiClient, type ReportingDimension, type ReportsListResponse, type RetryLogEntry, type Review, type ReviewComment, type ReviewReplyRequest, type ReviewReplyResponse, type ReviewsListOptions, type ReviewsListResponse, type StatsDimension, type Subscription, type SubscriptionDeferRequest, type SubscriptionDeferResponse, type SubscriptionListing, type SubscriptionOffer, type SubscriptionOfferPhase, type SubscriptionPurchase, type SubscriptionPurchaseLineItem, type SubscriptionPurchaseV2, type SubscriptionsListResponse, type TaxAndComplianceSettings, type Testers, type TokenPagination, type Track, type TrackListResponse, type UploadResponse, type User, type UserComment, type UsersApiClient, type UsersListResponse, type VitalsMetricSet, type VoidedPurchase, type VoidedPurchasesListResponse, createApiClient, createEnterpriseClient, createGamesClient, createHttpClient, createRateLimiter, createReportingClient, createUsersClient, paginate, paginateAll, paginateParallel };
1123
+ /** Files below this threshold use simple upload instead. */
1124
+ declare const RESUMABLE_THRESHOLD: number;
1125
+
1126
+ export { type Achievement, type Anomaly, type AnomalyDetectionResponse, type ApiClientOptions, type ApiResponse, type ApkInfo, type AppDetails, type AppEdit, type AppRecoveriesListResponse, type AppRecoveryAction, type AppRecoveryTargeting, type BasePlan, type BasePlanMigratePricesRequest, type Bundle, type BundleListResponse, type ConvertRegionPricesRequest, type ConvertRegionPricesResponse, type ConvertedRegionPrice, type CountryAvailability, type CreateAppRecoveryActionRequest, type CustomApp, type CustomAppsListResponse, type DataSafety, type DataSafetyDataType, type DataSafetyPurpose, type DeobfuscationFile, type DeobfuscationUploadResponse, type DeveloperComment, type DeveloperPermission, type DeviceGroup, type DeviceSelector, type DeviceTier, type DeviceTierConfig, type DeviceTierConfigsListResponse, type EnterpriseApiClient, type ErrorIssue, type ErrorIssuesResponse, type ErrorReport, type ErrorReportsResponse, type ExternalTransaction, type ExternalTransactionAmount, type ExternalTransactionRefund, type ExternallyHostedApk, type ExternallyHostedApkResponse, type GameEvent, type GamesApiClient, type GeneratedApk, type GeneratedApksPerVersion, type Grant, type GrantsListResponse, type HttpClient, type Image, type ImageType, type ImageUploadResponse, type ImagesDeleteAllResponse, type ImagesListResponse, type InAppProduct, type InAppProductListing, type InAppProductsBatchGetRequest, type InAppProductsBatchUpdateRequest, type InAppProductsBatchUpdateResponse, type InAppProductsListResponse, type InternalAppSharingArtifact, type Leaderboard, type LeaderboardScore, type Listing, type ListingsListResponse, type MetricRow, type MetricSetQuery, type MetricSetResponse, type Money, type OffersListResponse, type OneTimeOffer, type OneTimeOfferRegionalConfig, type OneTimeOffersListResponse, type OneTimeProduct, type OneTimeProductListing, type OneTimeProductsListResponse, type PagedResponse, type PaginateOptions, type PlayApiClient, PlayApiError, type ProductPurchase, type PurchaseOption, type PurchaseOptionsListResponse, RATE_LIMIT_BUCKETS, RESUMABLE_THRESHOLD, type RateLimitBucket, type RateLimiter, type RegionalBasePlanConfig, type Release, type ReleaseNote, type ReleaseStatus, type ReportBucket, type ReportType, type ReportingAggregation, type ReportingApiClient, type ReportingDimension, type ReportsListResponse, type ResumableUploadOptions, type RetryLogEntry, type Review, type ReviewComment, type ReviewReplyRequest, type ReviewReplyResponse, type ReviewsListOptions, type ReviewsListResponse, type StatsDimension, type Subscription, type SubscriptionDeferRequest, type SubscriptionDeferResponse, type SubscriptionListing, type SubscriptionOffer, type SubscriptionOfferPhase, type SubscriptionPurchase, type SubscriptionPurchaseLineItem, type SubscriptionPurchaseV2, type SubscriptionsListResponse, type TaxAndComplianceSettings, type Testers, type TokenPagination, type Track, type TrackListResponse, type UploadProgressEvent, type UploadResponse, type User, type UserComment, type UsersApiClient, type UsersListResponse, type VitalsMetricSet, type VoidedPurchase, type VoidedPurchasesListResponse, createApiClient, createEnterpriseClient, createGamesClient, createHttpClient, createRateLimiter, createReportingClient, createUsersClient, paginate, paginateAll, paginateParallel };
package/dist/index.js CHANGED
@@ -21,8 +21,269 @@ var PlayApiError = class extends Error {
21
21
  };
22
22
 
23
23
  // src/http.ts
24
- import { readFile } from "fs/promises";
24
+ import { readFile, stat as stat2 } from "fs/promises";
25
25
  import { resolve, isAbsolute } from "path";
26
+
27
+ // src/resumable-upload.ts
28
+ import { open, stat } from "fs/promises";
29
+ var CHUNK_ALIGNMENT = 256 * 1024;
30
+ var DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024;
31
+ var RESUMABLE_THRESHOLD = 5 * 1024 * 1024;
32
+ function envInt(name) {
33
+ const val = process.env[name];
34
+ if (val === void 0) return void 0;
35
+ const n = Number(val);
36
+ return Number.isFinite(n) ? n : void 0;
37
+ }
38
+ function resolveChunkSize(explicit) {
39
+ const size = explicit ?? envInt("GPC_UPLOAD_CHUNK_SIZE") ?? DEFAULT_CHUNK_SIZE;
40
+ if (size < CHUNK_ALIGNMENT || size % CHUNK_ALIGNMENT !== 0) {
41
+ throw new PlayApiError(
42
+ `Chunk size must be a multiple of 256 KB (got ${size} bytes)`,
43
+ "UPLOAD_INVALID_CHUNK_SIZE",
44
+ void 0,
45
+ `Use a multiple of 262144 (256 KB). Common values: 1048576 (1 MB), 8388608 (8 MB), 16777216 (16 MB).`
46
+ );
47
+ }
48
+ return size;
49
+ }
50
+ function jitteredDelay(base, attempt, max) {
51
+ const exponential = base * 2 ** attempt;
52
+ const capped = Math.min(exponential, max);
53
+ return capped * (0.5 + Math.random() * 0.5);
54
+ }
55
+ async function resumableUpload(uploadUrl, filePath, contentType, ctx, options) {
56
+ const chunkSize = resolveChunkSize(options?.chunkSize);
57
+ const maxResumeAttempts = options?.maxResumeAttempts ?? 5;
58
+ const onProgress = options?.onProgress;
59
+ const fileStats = await stat(filePath);
60
+ const totalBytes = fileStats.size;
61
+ let sessionUri = options?.resumeSessionUri;
62
+ if (!sessionUri) {
63
+ sessionUri = await initiateSession(uploadUrl, contentType, totalBytes, ctx);
64
+ }
65
+ const startTime = Date.now();
66
+ let offset = 0;
67
+ if (options?.resumeSessionUri) {
68
+ offset = await queryProgress(sessionUri, totalBytes, ctx);
69
+ }
70
+ let fh;
71
+ try {
72
+ fh = await open(filePath, "r");
73
+ const chunkBuffer = Buffer.alloc(chunkSize);
74
+ while (offset < totalBytes) {
75
+ const remaining = totalBytes - offset;
76
+ const bytesToRead = Math.min(chunkSize, remaining);
77
+ const { bytesRead } = await fh.read(chunkBuffer, 0, bytesToRead, offset);
78
+ if (bytesRead === 0) break;
79
+ const chunk = Buffer.from(chunkBuffer.buffer, chunkBuffer.byteOffset, bytesRead);
80
+ const rangeEnd = offset + bytesRead - 1;
81
+ const contentRange = `bytes ${offset}-${rangeEnd}/${totalBytes}`;
82
+ let result;
83
+ for (let attempt = 0; attempt <= maxResumeAttempts; attempt++) {
84
+ if (attempt > 0) {
85
+ const delay = jitteredDelay(1e3, attempt - 1, 3e4);
86
+ await new Promise((r) => setTimeout(r, delay));
87
+ try {
88
+ const serverOffset = await queryProgress(sessionUri, totalBytes, ctx);
89
+ if (serverOffset >= offset + bytesRead) {
90
+ result = { complete: false };
91
+ break;
92
+ }
93
+ if (serverOffset > offset) {
94
+ }
95
+ } catch {
96
+ }
97
+ ctx.onRetry?.({
98
+ attempt,
99
+ method: "PUT",
100
+ path: sessionUri,
101
+ error: `Chunk upload failed at offset ${offset}, retrying`,
102
+ delayMs: Math.round(delay),
103
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
104
+ });
105
+ }
106
+ result = await sendChunk(sessionUri, chunk, contentRange, ctx);
107
+ if (result) break;
108
+ }
109
+ if (!result) {
110
+ throw new PlayApiError(
111
+ `Upload failed: chunk at offset ${offset} could not be sent after ${maxResumeAttempts + 1} attempts`,
112
+ "UPLOAD_CHUNK_FAILED",
113
+ void 0,
114
+ `The upload session is still valid for up to 1 week. Resume with: --resume-uri "${sessionUri}"`
115
+ );
116
+ }
117
+ offset += bytesRead;
118
+ if (onProgress) {
119
+ const elapsed = (Date.now() - startTime) / 1e3;
120
+ const bytesPerSecond = elapsed > 0 ? offset / elapsed : 0;
121
+ const remainingBytes = totalBytes - offset;
122
+ const etaSeconds = bytesPerSecond > 0 ? remainingBytes / bytesPerSecond : 0;
123
+ onProgress({
124
+ bytesUploaded: offset,
125
+ totalBytes,
126
+ percent: Math.round(offset / totalBytes * 100),
127
+ bytesPerSecond: Math.round(bytesPerSecond),
128
+ etaSeconds: Math.round(etaSeconds)
129
+ });
130
+ }
131
+ if (result.complete && result.response) {
132
+ return result.response;
133
+ }
134
+ }
135
+ throw new PlayApiError(
136
+ "Upload finished sending all bytes but did not receive a completion response",
137
+ "UPLOAD_NO_COMPLETION",
138
+ void 0,
139
+ "This is unexpected. Try uploading again."
140
+ );
141
+ } finally {
142
+ await fh?.close();
143
+ }
144
+ }
145
+ async function initiateSession(uploadUrl, contentType, totalBytes, ctx) {
146
+ const token = await ctx.getAccessToken();
147
+ const url = uploadUrl.includes("?") ? `${uploadUrl}&uploadType=resumable` : `${uploadUrl}?uploadType=resumable`;
148
+ const controller = new AbortController();
149
+ const timer = setTimeout(() => controller.abort(), 6e4);
150
+ let response;
151
+ try {
152
+ response = await fetch(url, {
153
+ method: "POST",
154
+ headers: {
155
+ Authorization: `Bearer ${token}`,
156
+ "X-Upload-Content-Type": contentType,
157
+ "X-Upload-Content-Length": String(totalBytes),
158
+ "Content-Length": "0"
159
+ },
160
+ signal: controller.signal
161
+ });
162
+ } finally {
163
+ clearTimeout(timer);
164
+ }
165
+ if (!response.ok) {
166
+ const body = await response.text();
167
+ throw new PlayApiError(
168
+ `Failed to initiate resumable upload: ${response.status} ${body.slice(0, 200)}`,
169
+ "UPLOAD_INITIATE_FAILED",
170
+ response.status,
171
+ "Check that the package name, edit ID, and credentials are correct."
172
+ );
173
+ }
174
+ const location = response.headers.get("Location");
175
+ if (!location) {
176
+ throw new PlayApiError(
177
+ "Resumable upload initiation did not return a session URI (Location header missing)",
178
+ "UPLOAD_NO_SESSION_URI",
179
+ response.status,
180
+ "This is a Google API issue. Try again."
181
+ );
182
+ }
183
+ return location;
184
+ }
185
+ async function sendChunk(sessionUri, chunk, contentRange, ctx) {
186
+ const token = await ctx.getAccessToken();
187
+ const chunkTimeoutMs = 3e4 + Math.ceil(chunk.byteLength / (1024 * 1024)) * 1e3;
188
+ const controller = new AbortController();
189
+ const timer = setTimeout(() => controller.abort(), chunkTimeoutMs);
190
+ let response;
191
+ try {
192
+ response = await fetch(sessionUri, {
193
+ method: "PUT",
194
+ headers: {
195
+ Authorization: `Bearer ${token}`,
196
+ "Content-Length": String(chunk.byteLength),
197
+ "Content-Range": contentRange
198
+ },
199
+ body: chunk,
200
+ signal: controller.signal
201
+ });
202
+ } catch {
203
+ return void 0;
204
+ } finally {
205
+ clearTimeout(timer);
206
+ }
207
+ if (response.status === 200 || response.status === 201) {
208
+ const text = await response.text();
209
+ const data = text ? JSON.parse(text) : {};
210
+ return { complete: true, response: { data, status: response.status } };
211
+ }
212
+ if (response.status === 308) {
213
+ await response.body?.cancel();
214
+ return { complete: false };
215
+ }
216
+ if (response.status === 404) {
217
+ throw new PlayApiError(
218
+ "Upload session not found. The session may have expired.",
219
+ "UPLOAD_SESSION_NOT_FOUND",
220
+ 404,
221
+ "Start a new upload. Resumable upload sessions are valid for up to 1 week."
222
+ );
223
+ }
224
+ if (response.status === 410) {
225
+ throw new PlayApiError(
226
+ "Upload session has expired.",
227
+ "UPLOAD_SESSION_EXPIRED",
228
+ 410,
229
+ "Start a new upload from the beginning."
230
+ );
231
+ }
232
+ if (response.status === 401) {
233
+ await response.body?.cancel();
234
+ return void 0;
235
+ }
236
+ if (response.status === 429 || response.status >= 500) {
237
+ await response.body?.cancel();
238
+ return void 0;
239
+ }
240
+ const body = await response.text();
241
+ throw new PlayApiError(
242
+ `Upload chunk failed with status ${response.status}: ${body.slice(0, 200)}`,
243
+ `UPLOAD_HTTP_${response.status}`,
244
+ response.status,
245
+ "The upload encountered an unexpected error."
246
+ );
247
+ }
248
+ async function queryProgress(sessionUri, totalBytes, ctx) {
249
+ const token = await ctx.getAccessToken();
250
+ const response = await fetch(sessionUri, {
251
+ method: "PUT",
252
+ headers: {
253
+ Authorization: `Bearer ${token}`,
254
+ "Content-Length": "0",
255
+ "Content-Range": `bytes */${totalBytes}`
256
+ }
257
+ });
258
+ if (response.status === 308) {
259
+ await response.body?.cancel();
260
+ const range = response.headers.get("Range");
261
+ if (range) {
262
+ const match = range.match(/bytes=0-(\d+)/);
263
+ if (match) {
264
+ return Number(match[1]) + 1;
265
+ }
266
+ }
267
+ return 0;
268
+ }
269
+ if (response.status === 200 || response.status === 201) {
270
+ await response.body?.cancel();
271
+ return totalBytes;
272
+ }
273
+ if (response.status === 404 || response.status === 410) {
274
+ await response.body?.cancel();
275
+ throw new PlayApiError(
276
+ "Upload session has expired while querying progress.",
277
+ "UPLOAD_SESSION_EXPIRED",
278
+ response.status,
279
+ "Start a new upload from the beginning."
280
+ );
281
+ }
282
+ await response.body?.cancel();
283
+ return 0;
284
+ }
285
+
286
+ // src/http.ts
26
287
  function stripHtml(text) {
27
288
  return text.replace(/<[^>]*>/g, " ").replace(/&[a-z]+;/gi, " ").replace(/\s+/g, " ").trim();
28
289
  }
@@ -60,14 +321,14 @@ function validateFilePath(filePath) {
60
321
  var BASE_URL = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications";
61
322
  var UPLOAD_BASE_URL = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications";
62
323
  var INTERNAL_SHARING_UPLOAD_BASE_URL = "https://androidpublisher.googleapis.com/upload/internalappsharing/v3/applications";
63
- function envInt(name) {
324
+ function envInt2(name) {
64
325
  const val = process.env[name];
65
326
  if (val === void 0) return void 0;
66
327
  const n = Number(val);
67
328
  return Number.isFinite(n) ? n : void 0;
68
329
  }
69
330
  function resolveOption(explicit, envName, fallback) {
70
- return explicit ?? envInt(envName) ?? fallback;
331
+ return explicit ?? envInt2(envName) ?? fallback;
71
332
  }
72
333
  function mapStatusToError(status, body) {
73
334
  switch (status) {
@@ -120,17 +381,17 @@ function mapStatusToError(status, body) {
120
381
  }
121
382
  }
122
383
  function isRetryable(status) {
123
- return status === 429 || status >= 500;
384
+ return status === 408 || status === 429 || status >= 500;
124
385
  }
125
- function jitteredDelay(base, attempt, max) {
386
+ function jitteredDelay2(base, attempt, max) {
126
387
  const exponential = base * 2 ** attempt;
127
388
  const capped = Math.min(exponential, max);
128
389
  return capped * (0.5 + Math.random() * 0.5);
129
390
  }
130
391
  function createHttpClient(options) {
131
- const maxRetries = resolveOption(options.maxRetries, "GPC_MAX_RETRIES", 3);
392
+ const maxRetries = resolveOption(options.maxRetries, "GPC_MAX_RETRIES", 5);
132
393
  const timeout = resolveOption(options.timeout, "GPC_TIMEOUT", 3e4);
133
- const uploadTimeoutExplicit = options.uploadTimeout ?? envInt("GPC_UPLOAD_TIMEOUT");
394
+ const uploadTimeoutExplicit = options.uploadTimeout ?? envInt2("GPC_UPLOAD_TIMEOUT");
134
395
  const baseDelay = resolveOption(options.baseDelay, "GPC_BASE_DELAY", 1e3);
135
396
  const maxDelay = resolveOption(options.maxDelay, "GPC_MAX_DELAY", 6e4);
136
397
  const onRetry = options.onRetry;
@@ -144,7 +405,7 @@ function createHttpClient(options) {
144
405
  let lastError;
145
406
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
146
407
  if (attempt > 0) {
147
- const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);
408
+ const delay = jitteredDelay2(baseDelay, attempt - 1, maxDelay);
148
409
  await new Promise((r) => setTimeout(r, delay));
149
410
  }
150
411
  const controller = new AbortController();
@@ -181,7 +442,7 @@ function createHttpClient(options) {
181
442
  );
182
443
  if (isRetryable(response.status) && attempt < maxRetries) {
183
444
  lastError = err;
184
- const delay = jitteredDelay(baseDelay, attempt, maxDelay);
445
+ const delay = jitteredDelay2(baseDelay, attempt, maxDelay);
185
446
  onRetry?.({
186
447
  attempt: attempt + 1,
187
448
  method,
@@ -217,7 +478,7 @@ function createHttpClient(options) {
217
478
  method,
218
479
  path,
219
480
  error: timeoutErr.message,
220
- delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
481
+ delayMs: Math.round(jitteredDelay2(baseDelay, attempt, maxDelay)),
221
482
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
222
483
  });
223
484
  continue;
@@ -237,7 +498,7 @@ function createHttpClient(options) {
237
498
  method,
238
499
  path,
239
500
  error: networkErr.message,
240
- delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
501
+ delayMs: Math.round(jitteredDelay2(baseDelay, attempt, maxDelay)),
241
502
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
242
503
  });
243
504
  continue;
@@ -268,7 +529,7 @@ function createHttpClient(options) {
268
529
  let lastError;
269
530
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
270
531
  if (attempt > 0) {
271
- const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);
532
+ const delay = jitteredDelay2(baseDelay, attempt - 1, maxDelay);
272
533
  await new Promise((r) => setTimeout(r, delay));
273
534
  }
274
535
  const controller = new AbortController();
@@ -302,7 +563,7 @@ function createHttpClient(options) {
302
563
  );
303
564
  if (isRetryable(response.status) && attempt < maxRetries) {
304
565
  lastError = err;
305
- const delay = jitteredDelay(baseDelay, attempt, maxDelay);
566
+ const delay = jitteredDelay2(baseDelay, attempt, maxDelay);
306
567
  onRetry?.({
307
568
  attempt: attempt + 1,
308
569
  method: "POST",
@@ -339,7 +600,7 @@ function createHttpClient(options) {
339
600
  method: "POST",
340
601
  path: `upload ${path}`,
341
602
  error: timeoutErr.message,
342
- delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
603
+ delayMs: Math.round(jitteredDelay2(baseDelay, attempt, maxDelay)),
343
604
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
344
605
  });
345
606
  continue;
@@ -359,7 +620,7 @@ function createHttpClient(options) {
359
620
  method: "POST",
360
621
  path: `upload ${path}`,
361
622
  error: networkErr.message,
362
- delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
623
+ delayMs: Math.round(jitteredDelay2(baseDelay, attempt, maxDelay)),
363
624
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
364
625
  });
365
626
  continue;
@@ -395,6 +656,25 @@ function createHttpClient(options) {
395
656
  upload(path, filePath, contentType) {
396
657
  return uploadRequest(path, filePath, contentType);
397
658
  },
659
+ async uploadResumable(path, filePath, contentType, uploadOptions) {
660
+ const safeFilePath = validateFilePath(filePath);
661
+ const fileStats = await stat2(safeFilePath);
662
+ const threshold = envInt2("GPC_UPLOAD_RESUMABLE_THRESHOLD") ?? RESUMABLE_THRESHOLD;
663
+ if (fileStats.size < threshold && !uploadOptions?.resumeSessionUri) {
664
+ uploadOptions?.onProgress?.({ bytesUploaded: 0, totalBytes: fileStats.size, percent: 0, bytesPerSecond: 0, etaSeconds: 0 });
665
+ const result = await uploadRequest(path, safeFilePath, contentType);
666
+ uploadOptions?.onProgress?.({ bytesUploaded: fileStats.size, totalBytes: fileStats.size, percent: 100, bytesPerSecond: 0, etaSeconds: 0 });
667
+ return result;
668
+ }
669
+ const uploadUrl = `${UPLOAD_BASE_URL}${path}`;
670
+ return resumableUpload(uploadUrl, safeFilePath, contentType, {
671
+ getAccessToken: () => options.auth.getAccessToken(),
672
+ maxRetries,
673
+ baseDelay,
674
+ maxDelay,
675
+ onRetry
676
+ }, uploadOptions);
677
+ },
398
678
  uploadInternal(path, filePath, contentType) {
399
679
  return uploadRequest(path, filePath, contentType, INTERNAL_SHARING_UPLOAD_BASE_URL);
400
680
  },
@@ -488,11 +768,12 @@ function createApiClient(options) {
488
768
  );
489
769
  return data.bundles;
490
770
  },
491
- async upload(packageName, editId, filePath) {
492
- const { data } = await http.upload(
771
+ async upload(packageName, editId, filePath, uploadOptions) {
772
+ const { data } = await http.uploadResumable(
493
773
  `/${packageName}/edits/${editId}/bundles`,
494
774
  filePath,
495
- "application/octet-stream"
775
+ "application/octet-stream",
776
+ uploadOptions
496
777
  );
497
778
  if (!data || !data.versionCode) {
498
779
  throw new PlayApiError(
@@ -769,7 +1050,6 @@ function createApiClient(options) {
769
1050
  async list(packageName, options2) {
770
1051
  const params = {};
771
1052
  if (options2?.token) params["token"] = options2.token;
772
- if (options2?.maxResults) params["maxResults"] = String(options2.maxResults);
773
1053
  const hasParams = Object.keys(params).length > 0;
774
1054
  const { data } = await http.get(
775
1055
  `/${packageName}/inappproducts`,
@@ -843,6 +1123,12 @@ function createApiClient(options) {
843
1123
  return data;
844
1124
  },
845
1125
  async getSubscriptionV1(packageName, subscriptionId, token) {
1126
+ if (typeof process !== "undefined" && process.emitWarning) {
1127
+ process.emitWarning(
1128
+ "purchases.subscriptions.get (v1) is deprecated by Google (shutdown Aug 2027). Use getSubscriptionV2() instead.",
1129
+ "DeprecationWarning"
1130
+ );
1131
+ }
846
1132
  const { data } = await http.get(
847
1133
  `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}`
848
1134
  );
@@ -1138,12 +1424,77 @@ function createApiClient(options) {
1138
1424
  };
1139
1425
  }
1140
1426
 
1427
+ // src/rate-limiter.ts
1428
+ var RATE_LIMIT_BUCKETS = {
1429
+ default: { name: "default", maxTokens: 200, refillRate: 200, refillIntervalMs: 1e3 },
1430
+ reviewsGet: { name: "reviewsGet", maxTokens: 200, refillRate: 200, refillIntervalMs: 36e5 },
1431
+ reviewsPost: {
1432
+ name: "reviewsPost",
1433
+ maxTokens: 2e3,
1434
+ refillRate: 2e3,
1435
+ refillIntervalMs: 864e5
1436
+ },
1437
+ voidedBurst: { name: "voidedBurst", maxTokens: 30, refillRate: 30, refillIntervalMs: 3e4 },
1438
+ voidedDaily: {
1439
+ name: "voidedDaily",
1440
+ maxTokens: 6e3,
1441
+ refillRate: 6e3,
1442
+ refillIntervalMs: 864e5
1443
+ },
1444
+ reporting: { name: "reporting", maxTokens: 10, refillRate: 10, refillIntervalMs: 1e3 }
1445
+ };
1446
+ function createRateLimiter(buckets) {
1447
+ const states = /* @__PURE__ */ new Map();
1448
+ if (buckets) {
1449
+ for (const bucket of buckets) {
1450
+ states.set(bucket.name, {
1451
+ tokens: bucket.maxTokens,
1452
+ lastRefillTime: Date.now(),
1453
+ config: bucket
1454
+ });
1455
+ }
1456
+ }
1457
+ return {
1458
+ async acquire(bucket) {
1459
+ const state = states.get(bucket);
1460
+ if (!state) return;
1461
+ const now = Date.now();
1462
+ const elapsed = now - state.lastRefillTime;
1463
+ const refill = Math.floor(
1464
+ elapsed / state.config.refillIntervalMs * state.config.refillRate
1465
+ );
1466
+ if (refill > 0) {
1467
+ state.tokens = Math.min(state.config.maxTokens, state.tokens + refill);
1468
+ state.lastRefillTime = now;
1469
+ }
1470
+ if (state.tokens > 0) {
1471
+ state.tokens--;
1472
+ return;
1473
+ }
1474
+ const tokensNeeded = 1;
1475
+ const waitMs = Math.ceil(
1476
+ tokensNeeded / state.config.refillRate * state.config.refillIntervalMs
1477
+ );
1478
+ await new Promise((r) => setTimeout(r, waitMs));
1479
+ const afterWait = Date.now();
1480
+ const totalElapsed = afterWait - state.lastRefillTime;
1481
+ const newTokens = Math.floor(
1482
+ totalElapsed / state.config.refillIntervalMs * state.config.refillRate
1483
+ );
1484
+ state.tokens = Math.min(state.config.maxTokens, newTokens) - 1;
1485
+ state.lastRefillTime = afterWait;
1486
+ }
1487
+ };
1488
+ }
1489
+
1141
1490
  // src/reporting-client.ts
1142
1491
  var REPORTING_BASE_URL = "https://playdeveloperreporting.googleapis.com/v1beta1";
1143
1492
  function createReportingClient(options) {
1144
1493
  const http = createHttpClient({ ...options, baseUrl: REPORTING_BASE_URL });
1494
+ const limiter = options.rateLimiter ?? createRateLimiter([RATE_LIMIT_BUCKETS["reporting"]]);
1145
1495
  return {
1146
1496
  async queryMetricSet(packageName, metricSet, query) {
1497
+ await limiter.acquire("reporting");
1147
1498
  const { data } = await http.post(
1148
1499
  `/apps/${packageName}/${metricSet}:query`,
1149
1500
  query
@@ -1151,10 +1502,12 @@ function createReportingClient(options) {
1151
1502
  return data;
1152
1503
  },
1153
1504
  async getAnomalies(packageName) {
1505
+ await limiter.acquire("reporting");
1154
1506
  const { data } = await http.get(`/apps/${packageName}/anomalies`);
1155
1507
  return data;
1156
1508
  },
1157
1509
  async searchErrorIssues(packageName, filter, pageSize, pageToken) {
1510
+ await limiter.acquire("reporting");
1158
1511
  const params = {};
1159
1512
  if (filter) params["filter"] = filter;
1160
1513
  if (pageSize) params["pageSize"] = String(pageSize);
@@ -1166,6 +1519,7 @@ function createReportingClient(options) {
1166
1519
  return data;
1167
1520
  },
1168
1521
  async searchErrorReports(packageName, issueName, pageSize, pageToken) {
1522
+ await limiter.acquire("reporting");
1169
1523
  const params = {};
1170
1524
  if (pageSize) params["pageSize"] = String(pageSize);
1171
1525
  if (pageToken) params["pageToken"] = pageToken;
@@ -1313,68 +1667,6 @@ function createEnterpriseClient(options) {
1313
1667
  };
1314
1668
  }
1315
1669
 
1316
- // src/rate-limiter.ts
1317
- var RATE_LIMIT_BUCKETS = {
1318
- default: { name: "default", maxTokens: 200, refillRate: 200, refillIntervalMs: 1e3 },
1319
- reviewsGet: { name: "reviewsGet", maxTokens: 200, refillRate: 200, refillIntervalMs: 36e5 },
1320
- reviewsPost: {
1321
- name: "reviewsPost",
1322
- maxTokens: 2e3,
1323
- refillRate: 2e3,
1324
- refillIntervalMs: 864e5
1325
- },
1326
- voidedBurst: { name: "voidedBurst", maxTokens: 30, refillRate: 30, refillIntervalMs: 3e4 },
1327
- voidedDaily: {
1328
- name: "voidedDaily",
1329
- maxTokens: 6e3,
1330
- refillRate: 6e3,
1331
- refillIntervalMs: 864e5
1332
- }
1333
- };
1334
- function createRateLimiter(buckets) {
1335
- const states = /* @__PURE__ */ new Map();
1336
- if (buckets) {
1337
- for (const bucket of buckets) {
1338
- states.set(bucket.name, {
1339
- tokens: bucket.maxTokens,
1340
- lastRefillTime: Date.now(),
1341
- config: bucket
1342
- });
1343
- }
1344
- }
1345
- return {
1346
- async acquire(bucket) {
1347
- const state = states.get(bucket);
1348
- if (!state) return;
1349
- const now = Date.now();
1350
- const elapsed = now - state.lastRefillTime;
1351
- const refill = Math.floor(
1352
- elapsed / state.config.refillIntervalMs * state.config.refillRate
1353
- );
1354
- if (refill > 0) {
1355
- state.tokens = Math.min(state.config.maxTokens, state.tokens + refill);
1356
- state.lastRefillTime = now;
1357
- }
1358
- if (state.tokens > 0) {
1359
- state.tokens--;
1360
- return;
1361
- }
1362
- const tokensNeeded = 1;
1363
- const waitMs = Math.ceil(
1364
- tokensNeeded / state.config.refillRate * state.config.refillIntervalMs
1365
- );
1366
- await new Promise((r) => setTimeout(r, waitMs));
1367
- const afterWait = Date.now();
1368
- const totalElapsed = afterWait - state.lastRefillTime;
1369
- const newTokens = Math.floor(
1370
- totalElapsed / state.config.refillIntervalMs * state.config.refillRate
1371
- );
1372
- state.tokens = Math.min(state.config.maxTokens, newTokens) - 1;
1373
- state.lastRefillTime = afterWait;
1374
- }
1375
- };
1376
- }
1377
-
1378
1670
  // src/paginate.ts
1379
1671
  async function* paginate(fetchPage, options) {
1380
1672
  let pageToken = options?.startPageToken;
@@ -1429,6 +1721,7 @@ async function paginateParallel(fetchPage, pageTokens, concurrency = 4) {
1429
1721
  export {
1430
1722
  PlayApiError,
1431
1723
  RATE_LIMIT_BUCKETS,
1724
+ RESUMABLE_THRESHOLD,
1432
1725
  createApiClient,
1433
1726
  createEnterpriseClient,
1434
1727
  createGamesClient,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts","../src/reporting-client.ts","../src/users-client.ts","../src/games-client.ts","../src/enterprise-client.ts","../src/rate-limiter.ts","../src/paginate.ts"],"sourcesContent":["export class PlayApiError extends Error {\n public readonly exitCode = 4;\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number,\n public readonly suggestion?: string,\n ) {\n super(message);\n this.name = \"PlayApiError\";\n }\n toJSON() {\n return {\n success: false,\n error: {\n code: this.code,\n message: this.message,\n suggestion: this.suggestion,\n },\n };\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { resolve, isAbsolute } from \"node:path\";\nimport { PlayApiError } from \"./errors.js\";\nimport type { ApiClientOptions, ApiResponse } from \"./types.js\";\n\n/** Strip HTML tags and collapse whitespace from a string. */\nfunction stripHtml(text: string): string {\n return text\n .replace(/<[^>]*>/g, \" \")\n .replace(/&[a-z]+;/gi, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/** Extract a short, safe error summary from API response body (no tokens/secrets). */\nfunction sanitizeErrorBody(body: string): string {\n try {\n const parsed = JSON.parse(body) as {\n error?: { message?: string; status?: string; code?: number };\n };\n if (parsed?.error?.message) {\n return `${parsed.error.code ?? \"?\"} ${parsed.error.status ?? \"\"}: ${parsed.error.message}`.trim();\n }\n } catch {\n // not JSON — may be HTML error page\n }\n // Strip HTML tags before truncating\n const cleaned = body.startsWith(\"<\") ? stripHtml(body) : body;\n return cleaned.length > 200 ? cleaned.slice(0, 200) + \"...\" : cleaned;\n}\n\n/** Validate upload file path to prevent path traversal. */\nfunction validateFilePath(filePath: string): string {\n const resolved = resolve(filePath);\n if (!isAbsolute(resolved)) {\n throw new PlayApiError(\n \"Invalid file path\",\n \"API_INVALID_PATH\",\n undefined,\n \"File path must resolve to an absolute path.\",\n );\n }\n // Block obvious traversal patterns in the original input\n if (filePath.includes(\"\\0\")) {\n throw new PlayApiError(\n \"Invalid file path: null bytes not allowed\",\n \"API_INVALID_PATH\",\n undefined,\n \"Provide a valid file path without null bytes.\",\n );\n }\n return resolved;\n}\n\nconst BASE_URL = \"https://androidpublisher.googleapis.com/androidpublisher/v3/applications\";\n\nconst UPLOAD_BASE_URL =\n \"https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications\";\n\nconst INTERNAL_SHARING_UPLOAD_BASE_URL =\n \"https://androidpublisher.googleapis.com/upload/internalappsharing/v3/applications\";\n\nexport interface HttpClient {\n get<T>(path: string, params?: Record<string, string>): Promise<ApiResponse<T>>;\n post<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;\n put<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;\n patch<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;\n delete<T>(path: string): Promise<ApiResponse<T>>;\n upload<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;\n uploadInternal<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;\n download(path: string): Promise<ArrayBuffer>;\n}\n\nfunction envInt(name: string): number | undefined {\n const val = process.env[name];\n if (val === undefined) return undefined;\n const n = Number(val);\n return Number.isFinite(n) ? n : undefined;\n}\n\nfunction resolveOption(explicit: number | undefined, envName: string, fallback: number): number {\n return explicit ?? envInt(envName) ?? fallback;\n}\n\nfunction mapStatusToError(status: number, body: string): { code: string; suggestion?: string } {\n switch (status) {\n case 400: {\n // Detect FAILED_PRECONDITION: edit has expired\n try {\n const parsed = JSON.parse(body) as { error?: { status?: string; message?: string } };\n if (\n parsed?.error?.status === \"FAILED_PRECONDITION\" &&\n parsed.error.message?.toLowerCase().includes(\"edit\")\n ) {\n return {\n code: \"API_EDIT_EXPIRED\",\n suggestion: \"The edit session has expired. Retry the operation to open a fresh edit.\",\n };\n }\n } catch {\n /* not JSON */\n }\n return { code: \"API_HTTP_400\", suggestion: \"Check request parameters and try again.\" };\n }\n case 401:\n return {\n code: \"API_UNAUTHORIZED\",\n suggestion: \"Check that your access token is valid and not expired.\",\n };\n case 403:\n return {\n code: \"API_FORBIDDEN\",\n suggestion: \"Ensure the service account has the required permissions for this operation.\",\n };\n case 404:\n return {\n code: \"API_NOT_FOUND\",\n suggestion: \"Verify the package name and resource IDs are correct.\",\n };\n case 409:\n return {\n code: \"API_EDIT_CONFLICT\",\n suggestion: \"Another edit may be in progress. Delete the existing edit and retry.\",\n };\n case 429:\n return {\n code: \"API_RATE_LIMITED\",\n suggestion: \"Too many requests. The client will retry automatically.\",\n };\n default:\n if (status >= 500) {\n return {\n code: \"API_SERVER_ERROR\",\n suggestion: \"Google Play API server error. The client will retry automatically.\",\n };\n }\n return { code: `API_HTTP_${status}` };\n }\n}\n\nfunction isRetryable(status: number): boolean {\n return status === 429 || status >= 500;\n}\n\nfunction jitteredDelay(base: number, attempt: number, max: number): number {\n const exponential = base * 2 ** attempt;\n const capped = Math.min(exponential, max);\n return capped * (0.5 + Math.random() * 0.5);\n}\n\nexport function createHttpClient(options: ApiClientOptions): HttpClient {\n const maxRetries = resolveOption(options.maxRetries, \"GPC_MAX_RETRIES\", 3);\n const timeout = resolveOption(options.timeout, \"GPC_TIMEOUT\", 30_000);\n const uploadTimeoutExplicit = options.uploadTimeout ?? envInt(\"GPC_UPLOAD_TIMEOUT\");\n const baseDelay = resolveOption(options.baseDelay, \"GPC_BASE_DELAY\", 1_000);\n const maxDelay = resolveOption(options.maxDelay, \"GPC_MAX_DELAY\", 60_000);\n const onRetry = options.onRetry;\n\n async function request<T>(\n method: string,\n path: string,\n body?: unknown,\n params?: Record<string, string>,\n ): Promise<ApiResponse<T>> {\n let url = `${options.baseUrl ?? BASE_URL}${path}`;\n if (params) {\n const search = new URLSearchParams(params);\n url += `?${search.toString()}`;\n }\n\n // Fetch token once before retries — the auth layer handles its own caching and mutex\n let token = await options.auth.getAccessToken();\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);\n await new Promise((r) => setTimeout(r, delay));\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n \"Accept-Encoding\": \"gzip, deflate\",\n Connection: \"keep-alive\",\n };\n\n const init: RequestInit = {\n method,\n headers,\n signal: controller.signal,\n keepalive: true,\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, init);\n\n if (response.ok) {\n const text = await response.text();\n const data = text ? (JSON.parse(text) as T) : ({} as T);\n return { data, status: response.status };\n }\n\n const errorBody = await response.text();\n const { code, suggestion } = mapStatusToError(response.status, errorBody);\n\n const err = new PlayApiError(\n `${method} ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,\n code,\n response.status,\n suggestion,\n );\n\n if (isRetryable(response.status) && attempt < maxRetries) {\n lastError = err;\n const delay = jitteredDelay(baseDelay, attempt, maxDelay);\n onRetry?.({\n attempt: attempt + 1,\n method,\n path,\n status: response.status,\n error: err.message,\n delayMs: Math.round(delay),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n\n // On 401, refresh token once before giving up\n if (response.status === 401 && attempt < maxRetries) {\n token = await options.auth.getAccessToken();\n lastError = err;\n continue;\n }\n\n throw err;\n } catch (error) {\n if (error instanceof PlayApiError) {\n throw error;\n }\n\n if (error instanceof DOMException && error.name === \"AbortError\") {\n const timeoutErr = new PlayApiError(\n `${method} ${path} timed out after ${timeout}ms`,\n \"API_TIMEOUT\",\n undefined,\n \"The request exceeded the configured timeout. Consider increasing the timeout value.\",\n );\n if (attempt < maxRetries) {\n lastError = timeoutErr;\n onRetry?.({\n attempt: attempt + 1,\n method,\n path,\n error: timeoutErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw timeoutErr;\n }\n\n const networkErr = new PlayApiError(\n `${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,\n \"API_NETWORK_ERROR\",\n undefined,\n \"A network error occurred. Check your internet connection.\",\n );\n if (attempt < maxRetries) {\n lastError = networkErr;\n onRetry?.({\n attempt: attempt + 1,\n method,\n path,\n error: networkErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw networkErr;\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Should not reach here, but just in case\n throw (\n lastError ??\n new PlayApiError(\n \"Request failed\",\n \"API_NETWORK_ERROR\",\n undefined,\n \"Check your network connection and try again. Use --verbose for details.\",\n )\n );\n }\n\n /** Calculate upload timeout: explicit value, or auto-scale from file size (1 MB/s minimum throughput + 30s overhead). */\n function computeUploadTimeout(fileSizeBytes: number): number {\n if (uploadTimeoutExplicit !== undefined) return uploadTimeoutExplicit;\n // Base: 30s overhead + 1s per MB (assumes ~1 MB/s minimum upload speed)\n const sizeMb = fileSizeBytes / (1024 * 1024);\n return Math.max(timeout, 30_000 + Math.ceil(sizeMb) * 1_000);\n }\n\n async function uploadRequest<T>(\n path: string,\n filePath: string,\n contentType: string,\n baseUrl: string = UPLOAD_BASE_URL,\n ): Promise<ApiResponse<T>> {\n const url = `${baseUrl}${path}`;\n const safeFilePath = validateFilePath(filePath);\n const fileBuffer = await readFile(safeFilePath);\n const effectiveTimeout = computeUploadTimeout(fileBuffer.byteLength);\n\n // Fetch token once before retries\n let token = await options.auth.getAccessToken();\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);\n await new Promise((r) => setTimeout(r, delay));\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), effectiveTimeout);\n\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": contentType,\n \"Accept-Encoding\": \"gzip, deflate\",\n Connection: \"keep-alive\",\n };\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: fileBuffer,\n signal: controller.signal,\n keepalive: true,\n });\n\n if (response.ok) {\n const text = await response.text();\n const data = text ? (JSON.parse(text) as T) : ({} as T);\n return { data, status: response.status };\n }\n\n const errorBody = await response.text();\n const { code, suggestion } = mapStatusToError(response.status, errorBody);\n\n const err = new PlayApiError(\n `POST upload ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,\n code,\n response.status,\n suggestion,\n );\n\n if (isRetryable(response.status) && attempt < maxRetries) {\n lastError = err;\n const delay = jitteredDelay(baseDelay, attempt, maxDelay);\n onRetry?.({\n attempt: attempt + 1,\n method: \"POST\",\n path: `upload ${path}`,\n status: response.status,\n error: err.message,\n delayMs: Math.round(delay),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n\n // On 401, refresh token once before giving up\n if (response.status === 401 && attempt < maxRetries) {\n token = await options.auth.getAccessToken();\n lastError = err;\n continue;\n }\n\n throw err;\n } catch (error) {\n if (error instanceof PlayApiError) {\n throw error;\n }\n\n if (error instanceof DOMException && error.name === \"AbortError\") {\n const sizeMb = Math.round(fileBuffer.byteLength / (1024 * 1024));\n const timeoutErr = new PlayApiError(\n `POST upload ${path} timed out after ${effectiveTimeout}ms (file: ${sizeMb} MB)`,\n \"API_TIMEOUT\",\n undefined,\n `Upload timed out. Set GPC_UPLOAD_TIMEOUT=${effectiveTimeout * 2} (ms) or use --timeout to increase.`,\n );\n if (attempt < maxRetries) {\n lastError = timeoutErr;\n onRetry?.({\n attempt: attempt + 1,\n method: \"POST\",\n path: `upload ${path}`,\n error: timeoutErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw timeoutErr;\n }\n\n const networkErr = new PlayApiError(\n `POST upload ${path} failed: ${error instanceof Error ? error.message : String(error)}`,\n \"API_NETWORK_ERROR\",\n undefined,\n \"A network error occurred. Check your internet connection.\",\n );\n if (attempt < maxRetries) {\n lastError = networkErr;\n onRetry?.({\n attempt: attempt + 1,\n method: \"POST\",\n path: `upload ${path}`,\n error: networkErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw networkErr;\n } finally {\n clearTimeout(timer);\n }\n }\n\n throw (\n lastError ??\n new PlayApiError(\n \"Upload request failed\",\n \"API_NETWORK_ERROR\",\n undefined,\n \"Check your network connection and try again. Use --verbose for details.\",\n )\n );\n }\n\n return {\n get<T>(path: string, params?: Record<string, string>) {\n return request<T>(\"GET\", path, undefined, params);\n },\n post<T>(path: string, body?: unknown) {\n return request<T>(\"POST\", path, body);\n },\n put<T>(path: string, body?: unknown) {\n return request<T>(\"PUT\", path, body);\n },\n patch<T>(path: string, body?: unknown) {\n return request<T>(\"PATCH\", path, body);\n },\n delete<T>(path: string) {\n return request<T>(\"DELETE\", path);\n },\n upload<T>(path: string, filePath: string, contentType: string) {\n return uploadRequest<T>(path, filePath, contentType);\n },\n uploadInternal<T>(path: string, filePath: string, contentType: string) {\n return uploadRequest<T>(path, filePath, contentType, INTERNAL_SHARING_UPLOAD_BASE_URL);\n },\n async download(path: string): Promise<ArrayBuffer> {\n const url = `${options.baseUrl ?? BASE_URL}${path}`;\n const token = await options.auth.getAccessToken();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Accept-Encoding\": \"gzip, deflate\",\n Connection: \"keep-alive\",\n },\n signal: controller.signal,\n keepalive: true,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n const { code, suggestion } = mapStatusToError(response.status, errorBody);\n throw new PlayApiError(\n `GET ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,\n code,\n response.status,\n suggestion,\n );\n }\n\n return await response.arrayBuffer();\n } finally {\n clearTimeout(timer);\n }\n },\n };\n}\n","import { PlayApiError } from \"./errors.js\";\nimport { createHttpClient } from \"./http.js\";\nimport type { RateLimiter } from \"./rate-limiter.js\";\nimport type {\n ApiClientOptions,\n AppDetails,\n AppEdit,\n AppRecoveriesListResponse,\n AppRecoveryAction,\n AppRecoveryTargeting,\n CreateAppRecoveryActionRequest,\n BasePlanMigratePricesRequest,\n Bundle,\n BundleListResponse,\n ConvertRegionPricesRequest,\n ConvertRegionPricesResponse,\n CountryAvailability,\n DataSafety,\n DeobfuscationFile,\n DeobfuscationUploadResponse,\n DeviceTierConfig,\n DeviceTierConfigsListResponse,\n ExternalTransaction,\n ExternalTransactionRefund,\n ExternallyHostedApk,\n ExternallyHostedApkResponse,\n Image,\n ImageType,\n ImageUploadResponse,\n ImagesDeleteAllResponse,\n ImagesListResponse,\n InAppProduct,\n InAppProductsListResponse,\n Listing,\n ListingsListResponse,\n OffersListResponse,\n ProductPurchase,\n Release,\n ReportsListResponse,\n ReportType,\n Review,\n ReviewReplyRequest,\n ReviewReplyResponse,\n ReviewsListOptions,\n ReviewsListResponse,\n Subscription,\n SubscriptionDeferRequest,\n SubscriptionDeferResponse,\n SubscriptionOffer,\n SubscriptionPurchase,\n SubscriptionPurchaseV2,\n SubscriptionsListResponse,\n Testers,\n Track,\n TrackListResponse,\n VoidedPurchasesListResponse,\n OneTimeProduct,\n OneTimeProductsListResponse,\n OneTimeOffer,\n OneTimeOffersListResponse,\n InternalAppSharingArtifact,\n GeneratedApk,\n GeneratedApksPerVersion,\n PurchaseOption,\n PurchaseOptionsListResponse,\n InAppProductsBatchUpdateRequest,\n InAppProductsBatchUpdateResponse,\n} from \"./types.js\";\n\nexport interface PlayApiClient {\n edits: {\n insert(packageName: string): Promise<AppEdit>;\n get(packageName: string, editId: string): Promise<AppEdit>;\n validate(packageName: string, editId: string): Promise<AppEdit>;\n commit(packageName: string, editId: string): Promise<AppEdit>;\n delete(packageName: string, editId: string): Promise<void>;\n };\n\n details: {\n get(packageName: string, editId: string): Promise<AppDetails>;\n update(packageName: string, editId: string, details: Partial<AppDetails>): Promise<AppDetails>;\n patch(packageName: string, editId: string, partial: Partial<AppDetails>): Promise<AppDetails>;\n };\n\n bundles: {\n list(packageName: string, editId: string): Promise<Bundle[]>;\n upload(packageName: string, editId: string, filePath: string): Promise<Bundle>;\n };\n\n tracks: {\n list(packageName: string, editId: string): Promise<Track[]>;\n get(packageName: string, editId: string, track: string): Promise<Track>;\n create(packageName: string, editId: string, trackName: string): Promise<Track>;\n update(packageName: string, editId: string, track: string, release: Release): Promise<Track>;\n };\n\n apks: {\n addExternallyHosted(\n packageName: string,\n editId: string,\n data: ExternallyHostedApk,\n ): Promise<ExternallyHostedApkResponse>;\n };\n\n listings: {\n list(packageName: string, editId: string): Promise<Listing[]>;\n get(packageName: string, editId: string, language: string): Promise<Listing>;\n update(\n packageName: string,\n editId: string,\n language: string,\n listing: Omit<Listing, \"language\">,\n ): Promise<Listing>;\n patch(\n packageName: string,\n editId: string,\n language: string,\n partial: Partial<Omit<Listing, \"language\">>,\n ): Promise<Listing>;\n delete(packageName: string, editId: string, language: string): Promise<void>;\n deleteAll(packageName: string, editId: string): Promise<void>;\n };\n\n images: {\n list(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n ): Promise<Image[]>;\n upload(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n filePath: string,\n ): Promise<Image>;\n delete(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n imageId: string,\n ): Promise<void>;\n deleteAll(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n ): Promise<Image[]>;\n };\n\n countryAvailability: {\n get(packageName: string, editId: string, track: string): Promise<CountryAvailability>;\n };\n\n dataSafety: {\n get(packageName: string): Promise<DataSafety>;\n update(packageName: string, data: DataSafety): Promise<DataSafety>;\n };\n\n reviews: {\n list(packageName: string, options?: ReviewsListOptions): Promise<ReviewsListResponse>;\n get(packageName: string, reviewId: string, translationLanguage?: string): Promise<Review>;\n reply(packageName: string, reviewId: string, replyText: string): Promise<ReviewReplyResponse>;\n };\n\n subscriptions: {\n list(\n packageName: string,\n options?: { pageToken?: string; pageSize?: number },\n ): Promise<SubscriptionsListResponse>;\n get(packageName: string, productId: string): Promise<Subscription>;\n create(packageName: string, data: Subscription, productId?: string): Promise<Subscription>;\n update(\n packageName: string,\n productId: string,\n data: Subscription,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<Subscription>;\n delete(packageName: string, productId: string): Promise<void>;\n activateBasePlan(\n packageName: string,\n productId: string,\n basePlanId: string,\n ): Promise<Subscription>;\n deactivateBasePlan(\n packageName: string,\n productId: string,\n basePlanId: string,\n ): Promise<Subscription>;\n deleteBasePlan(packageName: string, productId: string, basePlanId: string): Promise<void>;\n migratePrices(\n packageName: string,\n productId: string,\n basePlanId: string,\n body: BasePlanMigratePricesRequest,\n ): Promise<Subscription>;\n listOffers(\n packageName: string,\n productId: string,\n basePlanId: string,\n ): Promise<OffersListResponse>;\n getOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<SubscriptionOffer>;\n createOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n data: SubscriptionOffer,\n offerId?: string,\n ): Promise<SubscriptionOffer>;\n updateOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n data: SubscriptionOffer,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<SubscriptionOffer>;\n deleteOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<void>;\n activateOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<SubscriptionOffer>;\n deactivateOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<SubscriptionOffer>;\n };\n\n inappproducts: {\n list(\n packageName: string,\n options?: { token?: string; maxResults?: number },\n ): Promise<InAppProductsListResponse>;\n get(packageName: string, sku: string): Promise<InAppProduct>;\n create(\n packageName: string,\n data: InAppProduct,\n options?: { autoConvertMissingPrices?: boolean },\n ): Promise<InAppProduct>;\n update(\n packageName: string,\n sku: string,\n data: InAppProduct,\n options?: { autoConvertMissingPrices?: boolean; allowMissing?: boolean },\n ): Promise<InAppProduct>;\n delete(packageName: string, sku: string): Promise<void>;\n batchUpdate(\n packageName: string,\n requests: InAppProductsBatchUpdateRequest,\n ): Promise<InAppProductsBatchUpdateResponse>;\n batchGet(packageName: string, skus: string[]): Promise<InAppProduct[]>;\n };\n\n purchases: {\n getProduct(packageName: string, productId: string, token: string): Promise<ProductPurchase>;\n acknowledgeProduct(\n packageName: string,\n productId: string,\n token: string,\n body?: { developerPayload?: string },\n ): Promise<void>;\n consumeProduct(packageName: string, productId: string, token: string): Promise<void>;\n getSubscriptionV2(packageName: string, token: string): Promise<SubscriptionPurchaseV2>;\n getSubscriptionV1(\n packageName: string,\n subscriptionId: string,\n token: string,\n ): Promise<SubscriptionPurchase>;\n cancelSubscription(packageName: string, subscriptionId: string, token: string): Promise<void>;\n deferSubscription(\n packageName: string,\n subscriptionId: string,\n token: string,\n body: SubscriptionDeferRequest,\n ): Promise<SubscriptionDeferResponse>;\n revokeSubscriptionV2(packageName: string, token: string): Promise<void>;\n refundSubscriptionV2(packageName: string, token: string): Promise<void>;\n listVoided(\n packageName: string,\n options?: { startTime?: string; endTime?: string; maxResults?: number; token?: string },\n ): Promise<VoidedPurchasesListResponse>;\n };\n\n orders: {\n refund(\n packageName: string,\n orderId: string,\n body?: { fullRefund?: boolean; proratedRefund?: boolean },\n ): Promise<void>;\n };\n\n monetization: {\n convertRegionPrices(\n packageName: string,\n price: ConvertRegionPricesRequest,\n ): Promise<ConvertRegionPricesResponse>;\n };\n\n reports: {\n list(\n packageName: string,\n reportType: ReportType,\n year: number,\n month: number,\n ): Promise<ReportsListResponse>;\n };\n\n testers: {\n get(packageName: string, editId: string, track: string): Promise<Testers>;\n update(packageName: string, editId: string, track: string, testers: Testers): Promise<Testers>;\n };\n\n deobfuscation: {\n upload(\n packageName: string,\n editId: string,\n versionCode: number,\n filePath: string,\n ): Promise<DeobfuscationFile>;\n };\n\n appRecovery: {\n list(packageName: string, versionCode?: number): Promise<AppRecoveryAction[]>;\n cancel(packageName: string, appRecoveryId: string): Promise<void>;\n deploy(packageName: string, appRecoveryId: string): Promise<void>;\n create(\n packageName: string,\n request: CreateAppRecoveryActionRequest,\n ): Promise<AppRecoveryAction>;\n addTargeting(\n packageName: string,\n appRecoveryId: string,\n targeting: AppRecoveryTargeting,\n ): Promise<AppRecoveryAction>;\n };\n\n externalTransactions: {\n create(packageName: string, data: ExternalTransaction): Promise<ExternalTransaction>;\n get(packageName: string, transactionId: string): Promise<ExternalTransaction>;\n refund(\n packageName: string,\n transactionId: string,\n refundData: ExternalTransactionRefund,\n ): Promise<ExternalTransaction>;\n };\n\n deviceTiers: {\n list(packageName: string): Promise<DeviceTierConfig[]>;\n get(packageName: string, configId: string): Promise<DeviceTierConfig>;\n create(packageName: string, config: DeviceTierConfig): Promise<DeviceTierConfig>;\n };\n\n oneTimeProducts: {\n list(packageName: string): Promise<OneTimeProductsListResponse>;\n get(packageName: string, productId: string): Promise<OneTimeProduct>;\n create(packageName: string, product: OneTimeProduct): Promise<OneTimeProduct>;\n update(\n packageName: string,\n productId: string,\n product: Partial<OneTimeProduct>,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<OneTimeProduct>;\n delete(packageName: string, productId: string): Promise<void>;\n listOffers(packageName: string, productId: string): Promise<OneTimeOffersListResponse>;\n getOffer(packageName: string, productId: string, offerId: string): Promise<OneTimeOffer>;\n createOffer(packageName: string, productId: string, offer: OneTimeOffer): Promise<OneTimeOffer>;\n updateOffer(\n packageName: string,\n productId: string,\n offerId: string,\n offer: Partial<OneTimeOffer>,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<OneTimeOffer>;\n deleteOffer(packageName: string, productId: string, offerId: string): Promise<void>;\n };\n\n purchaseOptions: {\n list(packageName: string): Promise<PurchaseOptionsListResponse>;\n get(packageName: string, purchaseOptionId: string): Promise<PurchaseOption>;\n create(packageName: string, data: PurchaseOption): Promise<PurchaseOption>;\n activate(packageName: string, purchaseOptionId: string): Promise<PurchaseOption>;\n deactivate(packageName: string, purchaseOptionId: string): Promise<PurchaseOption>;\n };\n\n internalAppSharing: {\n uploadBundle(packageName: string, bundlePath: string): Promise<InternalAppSharingArtifact>;\n uploadApk(packageName: string, apkPath: string): Promise<InternalAppSharingArtifact>;\n };\n\n generatedApks: {\n list(packageName: string, versionCode: number): Promise<GeneratedApk[]>;\n download(packageName: string, versionCode: number, id: string): Promise<ArrayBuffer>;\n };\n}\n\nasync function rateLimit(limiter: RateLimiter | undefined, bucket: string): Promise<void> {\n if (limiter) await limiter.acquire(bucket);\n}\n\nexport function createApiClient(options: ApiClientOptions): PlayApiClient {\n const http = createHttpClient(options);\n const limiter = options.rateLimiter || undefined;\n\n return {\n edits: {\n async insert(packageName) {\n const { data } = await http.post<AppEdit>(`/${packageName}/edits`);\n return data;\n },\n\n async get(packageName, editId) {\n const { data } = await http.get<AppEdit>(`/${packageName}/edits/${editId}`);\n return data;\n },\n\n async validate(packageName, editId) {\n const { data } = await http.post<AppEdit>(`/${packageName}/edits/${editId}:validate`);\n return data;\n },\n\n async commit(packageName, editId) {\n const { data } = await http.post<AppEdit>(`/${packageName}/edits/${editId}:commit`);\n return data;\n },\n\n async delete(packageName, editId) {\n await http.delete(`/${packageName}/edits/${editId}`);\n },\n },\n\n details: {\n async get(packageName, editId) {\n const { data } = await http.get<AppDetails>(`/${packageName}/edits/${editId}/details`);\n return data;\n },\n\n async update(packageName, editId, details) {\n const { data } = await http.put<AppDetails>(\n `/${packageName}/edits/${editId}/details`,\n details,\n );\n return data;\n },\n\n async patch(packageName, editId, partial) {\n const { data } = await http.patch<AppDetails>(\n `/${packageName}/edits/${editId}/details`,\n partial,\n );\n return data;\n },\n },\n\n bundles: {\n async list(packageName, editId) {\n const { data } = await http.get<BundleListResponse>(\n `/${packageName}/edits/${editId}/bundles`,\n );\n return data.bundles;\n },\n\n async upload(packageName, editId, filePath) {\n const { data } = await http.upload<Bundle>(\n `/${packageName}/edits/${editId}/bundles`,\n filePath,\n \"application/octet-stream\",\n );\n if (!data || !data.versionCode) {\n throw new PlayApiError(\n \"Upload succeeded but no bundle data returned\",\n \"API_EMPTY_RESPONSE\",\n 200,\n \"This is unexpected. Retry the upload or contact Google Play support if the issue persists.\",\n );\n }\n return data;\n },\n },\n\n tracks: {\n async list(packageName, editId) {\n const { data } = await http.get<TrackListResponse>(\n `/${packageName}/edits/${editId}/tracks`,\n );\n return data.tracks;\n },\n\n async get(packageName, editId, track) {\n const { data } = await http.get<Track>(`/${packageName}/edits/${editId}/tracks/${track}`);\n return data;\n },\n\n async create(packageName, editId, trackName) {\n const { data } = await http.post<Track>(`/${packageName}/edits/${editId}/tracks`, {\n track: trackName,\n });\n return data;\n },\n\n async update(packageName, editId, track, release) {\n const { data } = await http.put<Track>(`/${packageName}/edits/${editId}/tracks/${track}`, {\n track,\n releases: [release],\n });\n return data;\n },\n },\n\n apks: {\n async addExternallyHosted(packageName, editId, apkData) {\n const { data } = await http.post<ExternallyHostedApkResponse>(\n `/${packageName}/edits/${editId}/apks/externallyHosted`,\n { externallyHostedApk: apkData },\n );\n return data;\n },\n },\n\n listings: {\n async list(packageName, editId) {\n const { data } = await http.get<ListingsListResponse>(\n `/${packageName}/edits/${editId}/listings`,\n );\n return data.listings || [];\n },\n\n async get(packageName, editId, language) {\n const { data } = await http.get<Listing>(\n `/${packageName}/edits/${editId}/listings/${language}`,\n );\n return data;\n },\n\n async update(packageName, editId, language, listing) {\n const { data } = await http.put<Listing>(\n `/${packageName}/edits/${editId}/listings/${language}`,\n listing,\n );\n return data;\n },\n\n async patch(packageName, editId, language, partial) {\n const { data } = await http.patch<Listing>(\n `/${packageName}/edits/${editId}/listings/${language}`,\n partial,\n );\n return data;\n },\n\n async delete(packageName, editId, language) {\n await http.delete(`/${packageName}/edits/${editId}/listings/${language}`);\n },\n\n async deleteAll(packageName, editId) {\n await http.delete(`/${packageName}/edits/${editId}/listings`);\n },\n },\n\n images: {\n async list(packageName, editId, language, imageType) {\n const { data } = await http.get<ImagesListResponse>(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}`,\n );\n return data.images || [];\n },\n\n async upload(packageName, editId, language, imageType, filePath) {\n const { data } = await http.upload<ImageUploadResponse>(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}`,\n filePath,\n filePath.endsWith(\".png\") ? \"image/png\" : \"image/jpeg\",\n );\n if (!data.image) {\n throw new PlayApiError(\n \"Upload succeeded but no image data returned\",\n \"API_EMPTY_RESPONSE\",\n 200,\n \"This is unexpected. Retry the upload or contact Google Play support if the issue persists.\",\n );\n }\n return data.image;\n },\n\n async delete(packageName, editId, language, imageType, imageId) {\n await http.delete(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}/${imageId}`,\n );\n },\n\n async deleteAll(packageName, editId, language, imageType) {\n const { data } = await http.delete<ImagesDeleteAllResponse>(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}`,\n );\n return data.deleted || [];\n },\n },\n\n countryAvailability: {\n async get(packageName, editId, track) {\n const { data } = await http.get<CountryAvailability>(\n `/${packageName}/edits/${editId}/countryAvailability/${track}`,\n );\n return data;\n },\n },\n\n dataSafety: {\n async get(packageName) {\n const { data } = await http.get<DataSafety>(`/${packageName}/dataSafety`);\n return data;\n },\n\n async update(packageName, body) {\n const { data } = await http.put<DataSafety>(`/${packageName}/dataSafety`, body);\n return data;\n },\n },\n\n reviews: {\n async list(packageName, options?) {\n await rateLimit(limiter, \"reviewsGet\");\n const params: Record<string, string> = {};\n if (options?.token) params[\"token\"] = options.token;\n if (options?.maxResults) params[\"maxResults\"] = String(options.maxResults);\n if (options?.translationLanguage)\n params[\"translationLanguage\"] = options.translationLanguage;\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<ReviewsListResponse>(\n `/${packageName}/reviews`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(packageName, reviewId, translationLanguage?) {\n await rateLimit(limiter, \"reviewsGet\");\n const params: Record<string, string> = {};\n if (translationLanguage) params[\"translationLanguage\"] = translationLanguage;\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<Review>(\n `/${packageName}/reviews/${reviewId}`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async reply(packageName, reviewId, replyText) {\n await rateLimit(limiter, \"reviewsPost\");\n const body: ReviewReplyRequest = { replyText };\n const { data } = await http.post<ReviewReplyResponse>(\n `/${packageName}/reviews/${reviewId}:reply`,\n body,\n );\n return data;\n },\n },\n\n subscriptions: {\n async list(packageName, options?) {\n const params: Record<string, string> = {};\n if (options?.pageToken) params[\"pageToken\"] = options.pageToken;\n if (options?.pageSize) params[\"pageSize\"] = String(options.pageSize);\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<SubscriptionsListResponse>(\n `/${packageName}/subscriptions`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(packageName, productId) {\n const { data } = await http.get<Subscription>(`/${packageName}/subscriptions/${productId}`);\n return data;\n },\n\n async create(packageName, body, productId?) {\n const params: Record<string, string> = {};\n if (productId) params[\"productId\"] = productId;\n params[\"regionsVersion.version\"] = \"2022/02\";\n const path = `/${packageName}/subscriptions?${new URLSearchParams(params).toString()}`;\n const { data } = await http.post<Subscription>(path, body);\n return data;\n },\n\n async update(packageName, productId, body, updateMask?, regionsVersion?) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/subscriptions/${productId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<Subscription>(path, body);\n return data;\n },\n\n async delete(packageName, productId) {\n await http.delete(`/${packageName}/subscriptions/${productId}`);\n },\n\n async activateBasePlan(packageName, productId, basePlanId) {\n const { data } = await http.post<Subscription>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}:activate`,\n );\n return data;\n },\n\n async deactivateBasePlan(packageName, productId, basePlanId) {\n const { data } = await http.post<Subscription>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}:deactivate`,\n );\n return data;\n },\n\n async deleteBasePlan(packageName, productId, basePlanId) {\n await http.delete(`/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}`);\n },\n\n async migratePrices(packageName, productId, basePlanId, body) {\n const { data } = await http.post<Subscription>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}:migratePrices`,\n body,\n );\n return data;\n },\n\n async listOffers(packageName, productId, basePlanId) {\n const { data } = await http.get<OffersListResponse>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers`,\n );\n return data;\n },\n\n async getOffer(packageName, productId, basePlanId, offerId) {\n const { data } = await http.get<SubscriptionOffer>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}`,\n );\n return data;\n },\n\n async createOffer(packageName, productId, basePlanId, body, offerId?) {\n const params: Record<string, string> = {};\n if (offerId) params[\"offerId\"] = offerId;\n params[\"regionsVersion.version\"] = \"2022/02\";\n const path = `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers?${new URLSearchParams(params).toString()}`;\n const { data } = await http.post<SubscriptionOffer>(path, body);\n return data;\n },\n\n async updateOffer(\n packageName,\n productId,\n basePlanId,\n offerId,\n body,\n updateMask?,\n regionsVersion?,\n ) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<SubscriptionOffer>(path, body);\n return data;\n },\n\n async deleteOffer(packageName, productId, basePlanId, offerId) {\n await http.delete(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}`,\n );\n },\n\n async activateOffer(packageName, productId, basePlanId, offerId) {\n const { data } = await http.post<SubscriptionOffer>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}:activate`,\n );\n return data;\n },\n\n async deactivateOffer(packageName, productId, basePlanId, offerId) {\n const { data } = await http.post<SubscriptionOffer>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}:deactivate`,\n );\n return data;\n },\n },\n\n inappproducts: {\n async list(packageName, options?) {\n const params: Record<string, string> = {};\n if (options?.token) params[\"token\"] = options.token;\n if (options?.maxResults) params[\"maxResults\"] = String(options.maxResults);\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<InAppProductsListResponse>(\n `/${packageName}/inappproducts`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(packageName, sku) {\n const { data } = await http.get<InAppProduct>(`/${packageName}/inappproducts/${sku}`);\n return data;\n },\n\n async create(packageName, body, options?) {\n const params: Record<string, string> = {};\n if (options?.autoConvertMissingPrices) params[\"autoConvertMissingPrices\"] = \"true\";\n const hasParams = Object.keys(params).length > 0;\n const path = hasParams\n ? `/${packageName}/inappproducts?${new URLSearchParams(params).toString()}`\n : `/${packageName}/inappproducts`;\n const { data } = await http.post<InAppProduct>(path, body);\n return data;\n },\n\n async update(packageName, sku, body, options?) {\n const params: Record<string, string> = {};\n if (options?.autoConvertMissingPrices) params[\"autoConvertMissingPrices\"] = \"true\";\n if (options?.allowMissing) params[\"allowMissing\"] = \"true\";\n const hasParams = Object.keys(params).length > 0;\n const path = hasParams\n ? `/${packageName}/inappproducts/${sku}?${new URLSearchParams(params).toString()}`\n : `/${packageName}/inappproducts/${sku}`;\n const { data } = await http.put<InAppProduct>(path, body);\n return data;\n },\n\n async delete(packageName, sku) {\n await http.delete(`/${packageName}/inappproducts/${sku}`);\n },\n\n async batchUpdate(packageName, requests) {\n const { data } = await http.post<InAppProductsBatchUpdateResponse>(\n `/${packageName}/inappproducts:batchUpdate`,\n requests,\n );\n return data;\n },\n\n async batchGet(packageName, skus) {\n const params: Record<string, string> = {};\n if (skus.length > 0) {\n params[\"sku\"] = skus.join(\",\");\n }\n const { data } = await http.get<{ inappproduct: InAppProduct[] }>(\n `/${packageName}/inappproducts:batchGet`,\n Object.keys(params).length > 0 ? params : undefined,\n );\n return data.inappproduct || [];\n },\n },\n\n purchases: {\n async getProduct(packageName, productId, token) {\n const { data } = await http.get<ProductPurchase>(\n `/${packageName}/purchases/products/${productId}/tokens/${token}`,\n );\n return data;\n },\n\n async acknowledgeProduct(packageName, productId, token, body?) {\n await http.post(\n `/${packageName}/purchases/products/${productId}/tokens/${token}:acknowledge`,\n body,\n );\n },\n\n async consumeProduct(packageName, productId, token) {\n await http.post(`/${packageName}/purchases/products/${productId}/tokens/${token}:consume`);\n },\n\n async getSubscriptionV2(packageName, token) {\n const { data } = await http.get<SubscriptionPurchaseV2>(\n `/${packageName}/purchases/subscriptionsv2/tokens/${token}`,\n );\n return data;\n },\n\n async getSubscriptionV1(packageName, subscriptionId, token) {\n const { data } = await http.get<SubscriptionPurchase>(\n `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}`,\n );\n return data;\n },\n\n async cancelSubscription(packageName, subscriptionId, token) {\n await http.post(\n `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}:cancel`,\n );\n },\n\n async deferSubscription(packageName, subscriptionId, token, body) {\n const { data } = await http.post<SubscriptionDeferResponse>(\n `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}:defer`,\n body,\n );\n return data;\n },\n\n async revokeSubscriptionV2(packageName, token) {\n await http.post(`/${packageName}/purchases/subscriptionsv2/tokens/${token}:revoke`);\n },\n\n async refundSubscriptionV2(packageName, token) {\n await http.post(`/${packageName}/purchases/subscriptionsv2/tokens/${token}:refund`);\n },\n\n async listVoided(packageName, options?) {\n await rateLimit(limiter, \"voidedBurst\");\n await rateLimit(limiter, \"voidedDaily\");\n const params: Record<string, string> = {};\n if (options?.startTime) params[\"startTime\"] = options.startTime;\n if (options?.endTime) params[\"endTime\"] = options.endTime;\n if (options?.maxResults) params[\"maxResults\"] = String(options.maxResults);\n if (options?.token) params[\"token\"] = options.token;\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<VoidedPurchasesListResponse>(\n `/${packageName}/purchases/voidedpurchases`,\n hasParams ? params : undefined,\n );\n return data;\n },\n },\n\n orders: {\n async refund(packageName, orderId, body?) {\n await http.post(`/${packageName}/orders/${orderId}:refund`, body);\n },\n },\n\n monetization: {\n async convertRegionPrices(packageName, price) {\n const { data } = await http.post<ConvertRegionPricesResponse>(\n `/${packageName}/pricing:convertRegionPrices`,\n price,\n );\n return data;\n },\n },\n\n reports: {\n async list(packageName, reportType, year, month) {\n const monthStr = String(month).padStart(2, \"0\");\n const { data } = await http.get<ReportsListResponse>(\n `/${packageName}/reports/${reportType}/${year}/${monthStr}`,\n );\n return data;\n },\n },\n\n testers: {\n async get(packageName, editId, track) {\n const { data } = await http.get<Testers>(\n `/${packageName}/edits/${editId}/testers/${track}`,\n );\n return data;\n },\n\n async update(packageName, editId, track, testersData) {\n const { data } = await http.put<Testers>(\n `/${packageName}/edits/${editId}/testers/${track}`,\n testersData,\n );\n return data;\n },\n },\n\n deobfuscation: {\n async upload(packageName, editId, versionCode, filePath) {\n const { data } = await http.upload<DeobfuscationUploadResponse>(\n `/${packageName}/edits/${editId}/apks/${versionCode}/deobfuscationFiles/proguard`,\n filePath,\n \"application/octet-stream\",\n );\n if (!data.deobfuscationFile) {\n throw new PlayApiError(\n \"Upload succeeded but no deobfuscation file data returned\",\n \"API_EMPTY_RESPONSE\",\n 200,\n \"This is unexpected. Retry the upload or contact Google Play support if the issue persists.\",\n );\n }\n return data.deobfuscationFile;\n },\n },\n\n appRecovery: {\n async list(packageName, versionCode?) {\n const params: Record<string, string> = {};\n if (versionCode !== undefined) params[\"versionCode\"] = String(versionCode);\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<AppRecoveriesListResponse>(\n `/${packageName}/appRecoveries`,\n hasParams ? params : undefined,\n );\n return data.recoveryActions || [];\n },\n\n async cancel(packageName, appRecoveryId) {\n await http.post(`/${packageName}/appRecovery/${appRecoveryId}:cancel`);\n },\n\n async deploy(packageName, appRecoveryId) {\n await http.post(`/${packageName}/appRecovery/${appRecoveryId}:deploy`);\n },\n\n async create(packageName, request) {\n const { data } = await http.post<AppRecoveryAction>(\n `/${packageName}/appRecoveries`,\n request,\n );\n return data;\n },\n\n async addTargeting(packageName, appRecoveryId, targeting) {\n const { data } = await http.post<AppRecoveryAction>(\n `/${packageName}/appRecoveries/${appRecoveryId}:addTargeting`,\n targeting,\n );\n return data;\n },\n },\n\n externalTransactions: {\n async create(packageName, body) {\n const { data } = await http.post<ExternalTransaction>(\n `/${packageName}/externalTransactions`,\n body,\n );\n return data;\n },\n\n async get(packageName, transactionId) {\n const { data } = await http.get<ExternalTransaction>(\n `/${packageName}/externalTransactions/${transactionId}`,\n );\n return data;\n },\n\n async refund(packageName, transactionId, refundData) {\n const { data } = await http.post<ExternalTransaction>(\n `/${packageName}/externalTransactions/${transactionId}:refund`,\n refundData,\n );\n return data;\n },\n },\n\n deviceTiers: {\n async list(packageName) {\n const { data } = await http.get<DeviceTierConfigsListResponse>(\n `/${packageName}/deviceTierConfigs`,\n );\n return data.deviceTierConfigs || [];\n },\n\n async get(packageName, configId) {\n const { data } = await http.get<DeviceTierConfig>(\n `/${packageName}/deviceTierConfigs/${configId}`,\n );\n return data;\n },\n\n async create(packageName, config) {\n const { data } = await http.post<DeviceTierConfig>(\n `/${packageName}/deviceTierConfigs`,\n config,\n );\n return data;\n },\n },\n\n oneTimeProducts: {\n async list(packageName) {\n const { data } = await http.get<OneTimeProductsListResponse>(\n `/${packageName}/oneTimeProducts`,\n );\n return data;\n },\n\n async get(packageName, productId) {\n const { data } = await http.get<OneTimeProduct>(\n `/${packageName}/oneTimeProducts/${productId}`,\n );\n return data;\n },\n\n async create(packageName, body) {\n const params = new URLSearchParams({ \"regionsVersion.version\": \"2022/02\" });\n const { data } = await http.post<OneTimeProduct>(\n `/${packageName}/oneTimeProducts?${params.toString()}`,\n body,\n );\n return data;\n },\n\n async update(packageName, productId, body, updateMask?, regionsVersion?) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/oneTimeProducts/${productId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<OneTimeProduct>(path, body);\n return data;\n },\n\n async delete(packageName, productId) {\n await http.delete(`/${packageName}/oneTimeProducts/${productId}`);\n },\n\n async listOffers(packageName, productId) {\n const { data } = await http.get<OneTimeOffersListResponse>(\n `/${packageName}/oneTimeProducts/${productId}/offers`,\n );\n return data;\n },\n\n async getOffer(packageName, productId, offerId) {\n const { data } = await http.get<OneTimeOffer>(\n `/${packageName}/oneTimeProducts/${productId}/offers/${offerId}`,\n );\n return data;\n },\n\n async createOffer(packageName, productId, body) {\n const { data } = await http.post<OneTimeOffer>(\n `/${packageName}/oneTimeProducts/${productId}/offers`,\n body,\n );\n return data;\n },\n\n async updateOffer(packageName, productId, offerId, body, updateMask?, regionsVersion?) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/oneTimeProducts/${productId}/offers/${offerId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<OneTimeOffer>(path, body);\n return data;\n },\n\n async deleteOffer(packageName, productId, offerId) {\n await http.delete(`/${packageName}/oneTimeProducts/${productId}/offers/${offerId}`);\n },\n },\n\n purchaseOptions: {\n async list(packageName) {\n const { data } = await http.get<PurchaseOptionsListResponse>(\n `/${packageName}/purchaseOptions`,\n );\n return data;\n },\n\n async get(packageName, purchaseOptionId) {\n const { data } = await http.get<PurchaseOption>(\n `/${packageName}/purchaseOptions/${purchaseOptionId}`,\n );\n return data;\n },\n\n async create(packageName, body) {\n const { data } = await http.post<PurchaseOption>(`/${packageName}/purchaseOptions`, body);\n return data;\n },\n\n async activate(packageName, purchaseOptionId) {\n const { data } = await http.post<PurchaseOption>(\n `/${packageName}/purchaseOptions/${purchaseOptionId}:activate`,\n );\n return data;\n },\n\n async deactivate(packageName, purchaseOptionId) {\n const { data } = await http.post<PurchaseOption>(\n `/${packageName}/purchaseOptions/${purchaseOptionId}:deactivate`,\n );\n return data;\n },\n },\n\n internalAppSharing: {\n async uploadBundle(packageName, bundlePath) {\n const { data } = await http.uploadInternal<InternalAppSharingArtifact>(\n `/${packageName}/artifacts/bundle`,\n bundlePath,\n \"application/octet-stream\",\n );\n return data;\n },\n\n async uploadApk(packageName, apkPath) {\n const { data } = await http.uploadInternal<InternalAppSharingArtifact>(\n `/${packageName}/artifacts/apk`,\n apkPath,\n \"application/vnd.android.package-archive\",\n );\n return data;\n },\n },\n\n generatedApks: {\n async list(packageName, versionCode) {\n const { data } = await http.get<GeneratedApksPerVersion>(\n `/${packageName}/generatedApks/${versionCode}`,\n );\n return data.generatedApks || [];\n },\n\n async download(packageName, versionCode, id) {\n return http.download(`/${packageName}/generatedApks/${versionCode}/download/${id}`);\n },\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport type {\n AnomalyDetectionResponse,\n ApiClientOptions,\n ErrorIssuesResponse,\n ErrorReportsResponse,\n MetricSetQuery,\n MetricSetResponse,\n VitalsMetricSet,\n} from \"./types.js\";\n\nconst REPORTING_BASE_URL = \"https://playdeveloperreporting.googleapis.com/v1beta1\";\n\nexport interface ReportingApiClient {\n queryMetricSet(\n packageName: string,\n metricSet: VitalsMetricSet,\n query: MetricSetQuery,\n ): Promise<MetricSetResponse>;\n\n getAnomalies(packageName: string): Promise<AnomalyDetectionResponse>;\n\n searchErrorIssues(\n packageName: string,\n filter?: string,\n pageSize?: number,\n pageToken?: string,\n ): Promise<ErrorIssuesResponse>;\n\n searchErrorReports(\n packageName: string,\n issueName: string,\n pageSize?: number,\n pageToken?: string,\n ): Promise<ErrorReportsResponse>;\n}\n\nexport function createReportingClient(options: ApiClientOptions): ReportingApiClient {\n const http = createHttpClient({ ...options, baseUrl: REPORTING_BASE_URL });\n\n return {\n async queryMetricSet(packageName, metricSet, query) {\n const { data } = await http.post<MetricSetResponse>(\n `/apps/${packageName}/${metricSet}:query`,\n query,\n );\n return data;\n },\n\n async getAnomalies(packageName) {\n const { data } = await http.get<AnomalyDetectionResponse>(`/apps/${packageName}/anomalies`);\n return data;\n },\n\n async searchErrorIssues(packageName, filter?, pageSize?, pageToken?) {\n const params: Record<string, string> = {};\n if (filter) params[\"filter\"] = filter;\n if (pageSize) params[\"pageSize\"] = String(pageSize);\n if (pageToken) params[\"pageToken\"] = pageToken;\n const { data } = await http.get<ErrorIssuesResponse>(\n `/apps/${packageName}/errorIssues:search`,\n params,\n );\n return data;\n },\n\n async searchErrorReports(packageName, issueName, pageSize?, pageToken?) {\n const params: Record<string, string> = {};\n if (pageSize) params[\"pageSize\"] = String(pageSize);\n if (pageToken) params[\"pageToken\"] = pageToken;\n const { data } = await http.get<ErrorReportsResponse>(\n `/apps/${packageName}/errorIssues/${issueName}/reports`,\n params,\n );\n return data;\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport type { ApiClientOptions, User, UsersListResponse, Grant } from \"./types.js\";\n\nconst USERS_BASE_URL = \"https://androidpublisher.googleapis.com/androidpublisher/v3/developers\";\n\nexport interface GrantsListResponse {\n grants: Grant[];\n nextPageToken?: string;\n}\n\nexport interface UsersApiClient {\n list(\n developerId: string,\n options?: { pageToken?: string; pageSize?: number },\n ): Promise<UsersListResponse>;\n\n get(developerId: string, userId: string): Promise<User>;\n\n create(developerId: string, user: Partial<User>): Promise<User>;\n\n update(\n developerId: string,\n userId: string,\n user: Partial<User>,\n updateMask?: string,\n ): Promise<User>;\n\n delete(developerId: string, userId: string): Promise<void>;\n\n grants: {\n list(developerId: string, email: string): Promise<GrantsListResponse>;\n create(developerId: string, email: string, grant: Partial<Grant>): Promise<Grant>;\n patch(\n developerId: string,\n email: string,\n packageName: string,\n grant: Partial<Grant>,\n updateMask?: string,\n ): Promise<Grant>;\n delete(developerId: string, email: string, packageName: string): Promise<void>;\n };\n}\n\nexport function createUsersClient(options: ApiClientOptions): UsersApiClient {\n const http = createHttpClient({ ...options, baseUrl: USERS_BASE_URL });\n\n return {\n async list(developerId, listOptions?) {\n const params: Record<string, string> = {};\n if (listOptions?.pageToken) params[\"pageToken\"] = listOptions.pageToken;\n if (listOptions?.pageSize) params[\"pageSize\"] = String(listOptions.pageSize);\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<UsersListResponse>(\n `/${developerId}/users`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(developerId, userId) {\n const { data } = await http.get<User>(`/${developerId}/users/${userId}`);\n return data;\n },\n\n async create(developerId, user) {\n const { data } = await http.post<User>(`/${developerId}/users`, user);\n return data;\n },\n\n async update(developerId, userId, user, updateMask?) {\n let path = `/${developerId}/users/${userId}`;\n if (updateMask) {\n path += `?updateMask=${encodeURIComponent(updateMask).replace(/%2C/gi, \",\")}`;\n }\n const { data } = await http.patch<User>(path, user);\n return data;\n },\n\n async delete(developerId, userId) {\n await http.delete(`/${developerId}/users/${userId}`);\n },\n\n grants: {\n async list(developerId, email) {\n const { data } = await http.get<GrantsListResponse>(\n `/${developerId}/users/${encodeURIComponent(email)}/grants`,\n );\n return data;\n },\n\n async create(developerId, email, grant) {\n const { data } = await http.post<Grant>(\n `/${developerId}/users/${encodeURIComponent(email)}/grants`,\n grant,\n );\n return data;\n },\n\n async patch(developerId, email, packageName, grant, updateMask?) {\n let path = `/${developerId}/users/${encodeURIComponent(email)}/grants/${encodeURIComponent(packageName)}`;\n if (updateMask) {\n path += `?updateMask=${encodeURIComponent(updateMask)}`;\n }\n const { data } = await http.patch<Grant>(path, grant);\n return data;\n },\n\n async delete(developerId, email, packageName) {\n await http.delete(\n `/${developerId}/users/${encodeURIComponent(email)}/grants/${encodeURIComponent(packageName)}`,\n );\n },\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport type { ApiClientOptions } from \"./types.js\";\n\nconst GAMES_BASE_URL = \"https://games.googleapis.com/games/v1\";\n\nexport interface Leaderboard {\n id: string;\n name: string;\n order: string;\n iconUrl?: string;\n}\n\nexport interface LeaderboardScore {\n leaderboardId: string;\n scoreValue: string;\n formattedScore: string;\n writeTimestamp?: string;\n tag?: string;\n}\n\nexport interface Achievement {\n id: string;\n name: string;\n description: string;\n state: \"REVEALED\" | \"HIDDEN\" | \"UNLOCKED\";\n currentSteps?: number;\n totalSteps?: number;\n experiencePoints?: number;\n formattedCurrentStepsString?: string;\n}\n\nexport interface GameEvent {\n definitionId: string;\n numEvents: string;\n formattedNumEvents: string;\n kind?: string;\n}\n\nexport interface GamesApiClient {\n leaderboards: {\n list(packageName: string): Promise<{ items?: Leaderboard[]; nextPageToken?: string }>;\n get(packageName: string, leaderboardId: string): Promise<Leaderboard>;\n getScores(\n packageName: string,\n leaderboardId: string,\n collection: string,\n timeSpan: string,\n ): Promise<{ items?: LeaderboardScore[] }>;\n };\n achievements: {\n list(packageName: string): Promise<{ items?: Achievement[]; nextPageToken?: string }>;\n reveal(packageName: string, achievementId: string): Promise<{ currentState: string }>;\n };\n events: {\n list(packageName: string): Promise<{ items?: GameEvent[]; nextPageToken?: string }>;\n };\n}\n\nexport function createGamesClient(options: ApiClientOptions): GamesApiClient {\n const http = createHttpClient({ ...options, baseUrl: GAMES_BASE_URL });\n\n return {\n leaderboards: {\n async list(packageName) {\n const { data } = await http.get<{ items?: Leaderboard[]; nextPageToken?: string }>(\n `/leaderboards?applicationId=${packageName}`,\n );\n return data;\n },\n async get(packageName, leaderboardId) {\n const { data } = await http.get<Leaderboard>(\n `/leaderboards/${leaderboardId}?applicationId=${packageName}`,\n );\n return data;\n },\n async getScores(packageName, leaderboardId, collection, timeSpan) {\n const { data } = await http.get<{ items?: LeaderboardScore[] }>(\n `/leaderboards/${leaderboardId}/scores/${collection}?timeSpan=${timeSpan}&applicationId=${packageName}`,\n );\n return data;\n },\n },\n achievements: {\n async list(packageName) {\n const { data } = await http.get<{ items?: Achievement[]; nextPageToken?: string }>(\n `/achievements?applicationId=${packageName}`,\n );\n return data;\n },\n async reveal(packageName, achievementId) {\n const { data } = await http.post<{ currentState: string }>(\n `/achievements/${achievementId}/reveal?applicationId=${packageName}`,\n {},\n );\n return data;\n },\n },\n events: {\n async list(packageName) {\n const { data } = await http.get<{ items?: GameEvent[]; nextPageToken?: string }>(\n `/events?applicationId=${packageName}`,\n );\n return data;\n },\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport type { ApiClientOptions } from \"./types.js\";\n\nconst ENTERPRISE_BASE_URL = \"https://playcustomapp.googleapis.com/playcustomapp/v1/organizations\";\n\nexport interface CustomApp {\n packageName?: string;\n title: string;\n languageCode?: string;\n organizations?: Array<{ organizationId: string; organizationName?: string }>;\n}\n\nexport interface CustomAppsListResponse {\n customApps?: CustomApp[];\n nextPageToken?: string;\n}\n\nexport interface EnterpriseApiClient {\n apps: {\n create(organizationId: string, app: Partial<CustomApp>): Promise<CustomApp>;\n list(organizationId: string): Promise<CustomAppsListResponse>;\n };\n}\n\nexport function createEnterpriseClient(options: ApiClientOptions): EnterpriseApiClient {\n const http = createHttpClient({ ...options, baseUrl: ENTERPRISE_BASE_URL });\n\n return {\n apps: {\n async create(organizationId, app) {\n const { data } = await http.post<CustomApp>(`/${organizationId}/apps`, app);\n return data;\n },\n async list(organizationId) {\n const { data } = await http.get<CustomAppsListResponse>(`/${organizationId}/apps`);\n return data;\n },\n },\n };\n}\n","export interface RateLimitBucket {\n name: string;\n maxTokens: number;\n refillRate: number;\n refillIntervalMs: number;\n}\n\nexport interface RateLimiter {\n acquire(bucket: string): Promise<void>;\n}\n\ninterface BucketState {\n tokens: number;\n lastRefillTime: number;\n config: RateLimitBucket;\n}\n\nexport const RATE_LIMIT_BUCKETS: Record<string, RateLimitBucket> = {\n default: { name: \"default\", maxTokens: 200, refillRate: 200, refillIntervalMs: 1_000 },\n reviewsGet: { name: \"reviewsGet\", maxTokens: 200, refillRate: 200, refillIntervalMs: 3_600_000 },\n reviewsPost: {\n name: \"reviewsPost\",\n maxTokens: 2_000,\n refillRate: 2_000,\n refillIntervalMs: 86_400_000,\n },\n voidedBurst: { name: \"voidedBurst\", maxTokens: 30, refillRate: 30, refillIntervalMs: 30_000 },\n voidedDaily: {\n name: \"voidedDaily\",\n maxTokens: 6_000,\n refillRate: 6_000,\n refillIntervalMs: 86_400_000,\n },\n};\n\nexport function createRateLimiter(buckets?: RateLimitBucket[]): RateLimiter {\n const states = new Map<string, BucketState>();\n\n if (buckets) {\n for (const bucket of buckets) {\n states.set(bucket.name, {\n tokens: bucket.maxTokens,\n lastRefillTime: Date.now(),\n config: bucket,\n });\n }\n }\n\n return {\n async acquire(bucket: string): Promise<void> {\n const state = states.get(bucket);\n if (!state) return;\n\n const now = Date.now();\n const elapsed = now - state.lastRefillTime;\n const refill = Math.floor(\n (elapsed / state.config.refillIntervalMs) * state.config.refillRate,\n );\n\n if (refill > 0) {\n state.tokens = Math.min(state.config.maxTokens, state.tokens + refill);\n state.lastRefillTime = now;\n }\n\n if (state.tokens > 0) {\n state.tokens--;\n return;\n }\n\n const tokensNeeded = 1;\n const waitMs = Math.ceil(\n (tokensNeeded / state.config.refillRate) * state.config.refillIntervalMs,\n );\n await new Promise((r) => setTimeout(r, waitMs));\n\n // Recalculate refill based on actual elapsed time since last refill\n const afterWait = Date.now();\n const totalElapsed = afterWait - state.lastRefillTime;\n const newTokens = Math.floor(\n (totalElapsed / state.config.refillIntervalMs) * state.config.refillRate,\n );\n state.tokens = Math.min(state.config.maxTokens, newTokens) - 1;\n state.lastRefillTime = afterWait;\n },\n };\n}\n","export interface PaginateOptions {\n limit?: number;\n startPageToken?: string;\n}\n\nexport async function* paginate<TItem>(\n fetchPage: (pageToken?: string) => Promise<{ items: TItem[]; nextPageToken?: string }>,\n options?: PaginateOptions,\n): AsyncGenerator<TItem[], void, unknown> {\n let pageToken = options?.startPageToken;\n let collected = 0;\n const limit = options?.limit;\n\n for (;;) {\n if (limit !== undefined && collected >= limit) break;\n\n const page = await fetchPage(pageToken);\n const items = page.items;\n\n if (items.length === 0) break;\n\n if (limit !== undefined) {\n const remaining = limit - collected;\n if (items.length > remaining) {\n yield items.slice(0, remaining);\n return;\n }\n }\n\n yield items;\n collected += items.length;\n pageToken = page.nextPageToken;\n\n if (!pageToken) break;\n }\n}\n\nexport async function paginateAll<TItem>(\n fetchPage: (pageToken?: string) => Promise<{ items: TItem[]; nextPageToken?: string }>,\n options?: PaginateOptions,\n): Promise<{ items: TItem[]; nextPageToken?: string }> {\n const allItems: TItem[] = [];\n let lastPageToken: string | undefined;\n const limit = options?.limit;\n\n for await (const items of paginate(fetchPage, options)) {\n allItems.push(...items);\n if (limit !== undefined && allItems.length >= limit) break;\n }\n\n // If we stopped due to limit, try to get the next page token for resumption\n if (limit !== undefined && allItems.length >= limit) {\n lastPageToken = undefined; // Already truncated by paginate\n }\n\n return { items: allItems, nextPageToken: lastPageToken };\n}\n\n/**\n * Fetch multiple known pages in parallel.\n * Useful when page tokens are predictable or when pre-fetching subsequent pages\n * after an initial sequential fetch reveals the token pattern.\n *\n * @param fetchPage - Function that fetches a page given a token\n * @param pageTokens - Array of page tokens to fetch concurrently\n * @param concurrency - Max concurrent requests (default: 4)\n */\nexport async function paginateParallel<TItem>(\n fetchPage: (pageToken?: string) => Promise<{ items: TItem[]; nextPageToken?: string }>,\n pageTokens: string[],\n concurrency = 4,\n): Promise<{ items: TItem[]; nextPageToken?: string }> {\n const allItems: TItem[] = [];\n let lastNextPageToken: string | undefined;\n\n // Process in batches of `concurrency`\n for (let i = 0; i < pageTokens.length; i += concurrency) {\n const batch = pageTokens.slice(i, i + concurrency);\n const results = await Promise.all(batch.map((token) => fetchPage(token)));\n\n for (const result of results) {\n allItems.push(...result.items);\n if (result.nextPageToken) {\n lastNextPageToken = result.nextPageToken;\n }\n }\n }\n\n return { items: allItems, nextPageToken: lastNextPageToken };\n}\n"],"mappings":"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","names":["options"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/resumable-upload.ts","../src/client.ts","../src/rate-limiter.ts","../src/reporting-client.ts","../src/users-client.ts","../src/games-client.ts","../src/enterprise-client.ts","../src/paginate.ts"],"sourcesContent":["export class PlayApiError extends Error {\n public readonly exitCode = 4;\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number,\n public readonly suggestion?: string,\n ) {\n super(message);\n this.name = \"PlayApiError\";\n }\n toJSON() {\n return {\n success: false,\n error: {\n code: this.code,\n message: this.message,\n suggestion: this.suggestion,\n },\n };\n }\n}\n","import { readFile, stat } from \"node:fs/promises\";\nimport { resolve, isAbsolute } from \"node:path\";\nimport { PlayApiError } from \"./errors.js\";\nimport { resumableUpload, RESUMABLE_THRESHOLD } from \"./resumable-upload.js\";\nimport type { ApiClientOptions, ApiResponse, ResumableUploadOptions } from \"./types.js\";\n\n/** Strip HTML tags and collapse whitespace from a string. */\nfunction stripHtml(text: string): string {\n return text\n .replace(/<[^>]*>/g, \" \")\n .replace(/&[a-z]+;/gi, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/** Extract a short, safe error summary from API response body (no tokens/secrets). */\nfunction sanitizeErrorBody(body: string): string {\n try {\n const parsed = JSON.parse(body) as {\n error?: { message?: string; status?: string; code?: number };\n };\n if (parsed?.error?.message) {\n return `${parsed.error.code ?? \"?\"} ${parsed.error.status ?? \"\"}: ${parsed.error.message}`.trim();\n }\n } catch {\n // not JSON — may be HTML error page\n }\n // Strip HTML tags before truncating\n const cleaned = body.startsWith(\"<\") ? stripHtml(body) : body;\n return cleaned.length > 200 ? cleaned.slice(0, 200) + \"...\" : cleaned;\n}\n\n/** Validate upload file path to prevent path traversal. */\nfunction validateFilePath(filePath: string): string {\n const resolved = resolve(filePath);\n if (!isAbsolute(resolved)) {\n throw new PlayApiError(\n \"Invalid file path\",\n \"API_INVALID_PATH\",\n undefined,\n \"File path must resolve to an absolute path.\",\n );\n }\n // Block obvious traversal patterns in the original input\n if (filePath.includes(\"\\0\")) {\n throw new PlayApiError(\n \"Invalid file path: null bytes not allowed\",\n \"API_INVALID_PATH\",\n undefined,\n \"Provide a valid file path without null bytes.\",\n );\n }\n return resolved;\n}\n\nconst BASE_URL = \"https://androidpublisher.googleapis.com/androidpublisher/v3/applications\";\n\nconst UPLOAD_BASE_URL =\n \"https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications\";\n\nconst INTERNAL_SHARING_UPLOAD_BASE_URL =\n \"https://androidpublisher.googleapis.com/upload/internalappsharing/v3/applications\";\n\nexport interface HttpClient {\n get<T>(path: string, params?: Record<string, string>): Promise<ApiResponse<T>>;\n post<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;\n put<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;\n patch<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;\n delete<T>(path: string): Promise<ApiResponse<T>>;\n upload<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;\n uploadResumable<T>(path: string, filePath: string, contentType: string, options?: ResumableUploadOptions): Promise<ApiResponse<T>>;\n uploadInternal<T>(path: string, filePath: string, contentType: string): Promise<ApiResponse<T>>;\n download(path: string): Promise<ArrayBuffer>;\n}\n\nfunction envInt(name: string): number | undefined {\n const val = process.env[name];\n if (val === undefined) return undefined;\n const n = Number(val);\n return Number.isFinite(n) ? n : undefined;\n}\n\nfunction resolveOption(explicit: number | undefined, envName: string, fallback: number): number {\n return explicit ?? envInt(envName) ?? fallback;\n}\n\nfunction mapStatusToError(status: number, body: string): { code: string; suggestion?: string } {\n switch (status) {\n case 400: {\n // Detect FAILED_PRECONDITION: edit has expired\n try {\n const parsed = JSON.parse(body) as { error?: { status?: string; message?: string } };\n if (\n parsed?.error?.status === \"FAILED_PRECONDITION\" &&\n parsed.error.message?.toLowerCase().includes(\"edit\")\n ) {\n return {\n code: \"API_EDIT_EXPIRED\",\n suggestion: \"The edit session has expired. Retry the operation to open a fresh edit.\",\n };\n }\n } catch {\n /* not JSON */\n }\n return { code: \"API_HTTP_400\", suggestion: \"Check request parameters and try again.\" };\n }\n case 401:\n return {\n code: \"API_UNAUTHORIZED\",\n suggestion: \"Check that your access token is valid and not expired.\",\n };\n case 403:\n return {\n code: \"API_FORBIDDEN\",\n suggestion: \"Ensure the service account has the required permissions for this operation.\",\n };\n case 404:\n return {\n code: \"API_NOT_FOUND\",\n suggestion: \"Verify the package name and resource IDs are correct.\",\n };\n case 409:\n return {\n code: \"API_EDIT_CONFLICT\",\n suggestion: \"Another edit may be in progress. Delete the existing edit and retry.\",\n };\n case 429:\n return {\n code: \"API_RATE_LIMITED\",\n suggestion: \"Too many requests. The client will retry automatically.\",\n };\n default:\n if (status >= 500) {\n return {\n code: \"API_SERVER_ERROR\",\n suggestion: \"Google Play API server error. The client will retry automatically.\",\n };\n }\n return { code: `API_HTTP_${status}` };\n }\n}\n\nfunction isRetryable(status: number): boolean {\n return status === 408 || status === 429 || status >= 500;\n}\n\nfunction jitteredDelay(base: number, attempt: number, max: number): number {\n const exponential = base * 2 ** attempt;\n const capped = Math.min(exponential, max);\n return capped * (0.5 + Math.random() * 0.5);\n}\n\nexport function createHttpClient(options: ApiClientOptions): HttpClient {\n const maxRetries = resolveOption(options.maxRetries, \"GPC_MAX_RETRIES\", 5);\n const timeout = resolveOption(options.timeout, \"GPC_TIMEOUT\", 30_000);\n const uploadTimeoutExplicit = options.uploadTimeout ?? envInt(\"GPC_UPLOAD_TIMEOUT\");\n const baseDelay = resolveOption(options.baseDelay, \"GPC_BASE_DELAY\", 1_000);\n const maxDelay = resolveOption(options.maxDelay, \"GPC_MAX_DELAY\", 60_000);\n const onRetry = options.onRetry;\n\n async function request<T>(\n method: string,\n path: string,\n body?: unknown,\n params?: Record<string, string>,\n ): Promise<ApiResponse<T>> {\n let url = `${options.baseUrl ?? BASE_URL}${path}`;\n if (params) {\n const search = new URLSearchParams(params);\n url += `?${search.toString()}`;\n }\n\n // Fetch token once before retries — the auth layer handles its own caching and mutex\n let token = await options.auth.getAccessToken();\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);\n await new Promise((r) => setTimeout(r, delay));\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n \"Accept-Encoding\": \"gzip, deflate\",\n Connection: \"keep-alive\",\n };\n\n const init: RequestInit = {\n method,\n headers,\n signal: controller.signal,\n keepalive: true,\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, init);\n\n if (response.ok) {\n const text = await response.text();\n const data = text ? (JSON.parse(text) as T) : ({} as T);\n return { data, status: response.status };\n }\n\n const errorBody = await response.text();\n const { code, suggestion } = mapStatusToError(response.status, errorBody);\n\n const err = new PlayApiError(\n `${method} ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,\n code,\n response.status,\n suggestion,\n );\n\n if (isRetryable(response.status) && attempt < maxRetries) {\n lastError = err;\n const delay = jitteredDelay(baseDelay, attempt, maxDelay);\n onRetry?.({\n attempt: attempt + 1,\n method,\n path,\n status: response.status,\n error: err.message,\n delayMs: Math.round(delay),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n\n // On 401, refresh token once before giving up\n if (response.status === 401 && attempt < maxRetries) {\n token = await options.auth.getAccessToken();\n lastError = err;\n continue;\n }\n\n throw err;\n } catch (error) {\n if (error instanceof PlayApiError) {\n throw error;\n }\n\n if (error instanceof DOMException && error.name === \"AbortError\") {\n const timeoutErr = new PlayApiError(\n `${method} ${path} timed out after ${timeout}ms`,\n \"API_TIMEOUT\",\n undefined,\n \"The request exceeded the configured timeout. Consider increasing the timeout value.\",\n );\n if (attempt < maxRetries) {\n lastError = timeoutErr;\n onRetry?.({\n attempt: attempt + 1,\n method,\n path,\n error: timeoutErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw timeoutErr;\n }\n\n const networkErr = new PlayApiError(\n `${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,\n \"API_NETWORK_ERROR\",\n undefined,\n \"A network error occurred. Check your internet connection.\",\n );\n if (attempt < maxRetries) {\n lastError = networkErr;\n onRetry?.({\n attempt: attempt + 1,\n method,\n path,\n error: networkErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw networkErr;\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Should not reach here, but just in case\n throw (\n lastError ??\n new PlayApiError(\n \"Request failed\",\n \"API_NETWORK_ERROR\",\n undefined,\n \"Check your network connection and try again. Use --verbose for details.\",\n )\n );\n }\n\n /** Calculate upload timeout: explicit value, or auto-scale from file size (1 MB/s minimum throughput + 30s overhead). */\n function computeUploadTimeout(fileSizeBytes: number): number {\n if (uploadTimeoutExplicit !== undefined) return uploadTimeoutExplicit;\n // Base: 30s overhead + 1s per MB (assumes ~1 MB/s minimum upload speed)\n const sizeMb = fileSizeBytes / (1024 * 1024);\n return Math.max(timeout, 30_000 + Math.ceil(sizeMb) * 1_000);\n }\n\n async function uploadRequest<T>(\n path: string,\n filePath: string,\n contentType: string,\n baseUrl: string = UPLOAD_BASE_URL,\n ): Promise<ApiResponse<T>> {\n const url = `${baseUrl}${path}`;\n const safeFilePath = validateFilePath(filePath);\n const fileBuffer = await readFile(safeFilePath);\n const effectiveTimeout = computeUploadTimeout(fileBuffer.byteLength);\n\n // Fetch token once before retries\n let token = await options.auth.getAccessToken();\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);\n await new Promise((r) => setTimeout(r, delay));\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), effectiveTimeout);\n\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": contentType,\n \"Accept-Encoding\": \"gzip, deflate\",\n Connection: \"keep-alive\",\n };\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: fileBuffer,\n signal: controller.signal,\n keepalive: true,\n });\n\n if (response.ok) {\n const text = await response.text();\n const data = text ? (JSON.parse(text) as T) : ({} as T);\n return { data, status: response.status };\n }\n\n const errorBody = await response.text();\n const { code, suggestion } = mapStatusToError(response.status, errorBody);\n\n const err = new PlayApiError(\n `POST upload ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,\n code,\n response.status,\n suggestion,\n );\n\n if (isRetryable(response.status) && attempt < maxRetries) {\n lastError = err;\n const delay = jitteredDelay(baseDelay, attempt, maxDelay);\n onRetry?.({\n attempt: attempt + 1,\n method: \"POST\",\n path: `upload ${path}`,\n status: response.status,\n error: err.message,\n delayMs: Math.round(delay),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n\n // On 401, refresh token once before giving up\n if (response.status === 401 && attempt < maxRetries) {\n token = await options.auth.getAccessToken();\n lastError = err;\n continue;\n }\n\n throw err;\n } catch (error) {\n if (error instanceof PlayApiError) {\n throw error;\n }\n\n if (error instanceof DOMException && error.name === \"AbortError\") {\n const sizeMb = Math.round(fileBuffer.byteLength / (1024 * 1024));\n const timeoutErr = new PlayApiError(\n `POST upload ${path} timed out after ${effectiveTimeout}ms (file: ${sizeMb} MB)`,\n \"API_TIMEOUT\",\n undefined,\n `Upload timed out. Set GPC_UPLOAD_TIMEOUT=${effectiveTimeout * 2} (ms) or use --timeout to increase.`,\n );\n if (attempt < maxRetries) {\n lastError = timeoutErr;\n onRetry?.({\n attempt: attempt + 1,\n method: \"POST\",\n path: `upload ${path}`,\n error: timeoutErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw timeoutErr;\n }\n\n const networkErr = new PlayApiError(\n `POST upload ${path} failed: ${error instanceof Error ? error.message : String(error)}`,\n \"API_NETWORK_ERROR\",\n undefined,\n \"A network error occurred. Check your internet connection.\",\n );\n if (attempt < maxRetries) {\n lastError = networkErr;\n onRetry?.({\n attempt: attempt + 1,\n method: \"POST\",\n path: `upload ${path}`,\n error: networkErr.message,\n delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),\n timestamp: new Date().toISOString(),\n });\n continue;\n }\n throw networkErr;\n } finally {\n clearTimeout(timer);\n }\n }\n\n throw (\n lastError ??\n new PlayApiError(\n \"Upload request failed\",\n \"API_NETWORK_ERROR\",\n undefined,\n \"Check your network connection and try again. Use --verbose for details.\",\n )\n );\n }\n\n return {\n get<T>(path: string, params?: Record<string, string>) {\n return request<T>(\"GET\", path, undefined, params);\n },\n post<T>(path: string, body?: unknown) {\n return request<T>(\"POST\", path, body);\n },\n put<T>(path: string, body?: unknown) {\n return request<T>(\"PUT\", path, body);\n },\n patch<T>(path: string, body?: unknown) {\n return request<T>(\"PATCH\", path, body);\n },\n delete<T>(path: string) {\n return request<T>(\"DELETE\", path);\n },\n upload<T>(path: string, filePath: string, contentType: string) {\n return uploadRequest<T>(path, filePath, contentType);\n },\n async uploadResumable<T>(path: string, filePath: string, contentType: string, uploadOptions?: ResumableUploadOptions) {\n const safeFilePath = validateFilePath(filePath);\n const fileStats = await stat(safeFilePath);\n\n // For small files, fall back to simple upload (less overhead)\n const threshold = envInt(\"GPC_UPLOAD_RESUMABLE_THRESHOLD\") ?? RESUMABLE_THRESHOLD;\n if (fileStats.size < threshold && !uploadOptions?.resumeSessionUri) {\n // Fire progress callbacks for consistency\n uploadOptions?.onProgress?.({ bytesUploaded: 0, totalBytes: fileStats.size, percent: 0, bytesPerSecond: 0, etaSeconds: 0 });\n const result = await uploadRequest<T>(path, safeFilePath, contentType);\n uploadOptions?.onProgress?.({ bytesUploaded: fileStats.size, totalBytes: fileStats.size, percent: 100, bytesPerSecond: 0, etaSeconds: 0 });\n return result;\n }\n\n const uploadUrl = `${UPLOAD_BASE_URL}${path}`;\n return resumableUpload<T>(uploadUrl, safeFilePath, contentType, {\n getAccessToken: () => options.auth.getAccessToken(),\n maxRetries,\n baseDelay,\n maxDelay,\n onRetry,\n }, uploadOptions);\n },\n uploadInternal<T>(path: string, filePath: string, contentType: string) {\n return uploadRequest<T>(path, filePath, contentType, INTERNAL_SHARING_UPLOAD_BASE_URL);\n },\n async download(path: string): Promise<ArrayBuffer> {\n const url = `${options.baseUrl ?? BASE_URL}${path}`;\n const token = await options.auth.getAccessToken();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Accept-Encoding\": \"gzip, deflate\",\n Connection: \"keep-alive\",\n },\n signal: controller.signal,\n keepalive: true,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n const { code, suggestion } = mapStatusToError(response.status, errorBody);\n throw new PlayApiError(\n `GET ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,\n code,\n response.status,\n suggestion,\n );\n }\n\n return await response.arrayBuffer();\n } finally {\n clearTimeout(timer);\n }\n },\n };\n}\n","import { open, stat } from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { PlayApiError } from \"./errors.js\";\nimport type { ApiResponse, ResumableUploadOptions, UploadProgressEvent } from \"./types.js\";\n\n/** 256 KB — Google requires chunk sizes to be multiples of this. */\nconst CHUNK_ALIGNMENT = 256 * 1024;\n\n/** 8 MB — default chunk size (multiple of 256 KB). */\nconst DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024;\n\n/** Files below this threshold use simple upload instead. */\nexport const RESUMABLE_THRESHOLD = 5 * 1024 * 1024; // 5 MB\n\nfunction envInt(name: string): number | undefined {\n const val = process.env[name];\n if (val === undefined) return undefined;\n const n = Number(val);\n return Number.isFinite(n) ? n : undefined;\n}\n\nfunction resolveChunkSize(explicit?: number): number {\n const size = explicit ?? envInt(\"GPC_UPLOAD_CHUNK_SIZE\") ?? DEFAULT_CHUNK_SIZE;\n if (size < CHUNK_ALIGNMENT || size % CHUNK_ALIGNMENT !== 0) {\n throw new PlayApiError(\n `Chunk size must be a multiple of 256 KB (got ${size} bytes)`,\n \"UPLOAD_INVALID_CHUNK_SIZE\",\n undefined,\n `Use a multiple of 262144 (256 KB). Common values: 1048576 (1 MB), 8388608 (8 MB), 16777216 (16 MB).`,\n );\n }\n return size;\n}\n\nfunction jitteredDelay(base: number, attempt: number, max: number): number {\n const exponential = base * 2 ** attempt;\n const capped = Math.min(exponential, max);\n return capped * (0.5 + Math.random() * 0.5);\n}\n\ninterface ResumableUploadContext {\n getAccessToken: () => Promise<string>;\n maxRetries: number;\n baseDelay: number;\n maxDelay: number;\n onRetry?: (info: { attempt: number; method: string; path: string; error: string; delayMs: number; timestamp: string }) => void;\n}\n\n/**\n * Google Play resumable upload protocol.\n *\n * 1. Initiate session → POST with X-Upload-Content-Type/Length, get Location header\n * 2. Stream chunks → PUT chunks with Content-Range to session URI\n * 3. Resume on failure → PUT with Content-Range: bytes * /total to query progress\n */\nexport async function resumableUpload<T>(\n uploadUrl: string,\n filePath: string,\n contentType: string,\n ctx: ResumableUploadContext,\n options?: ResumableUploadOptions,\n): Promise<ApiResponse<T>> {\n const chunkSize = resolveChunkSize(options?.chunkSize);\n const maxResumeAttempts = options?.maxResumeAttempts ?? 5;\n const onProgress = options?.onProgress;\n\n const fileStats = await stat(filePath);\n const totalBytes = fileStats.size;\n\n // Step 1: Initiate resumable session (or resume existing)\n let sessionUri = options?.resumeSessionUri;\n if (!sessionUri) {\n sessionUri = await initiateSession(uploadUrl, contentType, totalBytes, ctx);\n }\n\n // Step 2: Stream file in chunks\n const startTime = Date.now();\n let offset = 0;\n\n // If resuming, query the server for where we left off\n if (options?.resumeSessionUri) {\n offset = await queryProgress(sessionUri, totalBytes, ctx);\n }\n\n let fh: FileHandle | undefined;\n try {\n fh = await open(filePath, \"r\");\n const chunkBuffer = Buffer.alloc(chunkSize);\n\n while (offset < totalBytes) {\n const remaining = totalBytes - offset;\n const bytesToRead = Math.min(chunkSize, remaining);\n const { bytesRead } = await fh.read(chunkBuffer, 0, bytesToRead, offset);\n\n if (bytesRead === 0) break;\n\n // Always copy the chunk to avoid race conditions — fetch may read the body\n // asynchronously while we overwrite chunkBuffer on the next iteration\n const chunk = Buffer.from(chunkBuffer.buffer, chunkBuffer.byteOffset, bytesRead);\n const rangeEnd = offset + bytesRead - 1;\n const contentRange = `bytes ${offset}-${rangeEnd}/${totalBytes}`;\n\n let result: ChunkResult<T> | undefined;\n for (let attempt = 0; attempt <= maxResumeAttempts; attempt++) {\n if (attempt > 0) {\n const delay = jitteredDelay(1000, attempt - 1, 30_000);\n await new Promise((r) => setTimeout(r, delay));\n\n // Query server for actual progress before retrying\n try {\n const serverOffset = await queryProgress(sessionUri, totalBytes, ctx);\n if (serverOffset >= offset + bytesRead) {\n // Server already has this chunk, advance\n result = { complete: false };\n break;\n }\n if (serverOffset > offset) {\n // Partial — skip to where server is, but we'll need to re-read\n // For simplicity, just retry the whole chunk since it's small enough\n }\n } catch {\n // Query failed — just retry the PUT\n }\n\n ctx.onRetry?.({\n attempt,\n method: \"PUT\",\n path: sessionUri,\n error: `Chunk upload failed at offset ${offset}, retrying`,\n delayMs: Math.round(delay),\n timestamp: new Date().toISOString(),\n });\n }\n\n result = await sendChunk<T>(sessionUri, chunk, contentRange, ctx);\n if (result) break;\n }\n\n if (!result) {\n throw new PlayApiError(\n `Upload failed: chunk at offset ${offset} could not be sent after ${maxResumeAttempts + 1} attempts`,\n \"UPLOAD_CHUNK_FAILED\",\n undefined,\n `The upload session is still valid for up to 1 week. Resume with: --resume-uri \"${sessionUri}\"`,\n );\n }\n\n offset += bytesRead;\n\n // Fire progress callback\n if (onProgress) {\n const elapsed = (Date.now() - startTime) / 1000;\n const bytesPerSecond = elapsed > 0 ? offset / elapsed : 0;\n const remainingBytes = totalBytes - offset;\n const etaSeconds = bytesPerSecond > 0 ? remainingBytes / bytesPerSecond : 0;\n\n onProgress({\n bytesUploaded: offset,\n totalBytes,\n percent: Math.round((offset / totalBytes) * 100),\n bytesPerSecond: Math.round(bytesPerSecond),\n etaSeconds: Math.round(etaSeconds),\n });\n }\n\n // If the server returned a final response (200/201), we're done\n if (result.complete && result.response) {\n return result.response;\n }\n }\n\n // Should not reach here — last chunk should have returned complete\n throw new PlayApiError(\n \"Upload finished sending all bytes but did not receive a completion response\",\n \"UPLOAD_NO_COMPLETION\",\n undefined,\n \"This is unexpected. Try uploading again.\",\n );\n } finally {\n await fh?.close();\n }\n}\n\ninterface ChunkResult<T> {\n complete: boolean;\n response?: ApiResponse<T>;\n}\n\nasync function initiateSession(\n uploadUrl: string,\n contentType: string,\n totalBytes: number,\n ctx: ResumableUploadContext,\n): Promise<string> {\n const token = await ctx.getAccessToken();\n const url = uploadUrl.includes(\"?\")\n ? `${uploadUrl}&uploadType=resumable`\n : `${uploadUrl}?uploadType=resumable`;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 60_000); // 60s timeout for session initiation\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"X-Upload-Content-Type\": contentType,\n \"X-Upload-Content-Length\": String(totalBytes),\n \"Content-Length\": \"0\",\n },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n\n if (!response.ok) {\n const body = await response.text();\n throw new PlayApiError(\n `Failed to initiate resumable upload: ${response.status} ${body.slice(0, 200)}`,\n \"UPLOAD_INITIATE_FAILED\",\n response.status,\n \"Check that the package name, edit ID, and credentials are correct.\",\n );\n }\n\n const location = response.headers.get(\"Location\");\n if (!location) {\n throw new PlayApiError(\n \"Resumable upload initiation did not return a session URI (Location header missing)\",\n \"UPLOAD_NO_SESSION_URI\",\n response.status,\n \"This is a Google API issue. Try again.\",\n );\n }\n\n return location;\n}\n\nasync function sendChunk<T>(\n sessionUri: string,\n chunk: Buffer,\n contentRange: string,\n ctx: ResumableUploadContext,\n): Promise<ChunkResult<T> | undefined> {\n const token = await ctx.getAccessToken();\n\n // Timeout: 30s base + 1s per MB of chunk data\n const chunkTimeoutMs = 30_000 + Math.ceil(chunk.byteLength / (1024 * 1024)) * 1_000;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), chunkTimeoutMs);\n let response: Response;\n try {\n response = await fetch(sessionUri, {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Length\": String(chunk.byteLength),\n \"Content-Range\": contentRange,\n },\n body: chunk,\n signal: controller.signal,\n });\n } catch {\n // Network error or timeout — caller will retry\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n\n // 200 or 201 — upload complete\n if (response.status === 200 || response.status === 201) {\n const text = await response.text();\n const data = text ? (JSON.parse(text) as T) : ({} as T);\n return { complete: true, response: { data, status: response.status } };\n }\n\n // 308 Resume Incomplete — chunk accepted, continue\n if (response.status === 308) {\n await response.body?.cancel(); // Consume response to free connection\n return { complete: false };\n }\n\n // 404 — session not found (expired or invalid)\n if (response.status === 404) {\n throw new PlayApiError(\n \"Upload session not found. The session may have expired.\",\n \"UPLOAD_SESSION_NOT_FOUND\",\n 404,\n \"Start a new upload. Resumable upload sessions are valid for up to 1 week.\",\n );\n }\n\n // 410 — session gone\n if (response.status === 410) {\n throw new PlayApiError(\n \"Upload session has expired.\",\n \"UPLOAD_SESSION_EXPIRED\",\n 410,\n \"Start a new upload from the beginning.\",\n );\n }\n\n // 401 — token expired, refresh and retry\n if (response.status === 401) {\n await response.body?.cancel();\n return undefined; // Caller will retry, which will get a fresh token\n }\n\n // 5xx or 429 — retryable\n if (response.status === 429 || response.status >= 500) {\n await response.body?.cancel();\n return undefined; // Caller will retry\n }\n\n // Non-retryable error\n const body = await response.text();\n throw new PlayApiError(\n `Upload chunk failed with status ${response.status}: ${body.slice(0, 200)}`,\n `UPLOAD_HTTP_${response.status}`,\n response.status,\n \"The upload encountered an unexpected error.\",\n );\n}\n\nasync function queryProgress(\n sessionUri: string,\n totalBytes: number,\n ctx: ResumableUploadContext,\n): Promise<number> {\n const token = await ctx.getAccessToken();\n\n const response = await fetch(sessionUri, {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Length\": \"0\",\n \"Content-Range\": `bytes */${totalBytes}`,\n },\n });\n\n // 308 — partial upload, Range header tells us where we are\n if (response.status === 308) {\n await response.body?.cancel();\n const range = response.headers.get(\"Range\");\n if (range) {\n // Format: \"bytes=0-12345\"\n const match = range.match(/bytes=0-(\\d+)/);\n if (match) {\n return Number(match[1]) + 1; // Next byte to upload\n }\n }\n // No Range header means server has received 0 bytes\n return 0;\n }\n\n // 200/201 — upload is actually complete\n if (response.status === 200 || response.status === 201) {\n await response.body?.cancel();\n return totalBytes;\n }\n\n // 404/410 — session expired\n if (response.status === 404 || response.status === 410) {\n await response.body?.cancel();\n throw new PlayApiError(\n \"Upload session has expired while querying progress.\",\n \"UPLOAD_SESSION_EXPIRED\",\n response.status,\n \"Start a new upload from the beginning.\",\n );\n }\n\n await response.body?.cancel();\n return 0;\n}\n","import { PlayApiError } from \"./errors.js\";\nimport { createHttpClient } from \"./http.js\";\nimport type { RateLimiter } from \"./rate-limiter.js\";\nimport type {\n ApiClientOptions,\n AppDetails,\n AppEdit,\n AppRecoveriesListResponse,\n AppRecoveryAction,\n AppRecoveryTargeting,\n CreateAppRecoveryActionRequest,\n BasePlanMigratePricesRequest,\n Bundle,\n BundleListResponse,\n ConvertRegionPricesRequest,\n ConvertRegionPricesResponse,\n CountryAvailability,\n DataSafety,\n DeobfuscationFile,\n DeobfuscationUploadResponse,\n DeviceTierConfig,\n DeviceTierConfigsListResponse,\n ExternalTransaction,\n ExternalTransactionRefund,\n ExternallyHostedApk,\n ExternallyHostedApkResponse,\n Image,\n ImageType,\n ImageUploadResponse,\n ImagesDeleteAllResponse,\n ImagesListResponse,\n InAppProduct,\n InAppProductsListResponse,\n Listing,\n ListingsListResponse,\n OffersListResponse,\n ProductPurchase,\n Release,\n ReportsListResponse,\n ReportType,\n Review,\n ReviewReplyRequest,\n ReviewReplyResponse,\n ReviewsListOptions,\n ReviewsListResponse,\n Subscription,\n SubscriptionDeferRequest,\n SubscriptionDeferResponse,\n SubscriptionOffer,\n SubscriptionPurchase,\n SubscriptionPurchaseV2,\n SubscriptionsListResponse,\n Testers,\n Track,\n TrackListResponse,\n VoidedPurchasesListResponse,\n OneTimeProduct,\n OneTimeProductsListResponse,\n OneTimeOffer,\n OneTimeOffersListResponse,\n InternalAppSharingArtifact,\n GeneratedApk,\n GeneratedApksPerVersion,\n PurchaseOption,\n PurchaseOptionsListResponse,\n InAppProductsBatchUpdateRequest,\n InAppProductsBatchUpdateResponse,\n} from \"./types.js\";\n\nexport interface PlayApiClient {\n edits: {\n insert(packageName: string): Promise<AppEdit>;\n get(packageName: string, editId: string): Promise<AppEdit>;\n validate(packageName: string, editId: string): Promise<AppEdit>;\n commit(packageName: string, editId: string): Promise<AppEdit>;\n delete(packageName: string, editId: string): Promise<void>;\n };\n\n details: {\n get(packageName: string, editId: string): Promise<AppDetails>;\n update(packageName: string, editId: string, details: Partial<AppDetails>): Promise<AppDetails>;\n patch(packageName: string, editId: string, partial: Partial<AppDetails>): Promise<AppDetails>;\n };\n\n bundles: {\n list(packageName: string, editId: string): Promise<Bundle[]>;\n upload(packageName: string, editId: string, filePath: string, uploadOptions?: import(\"./types.js\").ResumableUploadOptions): Promise<Bundle>;\n };\n\n tracks: {\n list(packageName: string, editId: string): Promise<Track[]>;\n get(packageName: string, editId: string, track: string): Promise<Track>;\n create(packageName: string, editId: string, trackName: string): Promise<Track>;\n update(packageName: string, editId: string, track: string, release: Release): Promise<Track>;\n };\n\n apks: {\n addExternallyHosted(\n packageName: string,\n editId: string,\n data: ExternallyHostedApk,\n ): Promise<ExternallyHostedApkResponse>;\n };\n\n listings: {\n list(packageName: string, editId: string): Promise<Listing[]>;\n get(packageName: string, editId: string, language: string): Promise<Listing>;\n update(\n packageName: string,\n editId: string,\n language: string,\n listing: Omit<Listing, \"language\">,\n ): Promise<Listing>;\n patch(\n packageName: string,\n editId: string,\n language: string,\n partial: Partial<Omit<Listing, \"language\">>,\n ): Promise<Listing>;\n delete(packageName: string, editId: string, language: string): Promise<void>;\n deleteAll(packageName: string, editId: string): Promise<void>;\n };\n\n images: {\n list(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n ): Promise<Image[]>;\n upload(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n filePath: string,\n ): Promise<Image>;\n delete(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n imageId: string,\n ): Promise<void>;\n deleteAll(\n packageName: string,\n editId: string,\n language: string,\n imageType: ImageType,\n ): Promise<Image[]>;\n };\n\n countryAvailability: {\n get(packageName: string, editId: string, track: string): Promise<CountryAvailability>;\n };\n\n dataSafety: {\n get(packageName: string): Promise<DataSafety>;\n update(packageName: string, data: DataSafety): Promise<DataSafety>;\n };\n\n reviews: {\n list(packageName: string, options?: ReviewsListOptions): Promise<ReviewsListResponse>;\n get(packageName: string, reviewId: string, translationLanguage?: string): Promise<Review>;\n reply(packageName: string, reviewId: string, replyText: string): Promise<ReviewReplyResponse>;\n };\n\n subscriptions: {\n list(\n packageName: string,\n options?: { pageToken?: string; pageSize?: number },\n ): Promise<SubscriptionsListResponse>;\n get(packageName: string, productId: string): Promise<Subscription>;\n create(packageName: string, data: Subscription, productId?: string): Promise<Subscription>;\n update(\n packageName: string,\n productId: string,\n data: Subscription,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<Subscription>;\n delete(packageName: string, productId: string): Promise<void>;\n activateBasePlan(\n packageName: string,\n productId: string,\n basePlanId: string,\n ): Promise<Subscription>;\n deactivateBasePlan(\n packageName: string,\n productId: string,\n basePlanId: string,\n ): Promise<Subscription>;\n deleteBasePlan(packageName: string, productId: string, basePlanId: string): Promise<void>;\n migratePrices(\n packageName: string,\n productId: string,\n basePlanId: string,\n body: BasePlanMigratePricesRequest,\n ): Promise<Subscription>;\n listOffers(\n packageName: string,\n productId: string,\n basePlanId: string,\n ): Promise<OffersListResponse>;\n getOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<SubscriptionOffer>;\n createOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n data: SubscriptionOffer,\n offerId?: string,\n ): Promise<SubscriptionOffer>;\n updateOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n data: SubscriptionOffer,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<SubscriptionOffer>;\n deleteOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<void>;\n activateOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<SubscriptionOffer>;\n deactivateOffer(\n packageName: string,\n productId: string,\n basePlanId: string,\n offerId: string,\n ): Promise<SubscriptionOffer>;\n };\n\n inappproducts: {\n list(\n packageName: string,\n options?: { token?: string; maxResults?: number },\n ): Promise<InAppProductsListResponse>;\n get(packageName: string, sku: string): Promise<InAppProduct>;\n create(\n packageName: string,\n data: InAppProduct,\n options?: { autoConvertMissingPrices?: boolean },\n ): Promise<InAppProduct>;\n update(\n packageName: string,\n sku: string,\n data: InAppProduct,\n options?: { autoConvertMissingPrices?: boolean; allowMissing?: boolean },\n ): Promise<InAppProduct>;\n delete(packageName: string, sku: string): Promise<void>;\n batchUpdate(\n packageName: string,\n requests: InAppProductsBatchUpdateRequest,\n ): Promise<InAppProductsBatchUpdateResponse>;\n batchGet(packageName: string, skus: string[]): Promise<InAppProduct[]>;\n };\n\n purchases: {\n getProduct(packageName: string, productId: string, token: string): Promise<ProductPurchase>;\n acknowledgeProduct(\n packageName: string,\n productId: string,\n token: string,\n body?: { developerPayload?: string },\n ): Promise<void>;\n consumeProduct(packageName: string, productId: string, token: string): Promise<void>;\n getSubscriptionV2(packageName: string, token: string): Promise<SubscriptionPurchaseV2>;\n getSubscriptionV1(\n packageName: string,\n subscriptionId: string,\n token: string,\n ): Promise<SubscriptionPurchase>;\n cancelSubscription(packageName: string, subscriptionId: string, token: string): Promise<void>;\n deferSubscription(\n packageName: string,\n subscriptionId: string,\n token: string,\n body: SubscriptionDeferRequest,\n ): Promise<SubscriptionDeferResponse>;\n revokeSubscriptionV2(packageName: string, token: string): Promise<void>;\n refundSubscriptionV2(packageName: string, token: string): Promise<void>;\n listVoided(\n packageName: string,\n options?: { startTime?: string; endTime?: string; maxResults?: number; token?: string },\n ): Promise<VoidedPurchasesListResponse>;\n };\n\n orders: {\n refund(\n packageName: string,\n orderId: string,\n body?: { fullRefund?: boolean; proratedRefund?: boolean },\n ): Promise<void>;\n };\n\n monetization: {\n convertRegionPrices(\n packageName: string,\n price: ConvertRegionPricesRequest,\n ): Promise<ConvertRegionPricesResponse>;\n };\n\n reports: {\n list(\n packageName: string,\n reportType: ReportType,\n year: number,\n month: number,\n ): Promise<ReportsListResponse>;\n };\n\n testers: {\n get(packageName: string, editId: string, track: string): Promise<Testers>;\n update(packageName: string, editId: string, track: string, testers: Testers): Promise<Testers>;\n };\n\n deobfuscation: {\n upload(\n packageName: string,\n editId: string,\n versionCode: number,\n filePath: string,\n ): Promise<DeobfuscationFile>;\n };\n\n appRecovery: {\n list(packageName: string, versionCode?: number): Promise<AppRecoveryAction[]>;\n cancel(packageName: string, appRecoveryId: string): Promise<void>;\n deploy(packageName: string, appRecoveryId: string): Promise<void>;\n create(\n packageName: string,\n request: CreateAppRecoveryActionRequest,\n ): Promise<AppRecoveryAction>;\n addTargeting(\n packageName: string,\n appRecoveryId: string,\n targeting: AppRecoveryTargeting,\n ): Promise<AppRecoveryAction>;\n };\n\n externalTransactions: {\n create(packageName: string, data: ExternalTransaction): Promise<ExternalTransaction>;\n get(packageName: string, transactionId: string): Promise<ExternalTransaction>;\n refund(\n packageName: string,\n transactionId: string,\n refundData: ExternalTransactionRefund,\n ): Promise<ExternalTransaction>;\n };\n\n deviceTiers: {\n list(packageName: string): Promise<DeviceTierConfig[]>;\n get(packageName: string, configId: string): Promise<DeviceTierConfig>;\n create(packageName: string, config: DeviceTierConfig): Promise<DeviceTierConfig>;\n };\n\n oneTimeProducts: {\n list(packageName: string): Promise<OneTimeProductsListResponse>;\n get(packageName: string, productId: string): Promise<OneTimeProduct>;\n create(packageName: string, product: OneTimeProduct): Promise<OneTimeProduct>;\n update(\n packageName: string,\n productId: string,\n product: Partial<OneTimeProduct>,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<OneTimeProduct>;\n delete(packageName: string, productId: string): Promise<void>;\n listOffers(packageName: string, productId: string): Promise<OneTimeOffersListResponse>;\n getOffer(packageName: string, productId: string, offerId: string): Promise<OneTimeOffer>;\n createOffer(packageName: string, productId: string, offer: OneTimeOffer): Promise<OneTimeOffer>;\n updateOffer(\n packageName: string,\n productId: string,\n offerId: string,\n offer: Partial<OneTimeOffer>,\n updateMask?: string,\n regionsVersion?: string,\n ): Promise<OneTimeOffer>;\n deleteOffer(packageName: string, productId: string, offerId: string): Promise<void>;\n };\n\n purchaseOptions: {\n list(packageName: string): Promise<PurchaseOptionsListResponse>;\n get(packageName: string, purchaseOptionId: string): Promise<PurchaseOption>;\n create(packageName: string, data: PurchaseOption): Promise<PurchaseOption>;\n activate(packageName: string, purchaseOptionId: string): Promise<PurchaseOption>;\n deactivate(packageName: string, purchaseOptionId: string): Promise<PurchaseOption>;\n };\n\n internalAppSharing: {\n uploadBundle(packageName: string, bundlePath: string): Promise<InternalAppSharingArtifact>;\n uploadApk(packageName: string, apkPath: string): Promise<InternalAppSharingArtifact>;\n };\n\n generatedApks: {\n list(packageName: string, versionCode: number): Promise<GeneratedApk[]>;\n download(packageName: string, versionCode: number, id: string): Promise<ArrayBuffer>;\n };\n}\n\nasync function rateLimit(limiter: RateLimiter | undefined, bucket: string): Promise<void> {\n if (limiter) await limiter.acquire(bucket);\n}\n\nexport function createApiClient(options: ApiClientOptions): PlayApiClient {\n const http = createHttpClient(options);\n const limiter = options.rateLimiter || undefined;\n\n return {\n edits: {\n async insert(packageName) {\n const { data } = await http.post<AppEdit>(`/${packageName}/edits`);\n return data;\n },\n\n async get(packageName, editId) {\n const { data } = await http.get<AppEdit>(`/${packageName}/edits/${editId}`);\n return data;\n },\n\n async validate(packageName, editId) {\n const { data } = await http.post<AppEdit>(`/${packageName}/edits/${editId}:validate`);\n return data;\n },\n\n async commit(packageName, editId) {\n const { data } = await http.post<AppEdit>(`/${packageName}/edits/${editId}:commit`);\n return data;\n },\n\n async delete(packageName, editId) {\n await http.delete(`/${packageName}/edits/${editId}`);\n },\n },\n\n details: {\n async get(packageName, editId) {\n const { data } = await http.get<AppDetails>(`/${packageName}/edits/${editId}/details`);\n return data;\n },\n\n async update(packageName, editId, details) {\n const { data } = await http.put<AppDetails>(\n `/${packageName}/edits/${editId}/details`,\n details,\n );\n return data;\n },\n\n async patch(packageName, editId, partial) {\n const { data } = await http.patch<AppDetails>(\n `/${packageName}/edits/${editId}/details`,\n partial,\n );\n return data;\n },\n },\n\n bundles: {\n async list(packageName, editId) {\n const { data } = await http.get<BundleListResponse>(\n `/${packageName}/edits/${editId}/bundles`,\n );\n return data.bundles;\n },\n\n async upload(packageName, editId, filePath, uploadOptions) {\n const { data } = await http.uploadResumable<Bundle>(\n `/${packageName}/edits/${editId}/bundles`,\n filePath,\n \"application/octet-stream\",\n uploadOptions,\n );\n if (!data || !data.versionCode) {\n throw new PlayApiError(\n \"Upload succeeded but no bundle data returned\",\n \"API_EMPTY_RESPONSE\",\n 200,\n \"This is unexpected. Retry the upload or contact Google Play support if the issue persists.\",\n );\n }\n return data;\n },\n },\n\n tracks: {\n async list(packageName, editId) {\n const { data } = await http.get<TrackListResponse>(\n `/${packageName}/edits/${editId}/tracks`,\n );\n return data.tracks;\n },\n\n async get(packageName, editId, track) {\n const { data } = await http.get<Track>(`/${packageName}/edits/${editId}/tracks/${track}`);\n return data;\n },\n\n async create(packageName, editId, trackName) {\n const { data } = await http.post<Track>(`/${packageName}/edits/${editId}/tracks`, {\n track: trackName,\n });\n return data;\n },\n\n async update(packageName, editId, track, release) {\n const { data } = await http.put<Track>(`/${packageName}/edits/${editId}/tracks/${track}`, {\n track,\n releases: [release],\n });\n return data;\n },\n },\n\n apks: {\n async addExternallyHosted(packageName, editId, apkData) {\n const { data } = await http.post<ExternallyHostedApkResponse>(\n `/${packageName}/edits/${editId}/apks/externallyHosted`,\n { externallyHostedApk: apkData },\n );\n return data;\n },\n },\n\n listings: {\n async list(packageName, editId) {\n const { data } = await http.get<ListingsListResponse>(\n `/${packageName}/edits/${editId}/listings`,\n );\n return data.listings || [];\n },\n\n async get(packageName, editId, language) {\n const { data } = await http.get<Listing>(\n `/${packageName}/edits/${editId}/listings/${language}`,\n );\n return data;\n },\n\n async update(packageName, editId, language, listing) {\n const { data } = await http.put<Listing>(\n `/${packageName}/edits/${editId}/listings/${language}`,\n listing,\n );\n return data;\n },\n\n async patch(packageName, editId, language, partial) {\n const { data } = await http.patch<Listing>(\n `/${packageName}/edits/${editId}/listings/${language}`,\n partial,\n );\n return data;\n },\n\n async delete(packageName, editId, language) {\n await http.delete(`/${packageName}/edits/${editId}/listings/${language}`);\n },\n\n async deleteAll(packageName, editId) {\n await http.delete(`/${packageName}/edits/${editId}/listings`);\n },\n },\n\n images: {\n async list(packageName, editId, language, imageType) {\n const { data } = await http.get<ImagesListResponse>(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}`,\n );\n return data.images || [];\n },\n\n async upload(packageName, editId, language, imageType, filePath) {\n const { data } = await http.upload<ImageUploadResponse>(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}`,\n filePath,\n filePath.endsWith(\".png\") ? \"image/png\" : \"image/jpeg\",\n );\n if (!data.image) {\n throw new PlayApiError(\n \"Upload succeeded but no image data returned\",\n \"API_EMPTY_RESPONSE\",\n 200,\n \"This is unexpected. Retry the upload or contact Google Play support if the issue persists.\",\n );\n }\n return data.image;\n },\n\n async delete(packageName, editId, language, imageType, imageId) {\n await http.delete(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}/${imageId}`,\n );\n },\n\n async deleteAll(packageName, editId, language, imageType) {\n const { data } = await http.delete<ImagesDeleteAllResponse>(\n `/${packageName}/edits/${editId}/listings/${language}/${imageType}`,\n );\n return data.deleted || [];\n },\n },\n\n countryAvailability: {\n async get(packageName, editId, track) {\n const { data } = await http.get<CountryAvailability>(\n `/${packageName}/edits/${editId}/countryAvailability/${track}`,\n );\n return data;\n },\n },\n\n dataSafety: {\n async get(packageName) {\n const { data } = await http.get<DataSafety>(`/${packageName}/dataSafety`);\n return data;\n },\n\n async update(packageName, body) {\n const { data } = await http.put<DataSafety>(`/${packageName}/dataSafety`, body);\n return data;\n },\n },\n\n reviews: {\n async list(packageName, options?) {\n await rateLimit(limiter, \"reviewsGet\");\n const params: Record<string, string> = {};\n if (options?.token) params[\"token\"] = options.token;\n if (options?.maxResults) params[\"maxResults\"] = String(options.maxResults);\n if (options?.translationLanguage)\n params[\"translationLanguage\"] = options.translationLanguage;\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<ReviewsListResponse>(\n `/${packageName}/reviews`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(packageName, reviewId, translationLanguage?) {\n await rateLimit(limiter, \"reviewsGet\");\n const params: Record<string, string> = {};\n if (translationLanguage) params[\"translationLanguage\"] = translationLanguage;\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<Review>(\n `/${packageName}/reviews/${reviewId}`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async reply(packageName, reviewId, replyText) {\n await rateLimit(limiter, \"reviewsPost\");\n const body: ReviewReplyRequest = { replyText };\n const { data } = await http.post<ReviewReplyResponse>(\n `/${packageName}/reviews/${reviewId}:reply`,\n body,\n );\n return data;\n },\n },\n\n subscriptions: {\n async list(packageName, options?) {\n const params: Record<string, string> = {};\n if (options?.pageToken) params[\"pageToken\"] = options.pageToken;\n if (options?.pageSize) params[\"pageSize\"] = String(options.pageSize);\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<SubscriptionsListResponse>(\n `/${packageName}/subscriptions`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(packageName, productId) {\n const { data } = await http.get<Subscription>(`/${packageName}/subscriptions/${productId}`);\n return data;\n },\n\n async create(packageName, body, productId?) {\n const params: Record<string, string> = {};\n if (productId) params[\"productId\"] = productId;\n params[\"regionsVersion.version\"] = \"2022/02\";\n const path = `/${packageName}/subscriptions?${new URLSearchParams(params).toString()}`;\n const { data } = await http.post<Subscription>(path, body);\n return data;\n },\n\n async update(packageName, productId, body, updateMask?, regionsVersion?) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/subscriptions/${productId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<Subscription>(path, body);\n return data;\n },\n\n async delete(packageName, productId) {\n await http.delete(`/${packageName}/subscriptions/${productId}`);\n },\n\n async activateBasePlan(packageName, productId, basePlanId) {\n const { data } = await http.post<Subscription>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}:activate`,\n );\n return data;\n },\n\n async deactivateBasePlan(packageName, productId, basePlanId) {\n const { data } = await http.post<Subscription>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}:deactivate`,\n );\n return data;\n },\n\n async deleteBasePlan(packageName, productId, basePlanId) {\n await http.delete(`/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}`);\n },\n\n async migratePrices(packageName, productId, basePlanId, body) {\n const { data } = await http.post<Subscription>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}:migratePrices`,\n body,\n );\n return data;\n },\n\n async listOffers(packageName, productId, basePlanId) {\n const { data } = await http.get<OffersListResponse>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers`,\n );\n return data;\n },\n\n async getOffer(packageName, productId, basePlanId, offerId) {\n const { data } = await http.get<SubscriptionOffer>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}`,\n );\n return data;\n },\n\n async createOffer(packageName, productId, basePlanId, body, offerId?) {\n const params: Record<string, string> = {};\n if (offerId) params[\"offerId\"] = offerId;\n params[\"regionsVersion.version\"] = \"2022/02\";\n const path = `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers?${new URLSearchParams(params).toString()}`;\n const { data } = await http.post<SubscriptionOffer>(path, body);\n return data;\n },\n\n async updateOffer(\n packageName,\n productId,\n basePlanId,\n offerId,\n body,\n updateMask?,\n regionsVersion?,\n ) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<SubscriptionOffer>(path, body);\n return data;\n },\n\n async deleteOffer(packageName, productId, basePlanId, offerId) {\n await http.delete(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}`,\n );\n },\n\n async activateOffer(packageName, productId, basePlanId, offerId) {\n const { data } = await http.post<SubscriptionOffer>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}:activate`,\n );\n return data;\n },\n\n async deactivateOffer(packageName, productId, basePlanId, offerId) {\n const { data } = await http.post<SubscriptionOffer>(\n `/${packageName}/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}:deactivate`,\n );\n return data;\n },\n },\n\n inappproducts: {\n async list(packageName, options?) {\n // Note: maxResults and startIndex are deprecated and ignored by Google for inappproducts.list.\n // Server determines page size. Only token (pageToken) is supported for pagination.\n const params: Record<string, string> = {};\n if (options?.token) params[\"token\"] = options.token;\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<InAppProductsListResponse>(\n `/${packageName}/inappproducts`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(packageName, sku) {\n const { data } = await http.get<InAppProduct>(`/${packageName}/inappproducts/${sku}`);\n return data;\n },\n\n async create(packageName, body, options?) {\n const params: Record<string, string> = {};\n if (options?.autoConvertMissingPrices) params[\"autoConvertMissingPrices\"] = \"true\";\n const hasParams = Object.keys(params).length > 0;\n const path = hasParams\n ? `/${packageName}/inappproducts?${new URLSearchParams(params).toString()}`\n : `/${packageName}/inappproducts`;\n const { data } = await http.post<InAppProduct>(path, body);\n return data;\n },\n\n async update(packageName, sku, body, options?) {\n const params: Record<string, string> = {};\n if (options?.autoConvertMissingPrices) params[\"autoConvertMissingPrices\"] = \"true\";\n if (options?.allowMissing) params[\"allowMissing\"] = \"true\";\n const hasParams = Object.keys(params).length > 0;\n const path = hasParams\n ? `/${packageName}/inappproducts/${sku}?${new URLSearchParams(params).toString()}`\n : `/${packageName}/inappproducts/${sku}`;\n const { data } = await http.put<InAppProduct>(path, body);\n return data;\n },\n\n async delete(packageName, sku) {\n await http.delete(`/${packageName}/inappproducts/${sku}`);\n },\n\n async batchUpdate(packageName, requests) {\n const { data } = await http.post<InAppProductsBatchUpdateResponse>(\n `/${packageName}/inappproducts:batchUpdate`,\n requests,\n );\n return data;\n },\n\n async batchGet(packageName, skus) {\n const params: Record<string, string> = {};\n if (skus.length > 0) {\n params[\"sku\"] = skus.join(\",\");\n }\n const { data } = await http.get<{ inappproduct: InAppProduct[] }>(\n `/${packageName}/inappproducts:batchGet`,\n Object.keys(params).length > 0 ? params : undefined,\n );\n return data.inappproduct || [];\n },\n },\n\n purchases: {\n async getProduct(packageName, productId, token) {\n const { data } = await http.get<ProductPurchase>(\n `/${packageName}/purchases/products/${productId}/tokens/${token}`,\n );\n return data;\n },\n\n async acknowledgeProduct(packageName, productId, token, body?) {\n await http.post(\n `/${packageName}/purchases/products/${productId}/tokens/${token}:acknowledge`,\n body,\n );\n },\n\n async consumeProduct(packageName, productId, token) {\n await http.post(`/${packageName}/purchases/products/${productId}/tokens/${token}:consume`);\n },\n\n async getSubscriptionV2(packageName, token) {\n const { data } = await http.get<SubscriptionPurchaseV2>(\n `/${packageName}/purchases/subscriptionsv2/tokens/${token}`,\n );\n return data;\n },\n\n async getSubscriptionV1(packageName, subscriptionId, token) {\n if (typeof process !== \"undefined\" && process.emitWarning) {\n process.emitWarning(\n \"purchases.subscriptions.get (v1) is deprecated by Google (shutdown Aug 2027). Use getSubscriptionV2() instead.\",\n \"DeprecationWarning\",\n );\n }\n const { data } = await http.get<SubscriptionPurchase>(\n `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}`,\n );\n return data;\n },\n\n async cancelSubscription(packageName, subscriptionId, token) {\n await http.post(\n `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}:cancel`,\n );\n },\n\n async deferSubscription(packageName, subscriptionId, token, body) {\n const { data } = await http.post<SubscriptionDeferResponse>(\n `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}:defer`,\n body,\n );\n return data;\n },\n\n async revokeSubscriptionV2(packageName, token) {\n await http.post(`/${packageName}/purchases/subscriptionsv2/tokens/${token}:revoke`);\n },\n\n async refundSubscriptionV2(packageName, token) {\n await http.post(`/${packageName}/purchases/subscriptionsv2/tokens/${token}:refund`);\n },\n\n async listVoided(packageName, options?) {\n await rateLimit(limiter, \"voidedBurst\");\n await rateLimit(limiter, \"voidedDaily\");\n const params: Record<string, string> = {};\n if (options?.startTime) params[\"startTime\"] = options.startTime;\n if (options?.endTime) params[\"endTime\"] = options.endTime;\n if (options?.maxResults) params[\"maxResults\"] = String(options.maxResults);\n if (options?.token) params[\"token\"] = options.token;\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<VoidedPurchasesListResponse>(\n `/${packageName}/purchases/voidedpurchases`,\n hasParams ? params : undefined,\n );\n return data;\n },\n },\n\n orders: {\n async refund(packageName, orderId, body?) {\n await http.post(`/${packageName}/orders/${orderId}:refund`, body);\n },\n },\n\n monetization: {\n async convertRegionPrices(packageName, price) {\n const { data } = await http.post<ConvertRegionPricesResponse>(\n `/${packageName}/pricing:convertRegionPrices`,\n price,\n );\n return data;\n },\n },\n\n reports: {\n async list(packageName, reportType, year, month) {\n const monthStr = String(month).padStart(2, \"0\");\n const { data } = await http.get<ReportsListResponse>(\n `/${packageName}/reports/${reportType}/${year}/${monthStr}`,\n );\n return data;\n },\n },\n\n testers: {\n async get(packageName, editId, track) {\n const { data } = await http.get<Testers>(\n `/${packageName}/edits/${editId}/testers/${track}`,\n );\n return data;\n },\n\n async update(packageName, editId, track, testersData) {\n const { data } = await http.put<Testers>(\n `/${packageName}/edits/${editId}/testers/${track}`,\n testersData,\n );\n return data;\n },\n },\n\n deobfuscation: {\n async upload(packageName, editId, versionCode, filePath) {\n const { data } = await http.upload<DeobfuscationUploadResponse>(\n `/${packageName}/edits/${editId}/apks/${versionCode}/deobfuscationFiles/proguard`,\n filePath,\n \"application/octet-stream\",\n );\n if (!data.deobfuscationFile) {\n throw new PlayApiError(\n \"Upload succeeded but no deobfuscation file data returned\",\n \"API_EMPTY_RESPONSE\",\n 200,\n \"This is unexpected. Retry the upload or contact Google Play support if the issue persists.\",\n );\n }\n return data.deobfuscationFile;\n },\n },\n\n appRecovery: {\n async list(packageName, versionCode?) {\n const params: Record<string, string> = {};\n if (versionCode !== undefined) params[\"versionCode\"] = String(versionCode);\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<AppRecoveriesListResponse>(\n `/${packageName}/appRecoveries`,\n hasParams ? params : undefined,\n );\n return data.recoveryActions || [];\n },\n\n async cancel(packageName, appRecoveryId) {\n await http.post(`/${packageName}/appRecovery/${appRecoveryId}:cancel`);\n },\n\n async deploy(packageName, appRecoveryId) {\n await http.post(`/${packageName}/appRecovery/${appRecoveryId}:deploy`);\n },\n\n async create(packageName, request) {\n const { data } = await http.post<AppRecoveryAction>(\n `/${packageName}/appRecoveries`,\n request,\n );\n return data;\n },\n\n async addTargeting(packageName, appRecoveryId, targeting) {\n const { data } = await http.post<AppRecoveryAction>(\n `/${packageName}/appRecoveries/${appRecoveryId}:addTargeting`,\n targeting,\n );\n return data;\n },\n },\n\n externalTransactions: {\n async create(packageName, body) {\n const { data } = await http.post<ExternalTransaction>(\n `/${packageName}/externalTransactions`,\n body,\n );\n return data;\n },\n\n async get(packageName, transactionId) {\n const { data } = await http.get<ExternalTransaction>(\n `/${packageName}/externalTransactions/${transactionId}`,\n );\n return data;\n },\n\n async refund(packageName, transactionId, refundData) {\n const { data } = await http.post<ExternalTransaction>(\n `/${packageName}/externalTransactions/${transactionId}:refund`,\n refundData,\n );\n return data;\n },\n },\n\n deviceTiers: {\n async list(packageName) {\n const { data } = await http.get<DeviceTierConfigsListResponse>(\n `/${packageName}/deviceTierConfigs`,\n );\n return data.deviceTierConfigs || [];\n },\n\n async get(packageName, configId) {\n const { data } = await http.get<DeviceTierConfig>(\n `/${packageName}/deviceTierConfigs/${configId}`,\n );\n return data;\n },\n\n async create(packageName, config) {\n const { data } = await http.post<DeviceTierConfig>(\n `/${packageName}/deviceTierConfigs`,\n config,\n );\n return data;\n },\n },\n\n oneTimeProducts: {\n async list(packageName) {\n const { data } = await http.get<OneTimeProductsListResponse>(\n `/${packageName}/oneTimeProducts`,\n );\n return data;\n },\n\n async get(packageName, productId) {\n const { data } = await http.get<OneTimeProduct>(\n `/${packageName}/oneTimeProducts/${productId}`,\n );\n return data;\n },\n\n async create(packageName, body) {\n const params = new URLSearchParams({ \"regionsVersion.version\": \"2022/02\" });\n const { data } = await http.post<OneTimeProduct>(\n `/${packageName}/oneTimeProducts?${params.toString()}`,\n body,\n );\n return data;\n },\n\n async update(packageName, productId, body, updateMask?, regionsVersion?) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/oneTimeProducts/${productId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<OneTimeProduct>(path, body);\n return data;\n },\n\n async delete(packageName, productId) {\n await http.delete(`/${packageName}/oneTimeProducts/${productId}`);\n },\n\n async listOffers(packageName, productId) {\n const { data } = await http.get<OneTimeOffersListResponse>(\n `/${packageName}/oneTimeProducts/${productId}/offers`,\n );\n return data;\n },\n\n async getOffer(packageName, productId, offerId) {\n const { data } = await http.get<OneTimeOffer>(\n `/${packageName}/oneTimeProducts/${productId}/offers/${offerId}`,\n );\n return data;\n },\n\n async createOffer(packageName, productId, body) {\n const { data } = await http.post<OneTimeOffer>(\n `/${packageName}/oneTimeProducts/${productId}/offers`,\n body,\n );\n return data;\n },\n\n async updateOffer(packageName, productId, offerId, body, updateMask?, regionsVersion?) {\n const params: Record<string, string> = {};\n if (updateMask) params[\"updateMask\"] = updateMask;\n params[\"regionsVersion.version\"] = regionsVersion || \"2022/02\";\n const path = `/${packageName}/oneTimeProducts/${productId}/offers/${offerId}?${new URLSearchParams(params).toString()}`;\n const { data } = await http.patch<OneTimeOffer>(path, body);\n return data;\n },\n\n async deleteOffer(packageName, productId, offerId) {\n await http.delete(`/${packageName}/oneTimeProducts/${productId}/offers/${offerId}`);\n },\n },\n\n purchaseOptions: {\n async list(packageName) {\n const { data } = await http.get<PurchaseOptionsListResponse>(\n `/${packageName}/purchaseOptions`,\n );\n return data;\n },\n\n async get(packageName, purchaseOptionId) {\n const { data } = await http.get<PurchaseOption>(\n `/${packageName}/purchaseOptions/${purchaseOptionId}`,\n );\n return data;\n },\n\n async create(packageName, body) {\n const { data } = await http.post<PurchaseOption>(`/${packageName}/purchaseOptions`, body);\n return data;\n },\n\n async activate(packageName, purchaseOptionId) {\n const { data } = await http.post<PurchaseOption>(\n `/${packageName}/purchaseOptions/${purchaseOptionId}:activate`,\n );\n return data;\n },\n\n async deactivate(packageName, purchaseOptionId) {\n const { data } = await http.post<PurchaseOption>(\n `/${packageName}/purchaseOptions/${purchaseOptionId}:deactivate`,\n );\n return data;\n },\n },\n\n internalAppSharing: {\n async uploadBundle(packageName, bundlePath) {\n const { data } = await http.uploadInternal<InternalAppSharingArtifact>(\n `/${packageName}/artifacts/bundle`,\n bundlePath,\n \"application/octet-stream\",\n );\n return data;\n },\n\n async uploadApk(packageName, apkPath) {\n const { data } = await http.uploadInternal<InternalAppSharingArtifact>(\n `/${packageName}/artifacts/apk`,\n apkPath,\n \"application/vnd.android.package-archive\",\n );\n return data;\n },\n },\n\n generatedApks: {\n async list(packageName, versionCode) {\n const { data } = await http.get<GeneratedApksPerVersion>(\n `/${packageName}/generatedApks/${versionCode}`,\n );\n return data.generatedApks || [];\n },\n\n async download(packageName, versionCode, id) {\n return http.download(`/${packageName}/generatedApks/${versionCode}/download/${id}`);\n },\n },\n };\n}\n","export interface RateLimitBucket {\n name: string;\n maxTokens: number;\n refillRate: number;\n refillIntervalMs: number;\n}\n\nexport interface RateLimiter {\n acquire(bucket: string): Promise<void>;\n}\n\ninterface BucketState {\n tokens: number;\n lastRefillTime: number;\n config: RateLimitBucket;\n}\n\nexport const RATE_LIMIT_BUCKETS: Record<string, RateLimitBucket> = {\n default: { name: \"default\", maxTokens: 200, refillRate: 200, refillIntervalMs: 1_000 },\n reviewsGet: { name: \"reviewsGet\", maxTokens: 200, refillRate: 200, refillIntervalMs: 3_600_000 },\n reviewsPost: {\n name: \"reviewsPost\",\n maxTokens: 2_000,\n refillRate: 2_000,\n refillIntervalMs: 86_400_000,\n },\n voidedBurst: { name: \"voidedBurst\", maxTokens: 30, refillRate: 30, refillIntervalMs: 30_000 },\n voidedDaily: {\n name: \"voidedDaily\",\n maxTokens: 6_000,\n refillRate: 6_000,\n refillIntervalMs: 86_400_000,\n },\n reporting: { name: \"reporting\", maxTokens: 10, refillRate: 10, refillIntervalMs: 1_000 },\n};\n\nexport function createRateLimiter(buckets?: RateLimitBucket[]): RateLimiter {\n const states = new Map<string, BucketState>();\n\n if (buckets) {\n for (const bucket of buckets) {\n states.set(bucket.name, {\n tokens: bucket.maxTokens,\n lastRefillTime: Date.now(),\n config: bucket,\n });\n }\n }\n\n return {\n async acquire(bucket: string): Promise<void> {\n const state = states.get(bucket);\n if (!state) return;\n\n const now = Date.now();\n const elapsed = now - state.lastRefillTime;\n const refill = Math.floor(\n (elapsed / state.config.refillIntervalMs) * state.config.refillRate,\n );\n\n if (refill > 0) {\n state.tokens = Math.min(state.config.maxTokens, state.tokens + refill);\n state.lastRefillTime = now;\n }\n\n if (state.tokens > 0) {\n state.tokens--;\n return;\n }\n\n const tokensNeeded = 1;\n const waitMs = Math.ceil(\n (tokensNeeded / state.config.refillRate) * state.config.refillIntervalMs,\n );\n await new Promise((r) => setTimeout(r, waitMs));\n\n // Recalculate refill based on actual elapsed time since last refill\n const afterWait = Date.now();\n const totalElapsed = afterWait - state.lastRefillTime;\n const newTokens = Math.floor(\n (totalElapsed / state.config.refillIntervalMs) * state.config.refillRate,\n );\n state.tokens = Math.min(state.config.maxTokens, newTokens) - 1;\n state.lastRefillTime = afterWait;\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport { createRateLimiter, RATE_LIMIT_BUCKETS } from \"./rate-limiter.js\";\nimport type {\n AnomalyDetectionResponse,\n ApiClientOptions,\n ErrorIssuesResponse,\n ErrorReportsResponse,\n MetricSetQuery,\n MetricSetResponse,\n VitalsMetricSet,\n} from \"./types.js\";\n\nconst REPORTING_BASE_URL = \"https://playdeveloperreporting.googleapis.com/v1beta1\";\n\nexport interface ReportingApiClient {\n queryMetricSet(\n packageName: string,\n metricSet: VitalsMetricSet,\n query: MetricSetQuery,\n ): Promise<MetricSetResponse>;\n\n getAnomalies(packageName: string): Promise<AnomalyDetectionResponse>;\n\n searchErrorIssues(\n packageName: string,\n filter?: string,\n pageSize?: number,\n pageToken?: string,\n ): Promise<ErrorIssuesResponse>;\n\n searchErrorReports(\n packageName: string,\n issueName: string,\n pageSize?: number,\n pageToken?: string,\n ): Promise<ErrorReportsResponse>;\n}\n\nexport function createReportingClient(options: ApiClientOptions): ReportingApiClient {\n const http = createHttpClient({ ...options, baseUrl: REPORTING_BASE_URL });\n const limiter = options.rateLimiter ?? createRateLimiter([RATE_LIMIT_BUCKETS[\"reporting\"]!]);\n\n return {\n async queryMetricSet(packageName, metricSet, query) {\n await limiter.acquire(\"reporting\");\n const { data } = await http.post<MetricSetResponse>(\n `/apps/${packageName}/${metricSet}:query`,\n query,\n );\n return data;\n },\n\n async getAnomalies(packageName) {\n await limiter.acquire(\"reporting\");\n const { data } = await http.get<AnomalyDetectionResponse>(`/apps/${packageName}/anomalies`);\n return data;\n },\n\n async searchErrorIssues(packageName, filter?, pageSize?, pageToken?) {\n await limiter.acquire(\"reporting\");\n const params: Record<string, string> = {};\n if (filter) params[\"filter\"] = filter;\n if (pageSize) params[\"pageSize\"] = String(pageSize);\n if (pageToken) params[\"pageToken\"] = pageToken;\n const { data } = await http.get<ErrorIssuesResponse>(\n `/apps/${packageName}/errorIssues:search`,\n params,\n );\n return data;\n },\n\n async searchErrorReports(packageName, issueName, pageSize?, pageToken?) {\n await limiter.acquire(\"reporting\");\n const params: Record<string, string> = {};\n if (pageSize) params[\"pageSize\"] = String(pageSize);\n if (pageToken) params[\"pageToken\"] = pageToken;\n const { data } = await http.get<ErrorReportsResponse>(\n `/apps/${packageName}/errorIssues/${issueName}/reports`,\n params,\n );\n return data;\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport type { ApiClientOptions, User, UsersListResponse, Grant } from \"./types.js\";\n\nconst USERS_BASE_URL = \"https://androidpublisher.googleapis.com/androidpublisher/v3/developers\";\n\nexport interface GrantsListResponse {\n grants: Grant[];\n nextPageToken?: string;\n}\n\nexport interface UsersApiClient {\n list(\n developerId: string,\n options?: { pageToken?: string; pageSize?: number },\n ): Promise<UsersListResponse>;\n\n get(developerId: string, userId: string): Promise<User>;\n\n create(developerId: string, user: Partial<User>): Promise<User>;\n\n update(\n developerId: string,\n userId: string,\n user: Partial<User>,\n updateMask?: string,\n ): Promise<User>;\n\n delete(developerId: string, userId: string): Promise<void>;\n\n grants: {\n list(developerId: string, email: string): Promise<GrantsListResponse>;\n create(developerId: string, email: string, grant: Partial<Grant>): Promise<Grant>;\n patch(\n developerId: string,\n email: string,\n packageName: string,\n grant: Partial<Grant>,\n updateMask?: string,\n ): Promise<Grant>;\n delete(developerId: string, email: string, packageName: string): Promise<void>;\n };\n}\n\nexport function createUsersClient(options: ApiClientOptions): UsersApiClient {\n const http = createHttpClient({ ...options, baseUrl: USERS_BASE_URL });\n\n return {\n async list(developerId, listOptions?) {\n const params: Record<string, string> = {};\n if (listOptions?.pageToken) params[\"pageToken\"] = listOptions.pageToken;\n if (listOptions?.pageSize) params[\"pageSize\"] = String(listOptions.pageSize);\n const hasParams = Object.keys(params).length > 0;\n const { data } = await http.get<UsersListResponse>(\n `/${developerId}/users`,\n hasParams ? params : undefined,\n );\n return data;\n },\n\n async get(developerId, userId) {\n const { data } = await http.get<User>(`/${developerId}/users/${userId}`);\n return data;\n },\n\n async create(developerId, user) {\n const { data } = await http.post<User>(`/${developerId}/users`, user);\n return data;\n },\n\n async update(developerId, userId, user, updateMask?) {\n let path = `/${developerId}/users/${userId}`;\n if (updateMask) {\n path += `?updateMask=${encodeURIComponent(updateMask).replace(/%2C/gi, \",\")}`;\n }\n const { data } = await http.patch<User>(path, user);\n return data;\n },\n\n async delete(developerId, userId) {\n await http.delete(`/${developerId}/users/${userId}`);\n },\n\n grants: {\n async list(developerId, email) {\n const { data } = await http.get<GrantsListResponse>(\n `/${developerId}/users/${encodeURIComponent(email)}/grants`,\n );\n return data;\n },\n\n async create(developerId, email, grant) {\n const { data } = await http.post<Grant>(\n `/${developerId}/users/${encodeURIComponent(email)}/grants`,\n grant,\n );\n return data;\n },\n\n async patch(developerId, email, packageName, grant, updateMask?) {\n let path = `/${developerId}/users/${encodeURIComponent(email)}/grants/${encodeURIComponent(packageName)}`;\n if (updateMask) {\n path += `?updateMask=${encodeURIComponent(updateMask)}`;\n }\n const { data } = await http.patch<Grant>(path, grant);\n return data;\n },\n\n async delete(developerId, email, packageName) {\n await http.delete(\n `/${developerId}/users/${encodeURIComponent(email)}/grants/${encodeURIComponent(packageName)}`,\n );\n },\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport type { ApiClientOptions } from \"./types.js\";\n\nconst GAMES_BASE_URL = \"https://games.googleapis.com/games/v1\";\n\nexport interface Leaderboard {\n id: string;\n name: string;\n order: string;\n iconUrl?: string;\n}\n\nexport interface LeaderboardScore {\n leaderboardId: string;\n scoreValue: string;\n formattedScore: string;\n writeTimestamp?: string;\n tag?: string;\n}\n\nexport interface Achievement {\n id: string;\n name: string;\n description: string;\n state: \"REVEALED\" | \"HIDDEN\" | \"UNLOCKED\";\n currentSteps?: number;\n totalSteps?: number;\n experiencePoints?: number;\n formattedCurrentStepsString?: string;\n}\n\nexport interface GameEvent {\n definitionId: string;\n numEvents: string;\n formattedNumEvents: string;\n kind?: string;\n}\n\nexport interface GamesApiClient {\n leaderboards: {\n list(packageName: string): Promise<{ items?: Leaderboard[]; nextPageToken?: string }>;\n get(packageName: string, leaderboardId: string): Promise<Leaderboard>;\n getScores(\n packageName: string,\n leaderboardId: string,\n collection: string,\n timeSpan: string,\n ): Promise<{ items?: LeaderboardScore[] }>;\n };\n achievements: {\n list(packageName: string): Promise<{ items?: Achievement[]; nextPageToken?: string }>;\n reveal(packageName: string, achievementId: string): Promise<{ currentState: string }>;\n };\n events: {\n list(packageName: string): Promise<{ items?: GameEvent[]; nextPageToken?: string }>;\n };\n}\n\nexport function createGamesClient(options: ApiClientOptions): GamesApiClient {\n const http = createHttpClient({ ...options, baseUrl: GAMES_BASE_URL });\n\n return {\n leaderboards: {\n async list(packageName) {\n const { data } = await http.get<{ items?: Leaderboard[]; nextPageToken?: string }>(\n `/leaderboards?applicationId=${packageName}`,\n );\n return data;\n },\n async get(packageName, leaderboardId) {\n const { data } = await http.get<Leaderboard>(\n `/leaderboards/${leaderboardId}?applicationId=${packageName}`,\n );\n return data;\n },\n async getScores(packageName, leaderboardId, collection, timeSpan) {\n const { data } = await http.get<{ items?: LeaderboardScore[] }>(\n `/leaderboards/${leaderboardId}/scores/${collection}?timeSpan=${timeSpan}&applicationId=${packageName}`,\n );\n return data;\n },\n },\n achievements: {\n async list(packageName) {\n const { data } = await http.get<{ items?: Achievement[]; nextPageToken?: string }>(\n `/achievements?applicationId=${packageName}`,\n );\n return data;\n },\n async reveal(packageName, achievementId) {\n const { data } = await http.post<{ currentState: string }>(\n `/achievements/${achievementId}/reveal?applicationId=${packageName}`,\n {},\n );\n return data;\n },\n },\n events: {\n async list(packageName) {\n const { data } = await http.get<{ items?: GameEvent[]; nextPageToken?: string }>(\n `/events?applicationId=${packageName}`,\n );\n return data;\n },\n },\n };\n}\n","import { createHttpClient } from \"./http.js\";\nimport type { ApiClientOptions } from \"./types.js\";\n\nconst ENTERPRISE_BASE_URL = \"https://playcustomapp.googleapis.com/playcustomapp/v1/organizations\";\n\nexport interface CustomApp {\n packageName?: string;\n title: string;\n languageCode?: string;\n organizations?: Array<{ organizationId: string; organizationName?: string }>;\n}\n\nexport interface CustomAppsListResponse {\n customApps?: CustomApp[];\n nextPageToken?: string;\n}\n\nexport interface EnterpriseApiClient {\n apps: {\n create(organizationId: string, app: Partial<CustomApp>): Promise<CustomApp>;\n list(organizationId: string): Promise<CustomAppsListResponse>;\n };\n}\n\nexport function createEnterpriseClient(options: ApiClientOptions): EnterpriseApiClient {\n const http = createHttpClient({ ...options, baseUrl: ENTERPRISE_BASE_URL });\n\n return {\n apps: {\n async create(organizationId, app) {\n const { data } = await http.post<CustomApp>(`/${organizationId}/apps`, app);\n return data;\n },\n async list(organizationId) {\n const { data } = await http.get<CustomAppsListResponse>(`/${organizationId}/apps`);\n return data;\n },\n },\n };\n}\n","export interface PaginateOptions {\n limit?: number;\n startPageToken?: string;\n}\n\nexport async function* paginate<TItem>(\n fetchPage: (pageToken?: string) => Promise<{ items: TItem[]; nextPageToken?: string }>,\n options?: PaginateOptions,\n): AsyncGenerator<TItem[], void, unknown> {\n let pageToken = options?.startPageToken;\n let collected = 0;\n const limit = options?.limit;\n\n for (;;) {\n if (limit !== undefined && collected >= limit) break;\n\n const page = await fetchPage(pageToken);\n const items = page.items;\n\n if (items.length === 0) break;\n\n if (limit !== undefined) {\n const remaining = limit - collected;\n if (items.length > remaining) {\n yield items.slice(0, remaining);\n return;\n }\n }\n\n yield items;\n collected += items.length;\n pageToken = page.nextPageToken;\n\n if (!pageToken) break;\n }\n}\n\nexport async function paginateAll<TItem>(\n fetchPage: (pageToken?: string) => Promise<{ items: TItem[]; nextPageToken?: string }>,\n options?: PaginateOptions,\n): Promise<{ items: TItem[]; nextPageToken?: string }> {\n const allItems: TItem[] = [];\n let lastPageToken: string | undefined;\n const limit = options?.limit;\n\n for await (const items of paginate(fetchPage, options)) {\n allItems.push(...items);\n if (limit !== undefined && allItems.length >= limit) break;\n }\n\n // If we stopped due to limit, try to get the next page token for resumption\n if (limit !== undefined && allItems.length >= limit) {\n lastPageToken = undefined; // Already truncated by paginate\n }\n\n return { items: allItems, nextPageToken: lastPageToken };\n}\n\n/**\n * Fetch multiple known pages in parallel.\n * Useful when page tokens are predictable or when pre-fetching subsequent pages\n * after an initial sequential fetch reveals the token pattern.\n *\n * @param fetchPage - Function that fetches a page given a token\n * @param pageTokens - Array of page tokens to fetch concurrently\n * @param concurrency - Max concurrent requests (default: 4)\n */\nexport async function paginateParallel<TItem>(\n fetchPage: (pageToken?: string) => Promise<{ items: TItem[]; nextPageToken?: string }>,\n pageTokens: string[],\n concurrency = 4,\n): Promise<{ items: TItem[]; nextPageToken?: string }> {\n const allItems: TItem[] = [];\n let lastNextPageToken: string | undefined;\n\n // Process in batches of `concurrency`\n for (let i = 0; i < pageTokens.length; i += concurrency) {\n const batch = pageTokens.slice(i, i + concurrency);\n const results = await Promise.all(batch.map((token) => fetchPage(token)));\n\n for (const result of results) {\n allItems.push(...result.items);\n if (result.nextPageToken) {\n lastNextPageToken = result.nextPageToken;\n }\n }\n }\n\n return { items: allItems, nextPageToken: lastNextPageToken };\n}\n"],"mappings":";AAAO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAEtC,YACE,SACgB,MACA,YACA,YAChB;AACA,UAAM,OAAO;AAJG;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EATgB,WAAW;AAAA,EAU3B,SAAS;AACP,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,YAAY,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;;;ACrBA,SAAS,UAAU,QAAAA,aAAY;AAC/B,SAAS,SAAS,kBAAkB;;;ACDpC,SAAS,MAAM,YAAY;AAM3B,IAAM,kBAAkB,MAAM;AAG9B,IAAM,qBAAqB,IAAI,OAAO;AAG/B,IAAM,sBAAsB,IAAI,OAAO;AAE9C,SAAS,OAAO,MAAkC;AAChD,QAAM,MAAM,QAAQ,IAAI,IAAI;AAC5B,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,IAAI,OAAO,GAAG;AACpB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAS,iBAAiB,UAA2B;AACnD,QAAM,OAAO,YAAY,OAAO,uBAAuB,KAAK;AAC5D,MAAI,OAAO,mBAAmB,OAAO,oBAAoB,GAAG;AAC1D,UAAM,IAAI;AAAA,MACR,gDAAgD,IAAI;AAAA,MACpD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,MAAc,SAAiB,KAAqB;AACzE,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,SAAS,KAAK,IAAI,aAAa,GAAG;AACxC,SAAO,UAAU,MAAM,KAAK,OAAO,IAAI;AACzC;AAiBA,eAAsB,gBACpB,WACA,UACA,aACA,KACA,SACyB;AACzB,QAAM,YAAY,iBAAiB,SAAS,SAAS;AACrD,QAAM,oBAAoB,SAAS,qBAAqB;AACxD,QAAM,aAAa,SAAS;AAE5B,QAAM,YAAY,MAAM,KAAK,QAAQ;AACrC,QAAM,aAAa,UAAU;AAG7B,MAAI,aAAa,SAAS;AAC1B,MAAI,CAAC,YAAY;AACf,iBAAa,MAAM,gBAAgB,WAAW,aAAa,YAAY,GAAG;AAAA,EAC5E;AAGA,QAAM,YAAY,KAAK,IAAI;AAC3B,MAAI,SAAS;AAGb,MAAI,SAAS,kBAAkB;AAC7B,aAAS,MAAM,cAAc,YAAY,YAAY,GAAG;AAAA,EAC1D;AAEA,MAAI;AACJ,MAAI;AACF,SAAK,MAAM,KAAK,UAAU,GAAG;AAC7B,UAAM,cAAc,OAAO,MAAM,SAAS;AAE1C,WAAO,SAAS,YAAY;AAC1B,YAAM,YAAY,aAAa;AAC/B,YAAM,cAAc,KAAK,IAAI,WAAW,SAAS;AACjD,YAAM,EAAE,UAAU,IAAI,MAAM,GAAG,KAAK,aAAa,GAAG,aAAa,MAAM;AAEvE,UAAI,cAAc,EAAG;AAIrB,YAAM,QAAQ,OAAO,KAAK,YAAY,QAAQ,YAAY,YAAY,SAAS;AAC/E,YAAM,WAAW,SAAS,YAAY;AACtC,YAAM,eAAe,SAAS,MAAM,IAAI,QAAQ,IAAI,UAAU;AAE9D,UAAI;AACJ,eAAS,UAAU,GAAG,WAAW,mBAAmB,WAAW;AAC7D,YAAI,UAAU,GAAG;AACf,gBAAM,QAAQ,cAAc,KAAM,UAAU,GAAG,GAAM;AACrD,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC;AAG7C,cAAI;AACF,kBAAM,eAAe,MAAM,cAAc,YAAY,YAAY,GAAG;AACpE,gBAAI,gBAAgB,SAAS,WAAW;AAEtC,uBAAS,EAAE,UAAU,MAAM;AAC3B;AAAA,YACF;AACA,gBAAI,eAAe,QAAQ;AAAA,YAG3B;AAAA,UACF,QAAQ;AAAA,UAER;AAEA,cAAI,UAAU;AAAA,YACZ;AAAA,YACA,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,OAAO,iCAAiC,MAAM;AAAA,YAC9C,SAAS,KAAK,MAAM,KAAK;AAAA,YACzB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AAAA,QACH;AAEA,iBAAS,MAAM,UAAa,YAAY,OAAO,cAAc,GAAG;AAChE,YAAI,OAAQ;AAAA,MACd;AAEA,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR,kCAAkC,MAAM,4BAA4B,oBAAoB,CAAC;AAAA,UACzF;AAAA,UACA;AAAA,UACA,kFAAkF,UAAU;AAAA,QAC9F;AAAA,MACF;AAEA,gBAAU;AAGV,UAAI,YAAY;AACd,cAAM,WAAW,KAAK,IAAI,IAAI,aAAa;AAC3C,cAAM,iBAAiB,UAAU,IAAI,SAAS,UAAU;AACxD,cAAM,iBAAiB,aAAa;AACpC,cAAM,aAAa,iBAAiB,IAAI,iBAAiB,iBAAiB;AAE1E,mBAAW;AAAA,UACT,eAAe;AAAA,UACf;AAAA,UACA,SAAS,KAAK,MAAO,SAAS,aAAc,GAAG;AAAA,UAC/C,gBAAgB,KAAK,MAAM,cAAc;AAAA,UACzC,YAAY,KAAK,MAAM,UAAU;AAAA,QACnC,CAAC;AAAA,MACH;AAGA,UAAI,OAAO,YAAY,OAAO,UAAU;AACtC,eAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,IAAI,MAAM;AAAA,EAClB;AACF;AAOA,eAAe,gBACb,WACA,aACA,YACA,KACiB;AACjB,QAAM,QAAQ,MAAM,IAAI,eAAe;AACvC,QAAM,MAAM,UAAU,SAAS,GAAG,IAC9B,GAAG,SAAS,0BACZ,GAAG,SAAS;AAEhB,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,GAAM;AACzD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK;AAAA,MAC1B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,yBAAyB;AAAA,QACzB,2BAA2B,OAAO,UAAU;AAAA,QAC5C,kBAAkB;AAAA,MACpB;AAAA,MACA,QAAQ,WAAW;AAAA,IACrB,CAAC;AAAA,EACH,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,IAAI;AAAA,MACR,wCAAwC,SAAS,MAAM,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MAC7E;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,SAAS,QAAQ,IAAI,UAAU;AAChD,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,UACb,YACA,OACA,cACA,KACqC;AACrC,QAAM,QAAQ,MAAM,IAAI,eAAe;AAGvC,QAAM,iBAAiB,MAAS,KAAK,KAAK,MAAM,cAAc,OAAO,KAAK,IAAI;AAC9E,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,cAAc;AACjE,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,YAAY;AAAA,MACjC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,kBAAkB,OAAO,MAAM,UAAU;AAAA,QACzC,iBAAiB;AAAA,MACnB;AAAA,MACA,MAAM;AAAA,MACN,QAAQ,WAAW;AAAA,IACrB,CAAC;AAAA,EACH,QAAQ;AAEN,WAAO;AAAA,EACT,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AAGA,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,OAAO,OAAQ,KAAK,MAAM,IAAI,IAAW,CAAC;AAChD,WAAO,EAAE,UAAU,MAAM,UAAU,EAAE,MAAM,QAAQ,SAAS,OAAO,EAAE;AAAA,EACvE;AAGA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,SAAS,MAAM,OAAO;AAC5B,WAAO,EAAE,UAAU,MAAM;AAAA,EAC3B;AAGA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,SAAS,MAAM,OAAO;AAC5B,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,WAAW,OAAO,SAAS,UAAU,KAAK;AACrD,UAAM,SAAS,MAAM,OAAO;AAC5B,WAAO;AAAA,EACT;AAGA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAM,IAAI;AAAA,IACR,mCAAmC,SAAS,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IACzE,eAAe,SAAS,MAAM;AAAA,IAC9B,SAAS;AAAA,IACT;AAAA,EACF;AACF;AAEA,eAAe,cACb,YACA,YACA,KACiB;AACjB,QAAM,QAAQ,MAAM,IAAI,eAAe;AAEvC,QAAM,WAAW,MAAM,MAAM,YAAY;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,KAAK;AAAA,MAC9B,kBAAkB;AAAA,MAClB,iBAAiB,WAAW,UAAU;AAAA,IACxC;AAAA,EACF,CAAC;AAGD,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,SAAS,MAAM,OAAO;AAC5B,UAAM,QAAQ,SAAS,QAAQ,IAAI,OAAO;AAC1C,QAAI,OAAO;AAET,YAAM,QAAQ,MAAM,MAAM,eAAe;AACzC,UAAI,OAAO;AACT,eAAO,OAAO,MAAM,CAAC,CAAC,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,UAAM,SAAS,MAAM,OAAO;AAC5B,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,UAAM,SAAS,MAAM,OAAO;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,OAAO;AAC5B,SAAO;AACT;;;ADjXA,SAAS,UAAU,MAAsB;AACvC,SAAO,KACJ,QAAQ,YAAY,GAAG,EACvB,QAAQ,cAAc,GAAG,EACzB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAGA,SAAS,kBAAkB,MAAsB;AAC/C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,IAAI;AAG9B,QAAI,QAAQ,OAAO,SAAS;AAC1B,aAAO,GAAG,OAAO,MAAM,QAAQ,GAAG,IAAI,OAAO,MAAM,UAAU,EAAE,KAAK,OAAO,MAAM,OAAO,GAAG,KAAK;AAAA,IAClG;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM,UAAU,KAAK,WAAW,GAAG,IAAI,UAAU,IAAI,IAAI;AACzD,SAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM,GAAG,GAAG,IAAI,QAAQ;AAChE;AAGA,SAAS,iBAAiB,UAA0B;AAClD,QAAM,WAAW,QAAQ,QAAQ;AACjC,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,IAAI,GAAG;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,WAAW;AAEjB,IAAM,kBACJ;AAEF,IAAM,mCACJ;AAcF,SAASC,QAAO,MAAkC;AAChD,QAAM,MAAM,QAAQ,IAAI,IAAI;AAC5B,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,IAAI,OAAO,GAAG;AACpB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAS,cAAc,UAA8B,SAAiB,UAA0B;AAC9F,SAAO,YAAYA,QAAO,OAAO,KAAK;AACxC;AAEA,SAAS,iBAAiB,QAAgB,MAAqD;AAC7F,UAAQ,QAAQ;AAAA,IACd,KAAK,KAAK;AAER,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,YACE,QAAQ,OAAO,WAAW,yBAC1B,OAAO,MAAM,SAAS,YAAY,EAAE,SAAS,MAAM,GACnD;AACA,iBAAO;AAAA,YACL,MAAM;AAAA,YACN,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AACA,aAAO,EAAE,MAAM,gBAAgB,YAAY,0CAA0C;AAAA,IACvF;AAAA,IACA,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF;AACE,UAAI,UAAU,KAAK;AACjB,eAAO;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,QACd;AAAA,MACF;AACA,aAAO,EAAE,MAAM,YAAY,MAAM,GAAG;AAAA,EACxC;AACF;AAEA,SAAS,YAAY,QAAyB;AAC5C,SAAO,WAAW,OAAO,WAAW,OAAO,UAAU;AACvD;AAEA,SAASC,eAAc,MAAc,SAAiB,KAAqB;AACzE,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,SAAS,KAAK,IAAI,aAAa,GAAG;AACxC,SAAO,UAAU,MAAM,KAAK,OAAO,IAAI;AACzC;AAEO,SAAS,iBAAiB,SAAuC;AACtE,QAAM,aAAa,cAAc,QAAQ,YAAY,mBAAmB,CAAC;AACzE,QAAM,UAAU,cAAc,QAAQ,SAAS,eAAe,GAAM;AACpE,QAAM,wBAAwB,QAAQ,iBAAiBD,QAAO,oBAAoB;AAClF,QAAM,YAAY,cAAc,QAAQ,WAAW,kBAAkB,GAAK;AAC1E,QAAM,WAAW,cAAc,QAAQ,UAAU,iBAAiB,GAAM;AACxE,QAAM,UAAU,QAAQ;AAExB,iBAAe,QACb,QACA,MACA,MACA,QACyB;AACzB,QAAI,MAAM,GAAG,QAAQ,WAAW,QAAQ,GAAG,IAAI;AAC/C,QAAI,QAAQ;AACV,YAAM,SAAS,IAAI,gBAAgB,MAAM;AACzC,aAAO,IAAI,OAAO,SAAS,CAAC;AAAA,IAC9B;AAGA,QAAI,QAAQ,MAAM,QAAQ,KAAK,eAAe;AAC9C,QAAI;AAEJ,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,UAAI,UAAU,GAAG;AACf,cAAM,QAAQC,eAAc,WAAW,UAAU,GAAG,QAAQ;AAC5D,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC;AAAA,MAC/C;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAE1D,UAAI;AACF,cAAM,UAAkC;AAAA,UACtC,eAAe,UAAU,KAAK;AAAA,UAC9B,gBAAgB;AAAA,UAChB,mBAAmB;AAAA,UACnB,YAAY;AAAA,QACd;AAEA,cAAM,OAAoB;AAAA,UACxB;AAAA,UACA;AAAA,UACA,QAAQ,WAAW;AAAA,UACnB,WAAW;AAAA,QACb;AAEA,YAAI,SAAS,QAAW;AACtB,eAAK,OAAO,KAAK,UAAU,IAAI;AAAA,QACjC;AAEA,cAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AAEtC,YAAI,SAAS,IAAI;AACf,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,gBAAM,OAAO,OAAQ,KAAK,MAAM,IAAI,IAAW,CAAC;AAChD,iBAAO,EAAE,MAAM,QAAQ,SAAS,OAAO;AAAA,QACzC;AAEA,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,EAAE,MAAM,WAAW,IAAI,iBAAiB,SAAS,QAAQ,SAAS;AAExE,cAAM,MAAM,IAAI;AAAA,UACd,GAAG,MAAM,IAAI,IAAI,uBAAuB,SAAS,MAAM,KAAK,kBAAkB,SAAS,CAAC;AAAA,UACxF;AAAA,UACA,SAAS;AAAA,UACT;AAAA,QACF;AAEA,YAAI,YAAY,SAAS,MAAM,KAAK,UAAU,YAAY;AACxD,sBAAY;AACZ,gBAAM,QAAQA,eAAc,WAAW,SAAS,QAAQ;AACxD,oBAAU;AAAA,YACR,SAAS,UAAU;AAAA,YACnB;AAAA,YACA;AAAA,YACA,QAAQ,SAAS;AAAA,YACjB,OAAO,IAAI;AAAA,YACX,SAAS,KAAK,MAAM,KAAK;AAAA,YACzB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AACD;AAAA,QACF;AAGA,YAAI,SAAS,WAAW,OAAO,UAAU,YAAY;AACnD,kBAAQ,MAAM,QAAQ,KAAK,eAAe;AAC1C,sBAAY;AACZ;AAAA,QACF;AAEA,cAAM;AAAA,MACR,SAAS,OAAO;AACd,YAAI,iBAAiB,cAAc;AACjC,gBAAM;AAAA,QACR;AAEA,YAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AAChE,gBAAM,aAAa,IAAI;AAAA,YACrB,GAAG,MAAM,IAAI,IAAI,oBAAoB,OAAO;AAAA,YAC5C;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,cAAI,UAAU,YAAY;AACxB,wBAAY;AACZ,sBAAU;AAAA,cACR,SAAS,UAAU;AAAA,cACnB;AAAA,cACA;AAAA,cACA,OAAO,WAAW;AAAA,cAClB,SAAS,KAAK,MAAMA,eAAc,WAAW,SAAS,QAAQ,CAAC;AAAA,cAC/D,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YACpC,CAAC;AACD;AAAA,UACF;AACA,gBAAM;AAAA,QACR;AAEA,cAAM,aAAa,IAAI;AAAA,UACrB,GAAG,MAAM,IAAI,IAAI,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,UACnF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,UAAU,YAAY;AACxB,sBAAY;AACZ,oBAAU;AAAA,YACR,SAAS,UAAU;AAAA,YACnB;AAAA,YACA;AAAA,YACA,OAAO,WAAW;AAAA,YAClB,SAAS,KAAK,MAAMA,eAAc,WAAW,SAAS,QAAQ,CAAC;AAAA,YAC/D,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AACD;AAAA,QACF;AACA,cAAM;AAAA,MACR,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAGA,UACE,aACA,IAAI;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EAEJ;AAGA,WAAS,qBAAqB,eAA+B;AAC3D,QAAI,0BAA0B,OAAW,QAAO;AAEhD,UAAM,SAAS,iBAAiB,OAAO;AACvC,WAAO,KAAK,IAAI,SAAS,MAAS,KAAK,KAAK,MAAM,IAAI,GAAK;AAAA,EAC7D;AAEA,iBAAe,cACb,MACA,UACA,aACA,UAAkB,iBACO;AACzB,UAAM,MAAM,GAAG,OAAO,GAAG,IAAI;AAC7B,UAAM,eAAe,iBAAiB,QAAQ;AAC9C,UAAM,aAAa,MAAM,SAAS,YAAY;AAC9C,UAAM,mBAAmB,qBAAqB,WAAW,UAAU;AAGnE,QAAI,QAAQ,MAAM,QAAQ,KAAK,eAAe;AAC9C,QAAI;AAEJ,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,UAAI,UAAU,GAAG;AACf,cAAM,QAAQA,eAAc,WAAW,UAAU,GAAG,QAAQ;AAC5D,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC;AAAA,MAC/C;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,gBAAgB;AAEnE,UAAI;AACF,cAAM,UAAkC;AAAA,UACtC,eAAe,UAAU,KAAK;AAAA,UAC9B,gBAAgB;AAAA,UAChB,mBAAmB;AAAA,UACnB,YAAY;AAAA,QACd;AAEA,cAAM,WAAW,MAAM,MAAM,KAAK;AAAA,UAChC,QAAQ;AAAA,UACR;AAAA,UACA,MAAM;AAAA,UACN,QAAQ,WAAW;AAAA,UACnB,WAAW;AAAA,QACb,CAAC;AAED,YAAI,SAAS,IAAI;AACf,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,gBAAM,OAAO,OAAQ,KAAK,MAAM,IAAI,IAAW,CAAC;AAChD,iBAAO,EAAE,MAAM,QAAQ,SAAS,OAAO;AAAA,QACzC;AAEA,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,EAAE,MAAM,WAAW,IAAI,iBAAiB,SAAS,QAAQ,SAAS;AAExE,cAAM,MAAM,IAAI;AAAA,UACd,eAAe,IAAI,uBAAuB,SAAS,MAAM,KAAK,kBAAkB,SAAS,CAAC;AAAA,UAC1F;AAAA,UACA,SAAS;AAAA,UACT;AAAA,QACF;AAEA,YAAI,YAAY,SAAS,MAAM,KAAK,UAAU,YAAY;AACxD,sBAAY;AACZ,gBAAM,QAAQA,eAAc,WAAW,SAAS,QAAQ;AACxD,oBAAU;AAAA,YACR,SAAS,UAAU;AAAA,YACnB,QAAQ;AAAA,YACR,MAAM,UAAU,IAAI;AAAA,YACpB,QAAQ,SAAS;AAAA,YACjB,OAAO,IAAI;AAAA,YACX,SAAS,KAAK,MAAM,KAAK;AAAA,YACzB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AACD;AAAA,QACF;AAGA,YAAI,SAAS,WAAW,OAAO,UAAU,YAAY;AACnD,kBAAQ,MAAM,QAAQ,KAAK,eAAe;AAC1C,sBAAY;AACZ;AAAA,QACF;AAEA,cAAM;AAAA,MACR,SAAS,OAAO;AACd,YAAI,iBAAiB,cAAc;AACjC,gBAAM;AAAA,QACR;AAEA,YAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AAChE,gBAAM,SAAS,KAAK,MAAM,WAAW,cAAc,OAAO,KAAK;AAC/D,gBAAM,aAAa,IAAI;AAAA,YACrB,eAAe,IAAI,oBAAoB,gBAAgB,aAAa,MAAM;AAAA,YAC1E;AAAA,YACA;AAAA,YACA,4CAA4C,mBAAmB,CAAC;AAAA,UAClE;AACA,cAAI,UAAU,YAAY;AACxB,wBAAY;AACZ,sBAAU;AAAA,cACR,SAAS,UAAU;AAAA,cACnB,QAAQ;AAAA,cACR,MAAM,UAAU,IAAI;AAAA,cACpB,OAAO,WAAW;AAAA,cAClB,SAAS,KAAK,MAAMA,eAAc,WAAW,SAAS,QAAQ,CAAC;AAAA,cAC/D,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YACpC,CAAC;AACD;AAAA,UACF;AACA,gBAAM;AAAA,QACR;AAEA,cAAM,aAAa,IAAI;AAAA,UACrB,eAAe,IAAI,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,UACrF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,UAAU,YAAY;AACxB,sBAAY;AACZ,oBAAU;AAAA,YACR,SAAS,UAAU;AAAA,YACnB,QAAQ;AAAA,YACR,MAAM,UAAU,IAAI;AAAA,YACpB,OAAO,WAAW;AAAA,YAClB,SAAS,KAAK,MAAMA,eAAc,WAAW,SAAS,QAAQ,CAAC;AAAA,YAC/D,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AACD;AAAA,QACF;AACA,cAAM;AAAA,MACR,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAEA,UACE,aACA,IAAI;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,IAAO,MAAc,QAAiC;AACpD,aAAO,QAAW,OAAO,MAAM,QAAW,MAAM;AAAA,IAClD;AAAA,IACA,KAAQ,MAAc,MAAgB;AACpC,aAAO,QAAW,QAAQ,MAAM,IAAI;AAAA,IACtC;AAAA,IACA,IAAO,MAAc,MAAgB;AACnC,aAAO,QAAW,OAAO,MAAM,IAAI;AAAA,IACrC;AAAA,IACA,MAAS,MAAc,MAAgB;AACrC,aAAO,QAAW,SAAS,MAAM,IAAI;AAAA,IACvC;AAAA,IACA,OAAU,MAAc;AACtB,aAAO,QAAW,UAAU,IAAI;AAAA,IAClC;AAAA,IACA,OAAU,MAAc,UAAkB,aAAqB;AAC7D,aAAO,cAAiB,MAAM,UAAU,WAAW;AAAA,IACrD;AAAA,IACA,MAAM,gBAAmB,MAAc,UAAkB,aAAqB,eAAwC;AACpH,YAAM,eAAe,iBAAiB,QAAQ;AAC9C,YAAM,YAAY,MAAMC,MAAK,YAAY;AAGzC,YAAM,YAAYF,QAAO,gCAAgC,KAAK;AAC9D,UAAI,UAAU,OAAO,aAAa,CAAC,eAAe,kBAAkB;AAElE,uBAAe,aAAa,EAAE,eAAe,GAAG,YAAY,UAAU,MAAM,SAAS,GAAG,gBAAgB,GAAG,YAAY,EAAE,CAAC;AAC1H,cAAM,SAAS,MAAM,cAAiB,MAAM,cAAc,WAAW;AACrE,uBAAe,aAAa,EAAE,eAAe,UAAU,MAAM,YAAY,UAAU,MAAM,SAAS,KAAK,gBAAgB,GAAG,YAAY,EAAE,CAAC;AACzI,eAAO;AAAA,MACT;AAEA,YAAM,YAAY,GAAG,eAAe,GAAG,IAAI;AAC3C,aAAO,gBAAmB,WAAW,cAAc,aAAa;AAAA,QAC9D,gBAAgB,MAAM,QAAQ,KAAK,eAAe;AAAA,QAClD;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,GAAG,aAAa;AAAA,IAClB;AAAA,IACA,eAAkB,MAAc,UAAkB,aAAqB;AACrE,aAAO,cAAiB,MAAM,UAAU,aAAa,gCAAgC;AAAA,IACvF;AAAA,IACA,MAAM,SAAS,MAAoC;AACjD,YAAM,MAAM,GAAG,QAAQ,WAAW,QAAQ,GAAG,IAAI;AACjD,YAAM,QAAQ,MAAM,QAAQ,KAAK,eAAe;AAChD,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAE1D,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,KAAK;AAAA,UAChC,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,eAAe,UAAU,KAAK;AAAA,YAC9B,mBAAmB;AAAA,YACnB,YAAY;AAAA,UACd;AAAA,UACA,QAAQ,WAAW;AAAA,UACnB,WAAW;AAAA,QACb,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,YAAY,MAAM,SAAS,KAAK;AACtC,gBAAM,EAAE,MAAM,WAAW,IAAI,iBAAiB,SAAS,QAAQ,SAAS;AACxE,gBAAM,IAAI;AAAA,YACR,OAAO,IAAI,uBAAuB,SAAS,MAAM,KAAK,kBAAkB,SAAS,CAAC;AAAA,YAClF;AAAA,YACA,SAAS;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAEA,eAAO,MAAM,SAAS,YAAY;AAAA,MACpC,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AACF;;;AE3HA,eAAe,UAAU,SAAkC,QAA+B;AACxF,MAAI,QAAS,OAAM,QAAQ,QAAQ,MAAM;AAC3C;AAEO,SAAS,gBAAgB,SAA0C;AACxE,QAAM,OAAO,iBAAiB,OAAO;AACrC,QAAM,UAAU,QAAQ,eAAe;AAEvC,SAAO;AAAA,IACL,OAAO;AAAA,MACL,MAAM,OAAO,aAAa;AACxB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAc,IAAI,WAAW,QAAQ;AACjE,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,IAAI,aAAa,QAAQ;AAC7B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAa,IAAI,WAAW,UAAU,MAAM,EAAE;AAC1E,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,SAAS,aAAa,QAAQ;AAClC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAc,IAAI,WAAW,UAAU,MAAM,WAAW;AACpF,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ;AAChC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAc,IAAI,WAAW,UAAU,MAAM,SAAS;AAClF,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ;AAChC,cAAM,KAAK,OAAO,IAAI,WAAW,UAAU,MAAM,EAAE;AAAA,MACrD;AAAA,IACF;AAAA,IAEA,SAAS;AAAA,MACP,MAAM,IAAI,aAAa,QAAQ;AAC7B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAgB,IAAI,WAAW,UAAU,MAAM,UAAU;AACrF,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,SAAS;AACzC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM;AAAA,UAC/B;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,MAAM,aAAa,QAAQ,SAAS;AACxC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM;AAAA,UAC/B;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,SAAS;AAAA,MACP,MAAM,KAAK,aAAa,QAAQ;AAC9B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM;AAAA,QACjC;AACA,eAAO,KAAK;AAAA,MACd;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,UAAU,eAAe;AACzD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM;AAAA,UAC/B;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,CAAC,QAAQ,CAAC,KAAK,aAAa;AAC9B,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,QAAQ;AAAA,MACN,MAAM,KAAK,aAAa,QAAQ;AAC9B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM;AAAA,QACjC;AACA,eAAO,KAAK;AAAA,MACd;AAAA,MAEA,MAAM,IAAI,aAAa,QAAQ,OAAO;AACpC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAW,IAAI,WAAW,UAAU,MAAM,WAAW,KAAK,EAAE;AACxF,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,WAAW;AAC3C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAY,IAAI,WAAW,UAAU,MAAM,WAAW;AAAA,UAChF,OAAO;AAAA,QACT,CAAC;AACD,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,OAAO,SAAS;AAChD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAW,IAAI,WAAW,UAAU,MAAM,WAAW,KAAK,IAAI;AAAA,UACxF;AAAA,UACA,UAAU,CAAC,OAAO;AAAA,QACpB,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM;AAAA,MACJ,MAAM,oBAAoB,aAAa,QAAQ,SAAS;AACtD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM;AAAA,UAC/B,EAAE,qBAAqB,QAAQ;AAAA,QACjC;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,UAAU;AAAA,MACR,MAAM,KAAK,aAAa,QAAQ;AAC9B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM;AAAA,QACjC;AACA,eAAO,KAAK,YAAY,CAAC;AAAA,MAC3B;AAAA,MAEA,MAAM,IAAI,aAAa,QAAQ,UAAU;AACvC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ;AAAA,QACtD;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,UAAU,SAAS;AACnD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ;AAAA,UACpD;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,MAAM,aAAa,QAAQ,UAAU,SAAS;AAClD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ;AAAA,UACpD;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,UAAU;AAC1C,cAAM,KAAK,OAAO,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ,EAAE;AAAA,MAC1E;AAAA,MAEA,MAAM,UAAU,aAAa,QAAQ;AACnC,cAAM,KAAK,OAAO,IAAI,WAAW,UAAU,MAAM,WAAW;AAAA,MAC9D;AAAA,IACF;AAAA,IAEA,QAAQ;AAAA,MACN,MAAM,KAAK,aAAa,QAAQ,UAAU,WAAW;AACnD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ,IAAI,SAAS;AAAA,QACnE;AACA,eAAO,KAAK,UAAU,CAAC;AAAA,MACzB;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,UAAU,WAAW,UAAU;AAC/D,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ,IAAI,SAAS;AAAA,UACjE;AAAA,UACA,SAAS,SAAS,MAAM,IAAI,cAAc;AAAA,QAC5C;AACA,YAAI,CAAC,KAAK,OAAO;AACf,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,UAAU,WAAW,SAAS;AAC9D,cAAM,KAAK;AAAA,UACT,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ,IAAI,SAAS,IAAI,OAAO;AAAA,QAC9E;AAAA,MACF;AAAA,MAEA,MAAM,UAAU,aAAa,QAAQ,UAAU,WAAW;AACxD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,aAAa,QAAQ,IAAI,SAAS;AAAA,QACnE;AACA,eAAO,KAAK,WAAW,CAAC;AAAA,MAC1B;AAAA,IACF;AAAA,IAEA,qBAAqB;AAAA,MACnB,MAAM,IAAI,aAAa,QAAQ,OAAO;AACpC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,wBAAwB,KAAK;AAAA,QAC9D;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,YAAY;AAAA,MACV,MAAM,IAAI,aAAa;AACrB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAgB,IAAI,WAAW,aAAa;AACxE,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,MAAM;AAC9B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAgB,IAAI,WAAW,eAAe,IAAI;AAC9E,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,SAAS;AAAA,MACP,MAAM,KAAK,aAAaG,UAAU;AAChC,cAAM,UAAU,SAAS,YAAY;AACrC,cAAM,SAAiC,CAAC;AACxC,YAAIA,UAAS,MAAO,QAAO,OAAO,IAAIA,SAAQ;AAC9C,YAAIA,UAAS,WAAY,QAAO,YAAY,IAAI,OAAOA,SAAQ,UAAU;AACzE,YAAIA,UAAS;AACX,iBAAO,qBAAqB,IAAIA,SAAQ;AAC1C,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf,YAAY,SAAS;AAAA,QACvB;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,IAAI,aAAa,UAAU,qBAAsB;AACrD,cAAM,UAAU,SAAS,YAAY;AACrC,cAAM,SAAiC,CAAC;AACxC,YAAI,oBAAqB,QAAO,qBAAqB,IAAI;AACzD,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,YAAY,QAAQ;AAAA,UACnC,YAAY,SAAS;AAAA,QACvB;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,MAAM,aAAa,UAAU,WAAW;AAC5C,cAAM,UAAU,SAAS,aAAa;AACtC,cAAM,OAA2B,EAAE,UAAU;AAC7C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,YAAY,QAAQ;AAAA,UACnC;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,eAAe;AAAA,MACb,MAAM,KAAK,aAAaA,UAAU;AAChC,cAAM,SAAiC,CAAC;AACxC,YAAIA,UAAS,UAAW,QAAO,WAAW,IAAIA,SAAQ;AACtD,YAAIA,UAAS,SAAU,QAAO,UAAU,IAAI,OAAOA,SAAQ,QAAQ;AACnE,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf,YAAY,SAAS;AAAA,QACvB;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,IAAI,aAAa,WAAW;AAChC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAkB,IAAI,WAAW,kBAAkB,SAAS,EAAE;AAC1F,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,MAAM,WAAY;AAC1C,cAAM,SAAiC,CAAC;AACxC,YAAI,UAAW,QAAO,WAAW,IAAI;AACrC,eAAO,wBAAwB,IAAI;AACnC,cAAM,OAAO,IAAI,WAAW,kBAAkB,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC;AACpF,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAmB,MAAM,IAAI;AACzD,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,WAAW,MAAM,YAAa,gBAAiB;AACvE,cAAM,SAAiC,CAAC;AACxC,YAAI,WAAY,QAAO,YAAY,IAAI;AACvC,eAAO,wBAAwB,IAAI,kBAAkB;AACrD,cAAM,OAAO,IAAI,WAAW,kBAAkB,SAAS,IAAI,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC;AACjG,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,MAAoB,MAAM,IAAI;AAC1D,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,WAAW;AACnC,cAAM,KAAK,OAAO,IAAI,WAAW,kBAAkB,SAAS,EAAE;AAAA,MAChE;AAAA,MAEA,MAAM,iBAAiB,aAAa,WAAW,YAAY;AACzD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU;AAAA,QACpE;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,mBAAmB,aAAa,WAAW,YAAY;AAC3D,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU;AAAA,QACpE;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,eAAe,aAAa,WAAW,YAAY;AACvD,cAAM,KAAK,OAAO,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU,EAAE;AAAA,MACxF;AAAA,MAEA,MAAM,cAAc,aAAa,WAAW,YAAY,MAAM;AAC5D,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU;AAAA,UAClE;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,WAAW,aAAa,WAAW,YAAY;AACnD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU;AAAA,QACpE;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,SAAS,aAAa,WAAW,YAAY,SAAS;AAC1D,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU,WAAW,OAAO;AAAA,QACtF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,YAAY,aAAa,WAAW,YAAY,MAAM,SAAU;AACpE,cAAM,SAAiC,CAAC;AACxC,YAAI,QAAS,QAAO,SAAS,IAAI;AACjC,eAAO,wBAAwB,IAAI;AACnC,cAAM,OAAO,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU,WAAW,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC;AAChI,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAwB,MAAM,IAAI;AAC9D,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,YACJ,aACA,WACA,YACA,SACA,MACA,YACA,gBACA;AACA,cAAM,SAAiC,CAAC;AACxC,YAAI,WAAY,QAAO,YAAY,IAAI;AACvC,eAAO,wBAAwB,IAAI,kBAAkB;AACrD,cAAM,OAAO,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU,WAAW,OAAO,IAAI,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC;AAC3I,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,MAAyB,MAAM,IAAI;AAC/D,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,YAAY,aAAa,WAAW,YAAY,SAAS;AAC7D,cAAM,KAAK;AAAA,UACT,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU,WAAW,OAAO;AAAA,QACtF;AAAA,MACF;AAAA,MAEA,MAAM,cAAc,aAAa,WAAW,YAAY,SAAS;AAC/D,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU,WAAW,OAAO;AAAA,QACtF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,gBAAgB,aAAa,WAAW,YAAY,SAAS;AACjE,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,SAAS,cAAc,UAAU,WAAW,OAAO;AAAA,QACtF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,eAAe;AAAA,MACb,MAAM,KAAK,aAAaA,UAAU;AAGhC,cAAM,SAAiC,CAAC;AACxC,YAAIA,UAAS,MAAO,QAAO,OAAO,IAAIA,SAAQ;AAC9C,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf,YAAY,SAAS;AAAA,QACvB;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,IAAI,aAAa,KAAK;AAC1B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAkB,IAAI,WAAW,kBAAkB,GAAG,EAAE;AACpF,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,MAAMA,UAAU;AACxC,cAAM,SAAiC,CAAC;AACxC,YAAIA,UAAS,yBAA0B,QAAO,0BAA0B,IAAI;AAC5E,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,OAAO,YACT,IAAI,WAAW,kBAAkB,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC,KACvE,IAAI,WAAW;AACnB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAmB,MAAM,IAAI;AACzD,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,KAAK,MAAMA,UAAU;AAC7C,cAAM,SAAiC,CAAC;AACxC,YAAIA,UAAS,yBAA0B,QAAO,0BAA0B,IAAI;AAC5E,YAAIA,UAAS,aAAc,QAAO,cAAc,IAAI;AACpD,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,OAAO,YACT,IAAI,WAAW,kBAAkB,GAAG,IAAI,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC,KAC9E,IAAI,WAAW,kBAAkB,GAAG;AACxC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAkB,MAAM,IAAI;AACxD,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,KAAK;AAC7B,cAAM,KAAK,OAAO,IAAI,WAAW,kBAAkB,GAAG,EAAE;AAAA,MAC1D;AAAA,MAEA,MAAM,YAAY,aAAa,UAAU;AACvC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,SAAS,aAAa,MAAM;AAChC,cAAM,SAAiC,CAAC;AACxC,YAAI,KAAK,SAAS,GAAG;AACnB,iBAAO,KAAK,IAAI,KAAK,KAAK,GAAG;AAAA,QAC/B;AACA,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS;AAAA,QAC5C;AACA,eAAO,KAAK,gBAAgB,CAAC;AAAA,MAC/B;AAAA,IACF;AAAA,IAEA,WAAW;AAAA,MACT,MAAM,WAAW,aAAa,WAAW,OAAO;AAC9C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,uBAAuB,SAAS,WAAW,KAAK;AAAA,QACjE;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,mBAAmB,aAAa,WAAW,OAAO,MAAO;AAC7D,cAAM,KAAK;AAAA,UACT,IAAI,WAAW,uBAAuB,SAAS,WAAW,KAAK;AAAA,UAC/D;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,eAAe,aAAa,WAAW,OAAO;AAClD,cAAM,KAAK,KAAK,IAAI,WAAW,uBAAuB,SAAS,WAAW,KAAK,UAAU;AAAA,MAC3F;AAAA,MAEA,MAAM,kBAAkB,aAAa,OAAO;AAC1C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,qCAAqC,KAAK;AAAA,QAC3D;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,kBAAkB,aAAa,gBAAgB,OAAO;AAC1D,YAAI,OAAO,YAAY,eAAe,QAAQ,aAAa;AACzD,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,4BAA4B,cAAc,WAAW,KAAK;AAAA,QAC3E;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,mBAAmB,aAAa,gBAAgB,OAAO;AAC3D,cAAM,KAAK;AAAA,UACT,IAAI,WAAW,4BAA4B,cAAc,WAAW,KAAK;AAAA,QAC3E;AAAA,MACF;AAAA,MAEA,MAAM,kBAAkB,aAAa,gBAAgB,OAAO,MAAM;AAChE,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,4BAA4B,cAAc,WAAW,KAAK;AAAA,UACzE;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,qBAAqB,aAAa,OAAO;AAC7C,cAAM,KAAK,KAAK,IAAI,WAAW,qCAAqC,KAAK,SAAS;AAAA,MACpF;AAAA,MAEA,MAAM,qBAAqB,aAAa,OAAO;AAC7C,cAAM,KAAK,KAAK,IAAI,WAAW,qCAAqC,KAAK,SAAS;AAAA,MACpF;AAAA,MAEA,MAAM,WAAW,aAAaA,UAAU;AACtC,cAAM,UAAU,SAAS,aAAa;AACtC,cAAM,UAAU,SAAS,aAAa;AACtC,cAAM,SAAiC,CAAC;AACxC,YAAIA,UAAS,UAAW,QAAO,WAAW,IAAIA,SAAQ;AACtD,YAAIA,UAAS,QAAS,QAAO,SAAS,IAAIA,SAAQ;AAClD,YAAIA,UAAS,WAAY,QAAO,YAAY,IAAI,OAAOA,SAAQ,UAAU;AACzE,YAAIA,UAAS,MAAO,QAAO,OAAO,IAAIA,SAAQ;AAC9C,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf,YAAY,SAAS;AAAA,QACvB;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,QAAQ;AAAA,MACN,MAAM,OAAO,aAAa,SAAS,MAAO;AACxC,cAAM,KAAK,KAAK,IAAI,WAAW,WAAW,OAAO,WAAW,IAAI;AAAA,MAClE;AAAA,IACF;AAAA,IAEA,cAAc;AAAA,MACZ,MAAM,oBAAoB,aAAa,OAAO;AAC5C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,SAAS;AAAA,MACP,MAAM,KAAK,aAAa,YAAY,MAAM,OAAO;AAC/C,cAAM,WAAW,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG;AAC9C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,YAAY,UAAU,IAAI,IAAI,IAAI,QAAQ;AAAA,QAC3D;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,SAAS;AAAA,MACP,MAAM,IAAI,aAAa,QAAQ,OAAO;AACpC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,YAAY,KAAK;AAAA,QAClD;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ,OAAO,aAAa;AACpD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,YAAY,KAAK;AAAA,UAChD;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,eAAe;AAAA,MACb,MAAM,OAAO,aAAa,QAAQ,aAAa,UAAU;AACvD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,MAAM,SAAS,WAAW;AAAA,UACnD;AAAA,UACA;AAAA,QACF;AACA,YAAI,CAAC,KAAK,mBAAmB;AAC3B,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,IAEA,aAAa;AAAA,MACX,MAAM,KAAK,aAAa,aAAc;AACpC,cAAM,SAAiC,CAAC;AACxC,YAAI,gBAAgB,OAAW,QAAO,aAAa,IAAI,OAAO,WAAW;AACzE,cAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf,YAAY,SAAS;AAAA,QACvB;AACA,eAAO,KAAK,mBAAmB,CAAC;AAAA,MAClC;AAAA,MAEA,MAAM,OAAO,aAAa,eAAe;AACvC,cAAM,KAAK,KAAK,IAAI,WAAW,gBAAgB,aAAa,SAAS;AAAA,MACvE;AAAA,MAEA,MAAM,OAAO,aAAa,eAAe;AACvC,cAAM,KAAK,KAAK,IAAI,WAAW,gBAAgB,aAAa,SAAS;AAAA,MACvE;AAAA,MAEA,MAAM,OAAO,aAAa,SAAS;AACjC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,aAAa,aAAa,eAAe,WAAW;AACxD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,aAAa;AAAA,UAC9C;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,sBAAsB;AAAA,MACpB,MAAM,OAAO,aAAa,MAAM;AAC9B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,IAAI,aAAa,eAAe;AACpC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,yBAAyB,aAAa;AAAA,QACvD;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,eAAe,YAAY;AACnD,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,yBAAyB,aAAa;AAAA,UACrD;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,aAAa;AAAA,MACX,MAAM,KAAK,aAAa;AACtB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,QACjB;AACA,eAAO,KAAK,qBAAqB,CAAC;AAAA,MACpC;AAAA,MAEA,MAAM,IAAI,aAAa,UAAU;AAC/B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,sBAAsB,QAAQ;AAAA,QAC/C;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,QAAQ;AAChC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,iBAAiB;AAAA,MACf,MAAM,KAAK,aAAa;AACtB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,QACjB;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,IAAI,aAAa,WAAW;AAChC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,SAAS;AAAA,QAC9C;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,MAAM;AAC9B,cAAM,SAAS,IAAI,gBAAgB,EAAE,0BAA0B,UAAU,CAAC;AAC1E,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,OAAO,SAAS,CAAC;AAAA,UACpD;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,WAAW,MAAM,YAAa,gBAAiB;AACvE,cAAM,SAAiC,CAAC;AACxC,YAAI,WAAY,QAAO,YAAY,IAAI;AACvC,eAAO,wBAAwB,IAAI,kBAAkB;AACrD,cAAM,OAAO,IAAI,WAAW,oBAAoB,SAAS,IAAI,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC;AACnG,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,MAAsB,MAAM,IAAI;AAC5D,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,WAAW;AACnC,cAAM,KAAK,OAAO,IAAI,WAAW,oBAAoB,SAAS,EAAE;AAAA,MAClE;AAAA,MAEA,MAAM,WAAW,aAAa,WAAW;AACvC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,SAAS;AAAA,QAC9C;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,SAAS,aAAa,WAAW,SAAS;AAC9C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,SAAS,WAAW,OAAO;AAAA,QAChE;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,YAAY,aAAa,WAAW,MAAM;AAC9C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,SAAS;AAAA,UAC5C;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,YAAY,aAAa,WAAW,SAAS,MAAM,YAAa,gBAAiB;AACrF,cAAM,SAAiC,CAAC;AACxC,YAAI,WAAY,QAAO,YAAY,IAAI;AACvC,eAAO,wBAAwB,IAAI,kBAAkB;AACrD,cAAM,OAAO,IAAI,WAAW,oBAAoB,SAAS,WAAW,OAAO,IAAI,IAAI,gBAAgB,MAAM,EAAE,SAAS,CAAC;AACrH,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,MAAoB,MAAM,IAAI;AAC1D,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,YAAY,aAAa,WAAW,SAAS;AACjD,cAAM,KAAK,OAAO,IAAI,WAAW,oBAAoB,SAAS,WAAW,OAAO,EAAE;AAAA,MACpF;AAAA,IACF;AAAA,IAEA,iBAAiB;AAAA,MACf,MAAM,KAAK,aAAa;AACtB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,QACjB;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,IAAI,aAAa,kBAAkB;AACvC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,gBAAgB;AAAA,QACrD;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,MAAM;AAC9B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAqB,IAAI,WAAW,oBAAoB,IAAI;AACxF,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,SAAS,aAAa,kBAAkB;AAC5C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,gBAAgB;AAAA,QACrD;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,WAAW,aAAa,kBAAkB;AAC9C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,oBAAoB,gBAAgB;AAAA,QACrD;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,oBAAoB;AAAA,MAClB,MAAM,aAAa,aAAa,YAAY;AAC1C,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf;AAAA,UACA;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,UAAU,aAAa,SAAS;AACpC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW;AAAA,UACf;AAAA,UACA;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,eAAe;AAAA,MACb,MAAM,KAAK,aAAa,aAAa;AACnC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,kBAAkB,WAAW;AAAA,QAC9C;AACA,eAAO,KAAK,iBAAiB,CAAC;AAAA,MAChC;AAAA,MAEA,MAAM,SAAS,aAAa,aAAa,IAAI;AAC3C,eAAO,KAAK,SAAS,IAAI,WAAW,kBAAkB,WAAW,aAAa,EAAE,EAAE;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AACF;;;ACtsCO,IAAM,qBAAsD;AAAA,EACjE,SAAS,EAAE,MAAM,WAAW,WAAW,KAAK,YAAY,KAAK,kBAAkB,IAAM;AAAA,EACrF,YAAY,EAAE,MAAM,cAAc,WAAW,KAAK,YAAY,KAAK,kBAAkB,KAAU;AAAA,EAC/F,aAAa;AAAA,IACX,MAAM;AAAA,IACN,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,kBAAkB;AAAA,EACpB;AAAA,EACA,aAAa,EAAE,MAAM,eAAe,WAAW,IAAI,YAAY,IAAI,kBAAkB,IAAO;AAAA,EAC5F,aAAa;AAAA,IACX,MAAM;AAAA,IACN,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,kBAAkB;AAAA,EACpB;AAAA,EACA,WAAW,EAAE,MAAM,aAAa,WAAW,IAAI,YAAY,IAAI,kBAAkB,IAAM;AACzF;AAEO,SAAS,kBAAkB,SAA0C;AAC1E,QAAM,SAAS,oBAAI,IAAyB;AAE5C,MAAI,SAAS;AACX,eAAW,UAAU,SAAS;AAC5B,aAAO,IAAI,OAAO,MAAM;AAAA,QACtB,QAAQ,OAAO;AAAA,QACf,gBAAgB,KAAK,IAAI;AAAA,QACzB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,QAA+B;AAC3C,YAAM,QAAQ,OAAO,IAAI,MAAM;AAC/B,UAAI,CAAC,MAAO;AAEZ,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,UAAU,MAAM,MAAM;AAC5B,YAAM,SAAS,KAAK;AAAA,QACjB,UAAU,MAAM,OAAO,mBAAoB,MAAM,OAAO;AAAA,MAC3D;AAEA,UAAI,SAAS,GAAG;AACd,cAAM,SAAS,KAAK,IAAI,MAAM,OAAO,WAAW,MAAM,SAAS,MAAM;AACrE,cAAM,iBAAiB;AAAA,MACzB;AAEA,UAAI,MAAM,SAAS,GAAG;AACpB,cAAM;AACN;AAAA,MACF;AAEA,YAAM,eAAe;AACrB,YAAM,SAAS,KAAK;AAAA,QACjB,eAAe,MAAM,OAAO,aAAc,MAAM,OAAO;AAAA,MAC1D;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAG9C,YAAM,YAAY,KAAK,IAAI;AAC3B,YAAM,eAAe,YAAY,MAAM;AACvC,YAAM,YAAY,KAAK;AAAA,QACpB,eAAe,MAAM,OAAO,mBAAoB,MAAM,OAAO;AAAA,MAChE;AACA,YAAM,SAAS,KAAK,IAAI,MAAM,OAAO,WAAW,SAAS,IAAI;AAC7D,YAAM,iBAAiB;AAAA,IACzB;AAAA,EACF;AACF;;;AC1EA,IAAM,qBAAqB;AA0BpB,SAAS,sBAAsB,SAA+C;AACnF,QAAM,OAAO,iBAAiB,EAAE,GAAG,SAAS,SAAS,mBAAmB,CAAC;AACzE,QAAM,UAAU,QAAQ,eAAe,kBAAkB,CAAC,mBAAmB,WAAW,CAAE,CAAC;AAE3F,SAAO;AAAA,IACL,MAAM,eAAe,aAAa,WAAW,OAAO;AAClD,YAAM,QAAQ,QAAQ,WAAW;AACjC,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B,SAAS,WAAW,IAAI,SAAS;AAAA,QACjC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,aAAa,aAAa;AAC9B,YAAM,QAAQ,QAAQ,WAAW;AACjC,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAA8B,SAAS,WAAW,YAAY;AAC1F,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,kBAAkB,aAAa,QAAS,UAAW,WAAY;AACnE,YAAM,QAAQ,QAAQ,WAAW;AACjC,YAAM,SAAiC,CAAC;AACxC,UAAI,OAAQ,QAAO,QAAQ,IAAI;AAC/B,UAAI,SAAU,QAAO,UAAU,IAAI,OAAO,QAAQ;AAClD,UAAI,UAAW,QAAO,WAAW,IAAI;AACrC,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B,SAAS,WAAW;AAAA,QACpB;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,mBAAmB,aAAa,WAAW,UAAW,WAAY;AACtE,YAAM,QAAQ,QAAQ,WAAW;AACjC,YAAM,SAAiC,CAAC;AACxC,UAAI,SAAU,QAAO,UAAU,IAAI,OAAO,QAAQ;AAClD,UAAI,UAAW,QAAO,WAAW,IAAI;AACrC,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B,SAAS,WAAW,gBAAgB,SAAS;AAAA,QAC7C;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AChFA,IAAM,iBAAiB;AAwChB,SAAS,kBAAkB,SAA2C;AAC3E,QAAM,OAAO,iBAAiB,EAAE,GAAG,SAAS,SAAS,eAAe,CAAC;AAErE,SAAO;AAAA,IACL,MAAM,KAAK,aAAa,aAAc;AACpC,YAAM,SAAiC,CAAC;AACxC,UAAI,aAAa,UAAW,QAAO,WAAW,IAAI,YAAY;AAC9D,UAAI,aAAa,SAAU,QAAO,UAAU,IAAI,OAAO,YAAY,QAAQ;AAC3E,YAAM,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS;AAC/C,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B,IAAI,WAAW;AAAA,QACf,YAAY,SAAS;AAAA,MACvB;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,IAAI,aAAa,QAAQ;AAC7B,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAU,IAAI,WAAW,UAAU,MAAM,EAAE;AACvE,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,aAAa,MAAM;AAC9B,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAW,IAAI,WAAW,UAAU,IAAI;AACpE,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,aAAa,QAAQ,MAAM,YAAa;AACnD,UAAI,OAAO,IAAI,WAAW,UAAU,MAAM;AAC1C,UAAI,YAAY;AACd,gBAAQ,eAAe,mBAAmB,UAAU,EAAE,QAAQ,SAAS,GAAG,CAAC;AAAA,MAC7E;AACA,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK,MAAY,MAAM,IAAI;AAClD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,aAAa,QAAQ;AAChC,YAAM,KAAK,OAAO,IAAI,WAAW,UAAU,MAAM,EAAE;AAAA,IACrD;AAAA,IAEA,QAAQ;AAAA,MACN,MAAM,KAAK,aAAa,OAAO;AAC7B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,mBAAmB,KAAK,CAAC;AAAA,QACpD;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,OAAO,OAAO;AACtC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,IAAI,WAAW,UAAU,mBAAmB,KAAK,CAAC;AAAA,UAClD;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,MAAM,aAAa,OAAO,aAAa,OAAO,YAAa;AAC/D,YAAI,OAAO,IAAI,WAAW,UAAU,mBAAmB,KAAK,CAAC,WAAW,mBAAmB,WAAW,CAAC;AACvG,YAAI,YAAY;AACd,kBAAQ,eAAe,mBAAmB,UAAU,CAAC;AAAA,QACvD;AACA,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,MAAa,MAAM,KAAK;AACpD,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,aAAa,OAAO,aAAa;AAC5C,cAAM,KAAK;AAAA,UACT,IAAI,WAAW,UAAU,mBAAmB,KAAK,CAAC,WAAW,mBAAmB,WAAW,CAAC;AAAA,QAC9F;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC/GA,IAAM,iBAAiB;AAuDhB,SAAS,kBAAkB,SAA2C;AAC3E,QAAM,OAAO,iBAAiB,EAAE,GAAG,SAAS,SAAS,eAAe,CAAC;AAErE,SAAO;AAAA,IACL,cAAc;AAAA,MACZ,MAAM,KAAK,aAAa;AACtB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,+BAA+B,WAAW;AAAA,QAC5C;AACA,eAAO;AAAA,MACT;AAAA,MACA,MAAM,IAAI,aAAa,eAAe;AACpC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,iBAAiB,aAAa,kBAAkB,WAAW;AAAA,QAC7D;AACA,eAAO;AAAA,MACT;AAAA,MACA,MAAM,UAAU,aAAa,eAAe,YAAY,UAAU;AAChE,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,iBAAiB,aAAa,WAAW,UAAU,aAAa,QAAQ,kBAAkB,WAAW;AAAA,QACvG;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,cAAc;AAAA,MACZ,MAAM,KAAK,aAAa;AACtB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,+BAA+B,WAAW;AAAA,QAC5C;AACA,eAAO;AAAA,MACT;AAAA,MACA,MAAM,OAAO,aAAa,eAAe;AACvC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,iBAAiB,aAAa,yBAAyB,WAAW;AAAA,UAClE,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,MAAM,KAAK,aAAa;AACtB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,UAC1B,yBAAyB,WAAW;AAAA,QACtC;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;;;ACvGA,IAAM,sBAAsB;AAqBrB,SAAS,uBAAuB,SAAgD;AACrF,QAAM,OAAO,iBAAiB,EAAE,GAAG,SAAS,SAAS,oBAAoB,CAAC;AAE1E,SAAO;AAAA,IACL,MAAM;AAAA,MACJ,MAAM,OAAO,gBAAgB,KAAK;AAChC,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAgB,IAAI,cAAc,SAAS,GAAG;AAC1E,eAAO;AAAA,MACT;AAAA,MACA,MAAM,KAAK,gBAAgB;AACzB,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAA4B,IAAI,cAAc,OAAO;AACjF,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;;;AClCA,gBAAuB,SACrB,WACA,SACwC;AACxC,MAAI,YAAY,SAAS;AACzB,MAAI,YAAY;AAChB,QAAM,QAAQ,SAAS;AAEvB,aAAS;AACP,QAAI,UAAU,UAAa,aAAa,MAAO;AAE/C,UAAM,OAAO,MAAM,UAAU,SAAS;AACtC,UAAM,QAAQ,KAAK;AAEnB,QAAI,MAAM,WAAW,EAAG;AAExB,QAAI,UAAU,QAAW;AACvB,YAAM,YAAY,QAAQ;AAC1B,UAAI,MAAM,SAAS,WAAW;AAC5B,cAAM,MAAM,MAAM,GAAG,SAAS;AAC9B;AAAA,MACF;AAAA,IACF;AAEA,UAAM;AACN,iBAAa,MAAM;AACnB,gBAAY,KAAK;AAEjB,QAAI,CAAC,UAAW;AAAA,EAClB;AACF;AAEA,eAAsB,YACpB,WACA,SACqD;AACrD,QAAM,WAAoB,CAAC;AAC3B,MAAI;AACJ,QAAM,QAAQ,SAAS;AAEvB,mBAAiB,SAAS,SAAS,WAAW,OAAO,GAAG;AACtD,aAAS,KAAK,GAAG,KAAK;AACtB,QAAI,UAAU,UAAa,SAAS,UAAU,MAAO;AAAA,EACvD;AAGA,MAAI,UAAU,UAAa,SAAS,UAAU,OAAO;AACnD,oBAAgB;AAAA,EAClB;AAEA,SAAO,EAAE,OAAO,UAAU,eAAe,cAAc;AACzD;AAWA,eAAsB,iBACpB,WACA,YACA,cAAc,GACuC;AACrD,QAAM,WAAoB,CAAC;AAC3B,MAAI;AAGJ,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK,aAAa;AACvD,UAAM,QAAQ,WAAW,MAAM,GAAG,IAAI,WAAW;AACjD,UAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,UAAU,UAAU,KAAK,CAAC,CAAC;AAExE,eAAW,UAAU,SAAS;AAC5B,eAAS,KAAK,GAAG,OAAO,KAAK;AAC7B,UAAI,OAAO,eAAe;AACxB,4BAAoB,OAAO;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,UAAU,eAAe,kBAAkB;AAC7D;","names":["stat","envInt","jitteredDelay","stat","options"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpc-cli/api",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "Typed client for Google Play Developer API v3",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",