pgbeam 0.1.0 → 0.2.1

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.
@@ -1,3 +1,5 @@
1
+ import { _ } from '@swc/helpers/_/_extends';
2
+
1
3
  /**
2
4
  * Generated by scripts/generate-maps.ts — do not edit manually.
3
5
  * Run: pnpm --filter pgbeam generate
@@ -6,9 +8,17 @@
6
8
  exportAccountData: {
7
9
  method: "GET",
8
10
  path: "/v1/account/export"
11
+ },
12
+ getOnboardingProgress: {
13
+ method: "GET",
14
+ path: "/v1/organizations/{org_id}/onboarding"
15
+ },
16
+ updateOnboardingProgress: {
17
+ method: "PATCH",
18
+ path: "/v1/organizations/{org_id}/onboarding"
9
19
  }
10
20
  },
11
- usage: {
21
+ analytics: {
12
22
  getOrganizationActivity: {
13
23
  method: "GET",
14
24
  path: "/v1/organizations/{org_id}/activity"
@@ -21,6 +31,14 @@
21
31
  method: "GET",
22
32
  path: "/v1/organizations/{org_id}/plan"
23
33
  },
34
+ updateSpendLimit: {
35
+ method: "PUT",
36
+ path: "/v1/organizations/{org_id}/spend-limit"
37
+ },
38
+ submitCancellationFeedback: {
39
+ method: "POST",
40
+ path: "/v1/organizations/{org_id}/cancellation-feedback"
41
+ },
24
42
  getProjectInsights: {
25
43
  method: "GET",
26
44
  path: "/v1/projects/{project_id}/insights"
@@ -34,43 +52,15 @@
34
52
  path: "/v1/projects/{project_id}/usage"
35
53
  }
36
54
  },
37
- catalog: {
38
- listQueryCatalog: {
55
+ projects: {
56
+ listCacheRules: {
39
57
  method: "GET",
40
- path: "/v1/projects/{project_id}/databases/{database_id}/catalog"
58
+ path: "/v1/projects/{project_id}/databases/{database_id}/cache-rules"
41
59
  },
42
60
  updateCacheRule: {
43
61
  method: "PUT",
44
- path: "/v1/projects/{project_id}/databases/{database_id}/catalog/{query_hash}/cache-rule"
45
- }
46
- },
47
- databases: {
48
- listDatabases: {
49
- method: "GET",
50
- path: "/v1/projects/{project_id}/databases"
51
- },
52
- createDatabase: {
53
- method: "POST",
54
- path: "/v1/projects/{project_id}/databases"
55
- },
56
- getDatabase: {
57
- method: "GET",
58
- path: "/v1/projects/{project_id}/databases/{database_id}"
59
- },
60
- updateDatabase: {
61
- method: "PATCH",
62
- path: "/v1/projects/{project_id}/databases/{database_id}"
62
+ path: "/v1/projects/{project_id}/databases/{database_id}/cache-rules/{query_hash}"
63
63
  },
64
- deleteDatabase: {
65
- method: "DELETE",
66
- path: "/v1/projects/{project_id}/databases/{database_id}"
67
- },
68
- testDatabaseConnection: {
69
- method: "POST",
70
- path: "/v1/projects/{project_id}/databases/{database_id}/test-connection"
71
- }
72
- },
73
- domains: {
74
64
  listCustomDomains: {
75
65
  method: "GET",
76
66
  path: "/v1/projects/{project_id}/domains"
@@ -86,25 +76,7 @@
86
76
  verifyCustomDomain: {
87
77
  method: "POST",
88
78
  path: "/v1/projects/{project_id}/domains/{domain_id}/verify"
89
- }
90
- },
91
- health: {
92
- getHealth: {
93
- method: "GET",
94
- path: "/v1/health"
95
- }
96
- },
97
- onboarding: {
98
- getOnboardingProgress: {
99
- method: "GET",
100
- path: "/v1/organizations/{org_id}/onboarding"
101
79
  },
102
- updateOnboardingProgress: {
103
- method: "PATCH",
104
- path: "/v1/organizations/{org_id}/onboarding"
105
- }
106
- },
107
- projects: {
108
80
  listProjects: {
109
81
  method: "GET",
110
82
  path: "/v1/projects"
@@ -128,15 +100,7 @@
128
100
  getProjectMetrics: {
129
101
  method: "GET",
130
102
  path: "/v1/projects/{project_id}/metrics"
131
- }
132
- },
133
- regions: {
134
- listRegions: {
135
- method: "GET",
136
- path: "/v1/regions"
137
- }
138
- },
139
- replicas: {
103
+ },
140
104
  listReplicas: {
141
105
  method: "GET",
142
106
  path: "/v1/databases/{database_id}/replicas"
@@ -150,10 +114,40 @@
150
114
  path: "/v1/databases/{database_id}/replicas/{replica_id}"
151
115
  }
152
116
  },
153
- waitlist: {
154
- joinWaitlist: {
117
+ databases: {
118
+ listDatabases: {
119
+ method: "GET",
120
+ path: "/v1/projects/{project_id}/databases"
121
+ },
122
+ createDatabase: {
123
+ method: "POST",
124
+ path: "/v1/projects/{project_id}/databases"
125
+ },
126
+ getDatabase: {
127
+ method: "GET",
128
+ path: "/v1/projects/{project_id}/databases/{database_id}"
129
+ },
130
+ updateDatabase: {
131
+ method: "PATCH",
132
+ path: "/v1/projects/{project_id}/databases/{database_id}"
133
+ },
134
+ deleteDatabase: {
135
+ method: "DELETE",
136
+ path: "/v1/projects/{project_id}/databases/{database_id}"
137
+ },
138
+ testDatabaseConnection: {
155
139
  method: "POST",
156
- path: "/v1/waitlist"
140
+ path: "/v1/projects/{project_id}/databases/{database_id}/test-connection"
141
+ }
142
+ },
143
+ platform: {
144
+ getHealth: {
145
+ method: "GET",
146
+ path: "/v1/health"
147
+ },
148
+ listRegions: {
149
+ method: "GET",
150
+ path: "/v1/regions"
157
151
  }
158
152
  }
159
153
  };
@@ -178,14 +172,24 @@ const operationsByPath = {
178
172
  path: "/v1/organizations/{org_id}/plan",
179
173
  operationId: "getOrganizationPlan"
180
174
  },
181
- "GET /v1/projects/{project_id}/databases/{database_id}/catalog": {
175
+ "PUT /v1/organizations/{org_id}/spend-limit": {
176
+ method: "PUT",
177
+ path: "/v1/organizations/{org_id}/spend-limit",
178
+ operationId: "updateSpendLimit"
179
+ },
180
+ "POST /v1/organizations/{org_id}/cancellation-feedback": {
181
+ method: "POST",
182
+ path: "/v1/organizations/{org_id}/cancellation-feedback",
183
+ operationId: "submitCancellationFeedback"
184
+ },
185
+ "GET /v1/projects/{project_id}/databases/{database_id}/cache-rules": {
182
186
  method: "GET",
183
- path: "/v1/projects/{project_id}/databases/{database_id}/catalog",
184
- operationId: "listQueryCatalog"
187
+ path: "/v1/projects/{project_id}/databases/{database_id}/cache-rules",
188
+ operationId: "listCacheRules"
185
189
  },
186
- "PUT /v1/projects/{project_id}/databases/{database_id}/catalog/{query_hash}/cache-rule": {
190
+ "PUT /v1/projects/{project_id}/databases/{database_id}/cache-rules/{query_hash}": {
187
191
  method: "PUT",
188
- path: "/v1/projects/{project_id}/databases/{database_id}/catalog/{query_hash}/cache-rule",
192
+ path: "/v1/projects/{project_id}/databases/{database_id}/cache-rules/{query_hash}",
189
193
  operationId: "updateCacheRule"
190
194
  },
191
195
  "GET /v1/projects/{project_id}/databases": {
@@ -317,11 +321,6 @@ const operationsByPath = {
317
321
  method: "GET",
318
322
  path: "/v1/projects/{project_id}/usage",
319
323
  operationId: "getProjectUsage"
320
- },
321
- "POST /v1/waitlist": {
322
- method: "POST",
323
- path: "/v1/waitlist",
324
- operationId: "joinWaitlist"
325
324
  }
326
325
  };
327
326
 
@@ -347,7 +346,62 @@ class ApiError extends Error {
347
346
  if (typeof obj.message === "string") return obj.message;
348
347
  return undefined;
349
348
  }
349
+ const RETRYABLE_STATUS_CODES = new Set([
350
+ 408,
351
+ 429,
352
+ 502,
353
+ 503,
354
+ 504
355
+ ]);
356
+ const DEFAULT_RETRY = {
357
+ maxRetries: 5,
358
+ initialDelayMs: 500,
359
+ maxDelayMs: 30000,
360
+ idempotencyKeys: true
361
+ };
362
+ function resolveRetry(config) {
363
+ if (!config) return _({}, DEFAULT_RETRY, {
364
+ maxRetries: 0
365
+ });
366
+ return _({}, DEFAULT_RETRY, config);
367
+ }
368
+ /** Parse Retry-After header as milliseconds. Returns undefined if unparseable. */ function parseRetryAfter(response) {
369
+ const header = response.headers.get("Retry-After");
370
+ if (!header) return undefined;
371
+ // Integer seconds
372
+ const seconds = Number(header);
373
+ if (Number.isFinite(seconds) && seconds >= 0) {
374
+ return seconds * 1000;
375
+ }
376
+ // HTTP-date
377
+ const date = Date.parse(header);
378
+ if (Number.isFinite(date)) {
379
+ const ms = date - Date.now();
380
+ return ms > 0 ? ms : 0;
381
+ }
382
+ return undefined;
383
+ }
384
+ /** Compute exponential backoff with jitter. */ function backoff(attempt, initialDelayMs, maxDelayMs) {
385
+ const delay = Math.min(initialDelayMs * 2 ** attempt, maxDelayMs);
386
+ return delay * (0.5 + Math.random());
387
+ }
388
+ /** Generate a UUID v4 using crypto.randomUUID if available, otherwise fallback. */ function generateUUID() {
389
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
390
+ return crypto.randomUUID();
391
+ }
392
+ // Fallback for environments without crypto.randomUUID
393
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c)=>{
394
+ const r = Math.random() * 16 | 0;
395
+ const v = c === "x" ? r : r & 0x3 | 0x8;
396
+ return v.toString(16);
397
+ });
398
+ }
399
+ function isMutatingMethod(method) {
400
+ const upper = method.toUpperCase();
401
+ return upper === "POST" || upper === "PATCH";
402
+ }
350
403
  async function fetcher(options) {
404
+ var _options_fetchImpl;
351
405
  let url = options.path;
352
406
  if (options.pathParams) {
353
407
  for (const [key, value] of Object.entries(options.pathParams)){
@@ -369,29 +423,68 @@ async function fetcher(options) {
369
423
  if (options.body !== undefined) {
370
424
  headers["Content-Type"] = "application/json";
371
425
  }
372
- var _options_fetchImpl;
373
426
  const fetchFn = (_options_fetchImpl = options.fetchImpl) != null ? _options_fetchImpl : globalThis.fetch;
374
- const response = await fetchFn(fullUrl.toString(), {
375
- method: options.method,
376
- headers,
377
- body: options.body !== undefined ? JSON.stringify(options.body) : undefined
378
- });
379
- if (options.onResponse) {
380
- await options.onResponse(response);
381
- }
382
- if (!response.ok) {
383
- const errorBody = await response.json().catch(()=>null);
384
- throw new ApiError(response.status, response.statusText, errorBody);
427
+ const retry = resolveRetry(options.retry);
428
+ const urlStr = fullUrl.toString();
429
+ const fetchBody = options.body !== undefined ? JSON.stringify(options.body) : undefined;
430
+ // Generate idempotency key once, reused across all retry attempts.
431
+ const idempotencyKey = retry.maxRetries > 0 && retry.idempotencyKeys && isMutatingMethod(options.method) ? generateUUID() : undefined;
432
+ for(let attempt = 0; attempt <= retry.maxRetries; attempt++){
433
+ const reqHeaders = _({}, headers);
434
+ // Attach idempotency key on all attempts for mutating requests.
435
+ if (idempotencyKey) {
436
+ reqHeaders["Idempotency-Key"] = idempotencyKey;
437
+ }
438
+ try {
439
+ var _parseRetryAfter;
440
+ const response = await fetchFn(urlStr, {
441
+ method: options.method,
442
+ headers: reqHeaders,
443
+ body: fetchBody
444
+ });
445
+ if (options.onResponse) {
446
+ await options.onResponse(response);
447
+ }
448
+ if (response.ok) {
449
+ return parseResponseBody(response);
450
+ }
451
+ // Non-retryable error or last attempt — throw immediately.
452
+ if (!RETRYABLE_STATUS_CODES.has(response.status) || attempt === retry.maxRetries) {
453
+ const errorBody = await response.json().catch(()=>null);
454
+ throw new ApiError(response.status, response.statusText, errorBody);
455
+ }
456
+ // Retryable error — wait and retry.
457
+ const delay = (_parseRetryAfter = parseRetryAfter(response)) != null ? _parseRetryAfter : backoff(attempt, retry.initialDelayMs, retry.maxDelayMs);
458
+ await sleep(delay);
459
+ } catch (err) {
460
+ // If it's already an ApiError, rethrow (we threw it above).
461
+ if (err instanceof ApiError) throw err;
462
+ // Network error — retry if we have attempts left.
463
+ if (attempt === retry.maxRetries) throw err;
464
+ const delay = backoff(attempt, retry.initialDelayMs, retry.maxDelayMs);
465
+ await sleep(delay);
466
+ }
385
467
  }
468
+ // Unreachable — the loop either returns or throws.
469
+ throw new Error("fetcher: exhausted all retry attempts");
470
+ }
471
+ async function parseResponseBody(response) {
386
472
  if (response.status === 204) return undefined;
387
473
  const contentType = response.headers.get("content-type");
388
- if (contentType && contentType.includes("application/json")) {
474
+ if (contentType == null ? void 0 : contentType.includes("application/json")) {
389
475
  return response.json();
390
476
  }
391
477
  // Fallback: try JSON, return undefined if body is empty/non-JSON
392
478
  const text = await response.text();
393
479
  if (!text) return undefined;
394
- return JSON.parse(text);
480
+ try {
481
+ return JSON.parse(text);
482
+ } catch (unused) {
483
+ return undefined;
484
+ }
485
+ }
486
+ function sleep(ms) {
487
+ return new Promise((resolve)=>setTimeout(resolve, ms));
395
488
  }
396
489
 
397
490
  class PgBeamClient {
@@ -413,7 +506,8 @@ class PgBeamClient {
413
506
  baseUrl: this._baseUrl,
414
507
  token,
415
508
  fetchImpl: this._fetchImpl,
416
- onResponse: this._onResponse
509
+ onResponse: this._onResponse,
510
+ retry: this._retry
417
511
  });
418
512
  };
419
513
  }
@@ -432,7 +526,8 @@ class PgBeamClient {
432
526
  baseUrl: this._baseUrl,
433
527
  token,
434
528
  fetchImpl: this._fetchImpl,
435
- onResponse: this._onResponse
529
+ onResponse: this._onResponse,
530
+ retry: this._retry
436
531
  });
437
532
  };
438
533
  this._api = new Proxy({}, {
@@ -453,10 +548,16 @@ class PgBeamClient {
453
548
  return this._api;
454
549
  }
455
550
  constructor(options){
551
+ var _options_retry;
456
552
  this._baseUrl = options.baseUrl;
457
553
  this._tokenOrFn = options.token;
458
554
  this._fetchImpl = options.fetch;
459
555
  this._onResponse = options.onResponse;
556
+ this._retry = options.retry === false ? {
557
+ maxRetries: 0
558
+ } : (_options_retry = options.retry) != null ? _options_retry : {
559
+ maxRetries: 5
560
+ };
460
561
  }
461
562
  }
462
563