@gigadrive/sdk 0.1.2 → 0.2.0

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.mjs CHANGED
@@ -1,8 +1,2092 @@
1
- // src/test.ts
2
- var test = () => {
3
- console.log("asd");
1
+ // src/errors.ts
2
+ var GigadriveError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "GigadriveError";
6
+ }
7
+ };
8
+ var AuthenticationError = class extends GigadriveError {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "AuthenticationError";
12
+ }
13
+ };
14
+ var ApiError = class extends GigadriveError {
15
+ /** The HTTP status code of the failed response (e.g. 404, 500). */
16
+ status;
17
+ /** An optional machine-readable error code from the API response body. */
18
+ code;
19
+ constructor(message, status, code) {
20
+ super(message);
21
+ this.name = "ApiError";
22
+ this.status = status;
23
+ this.code = code;
24
+ }
25
+ };
26
+ var UploadError = class extends GigadriveError {
27
+ /** The underlying error that caused the upload to fail, if any. */
28
+ cause;
29
+ constructor(message, cause) {
30
+ super(message);
31
+ this.name = "UploadError";
32
+ this.cause = cause;
33
+ }
34
+ };
35
+ var UploadSessionExpiredError = class extends UploadError {
36
+ constructor(message = "The upload session expired before the upload completed. Start a new upload to retry.", cause) {
37
+ super(message, cause);
38
+ this.name = "UploadSessionExpiredError";
39
+ }
40
+ };
41
+
42
+ // src/auth/credential-provider.ts
43
+ var DEFAULT_SCOPE = "offline_access openid profile email";
44
+ var TOKEN_EXPIRY_MARGIN_SECONDS = 30;
45
+ var computeExpiresAt = (expiresInSeconds) => {
46
+ const ttl = expiresInSeconds ?? 3600;
47
+ const margin = Math.min(TOKEN_EXPIRY_MARGIN_SECONDS, ttl / 2);
48
+ return Date.now() + (ttl - margin) * 1e3;
49
+ };
50
+ var readEnv = (name) => {
51
+ if (typeof process === "undefined" || !process.env) return void 0;
52
+ return process.env[name];
53
+ };
54
+ var parseTokenResponse = (payload) => {
55
+ const data = payload;
56
+ if (typeof data.access_token !== "string" || data.access_token.length === 0) {
57
+ throw new AuthenticationError("Token endpoint returned no access_token");
58
+ }
59
+ return {
60
+ access_token: data.access_token,
61
+ refresh_token: typeof data.refresh_token === "string" ? data.refresh_token : void 0,
62
+ expires_in: typeof data.expires_in === "number" ? data.expires_in : void 0
63
+ };
64
+ };
65
+ var discoverOidc = async (issuerUrl, fetchFn) => {
66
+ const discoveryUrl = `${issuerUrl.replace(/\/$/, "")}/.well-known/openid-configuration`;
67
+ const response = await fetchFn(discoveryUrl);
68
+ if (!response.ok) {
69
+ throw new AuthenticationError(`OIDC discovery failed: ${response.status} ${response.statusText}`);
70
+ }
71
+ return await response.json();
72
+ };
73
+ var base64UrlEncode = (buffer) => {
74
+ let binary = "";
75
+ for (let i = 0; i < buffer.byteLength; i++) {
76
+ binary += String.fromCharCode(buffer[i]);
77
+ }
78
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
79
+ };
80
+ var generatePkce = async () => {
81
+ const randomBytes = new Uint8Array(64);
82
+ crypto.getRandomValues(randomBytes);
83
+ const codeVerifier = base64UrlEncode(randomBytes);
84
+ const encoder = new TextEncoder();
85
+ const data = encoder.encode(codeVerifier);
86
+ const digest = await crypto.subtle.digest("SHA-256", data);
87
+ const codeChallenge = base64UrlEncode(new Uint8Array(digest));
88
+ return { codeVerifier, codeChallenge };
89
+ };
90
+ var generateState = () => {
91
+ const bytes = new Uint8Array(16);
92
+ crypto.getRandomValues(bytes);
93
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
94
+ };
95
+ var OAuth2ClientCredentialProvider = class {
96
+ constructor(clientId, clientSecret, tokenUrl, fetchFn) {
97
+ this.clientId = clientId;
98
+ this.clientSecret = clientSecret;
99
+ this.tokenUrl = tokenUrl;
100
+ this.fetchFn = fetchFn;
101
+ }
102
+ clientId;
103
+ clientSecret;
104
+ tokenUrl;
105
+ fetchFn;
106
+ type = "oauth2-client-credentials";
107
+ async getToken() {
108
+ const credentials = btoa(`${this.clientId}:${this.clientSecret}`);
109
+ const response = await this.fetchFn(this.tokenUrl, {
110
+ method: "POST",
111
+ headers: {
112
+ "Content-Type": "application/x-www-form-urlencoded",
113
+ Authorization: `Basic ${credentials}`
114
+ },
115
+ body: new URLSearchParams({ grant_type: "client_credentials" }).toString()
116
+ });
117
+ if (!response.ok) {
118
+ const body = await response.text().catch(() => "");
119
+ throw new AuthenticationError(`OAuth2 token request failed (${response.status}): ${body}`);
120
+ }
121
+ const data = parseTokenResponse(await response.json());
122
+ return {
123
+ accessToken: data.access_token,
124
+ expiresAt: computeExpiresAt(data.expires_in)
125
+ };
126
+ }
127
+ };
128
+ var OAuth2AuthorizationCodeProvider = class {
129
+ constructor(clientId, issuerUrl, redirectUri, onAuthorizationUrl, fetchFn, scope = DEFAULT_SCOPE) {
130
+ this.clientId = clientId;
131
+ this.issuerUrl = issuerUrl;
132
+ this.redirectUri = redirectUri;
133
+ this.onAuthorizationUrl = onAuthorizationUrl;
134
+ this.fetchFn = fetchFn;
135
+ this.scope = scope;
136
+ }
137
+ clientId;
138
+ issuerUrl;
139
+ redirectUri;
140
+ onAuthorizationUrl;
141
+ fetchFn;
142
+ scope;
143
+ type = "oauth2-authorization-code";
144
+ currentRefreshToken = null;
145
+ async getToken() {
146
+ const oidc = await discoverOidc(this.issuerUrl, this.fetchFn);
147
+ if (!oidc.token_endpoint) {
148
+ throw new AuthenticationError("OIDC discovery returned no token endpoint");
149
+ }
150
+ if (this.currentRefreshToken) {
151
+ const refreshed = await this.tryRefresh(oidc.token_endpoint, this.currentRefreshToken);
152
+ if (refreshed) return refreshed;
153
+ this.currentRefreshToken = null;
154
+ }
155
+ if (!oidc.authorization_endpoint) {
156
+ throw new AuthenticationError("OIDC discovery returned incomplete endpoints");
157
+ }
158
+ const { codeVerifier, codeChallenge } = await generatePkce();
159
+ const state = generateState();
160
+ const authUrl = new URL(oidc.authorization_endpoint);
161
+ authUrl.searchParams.set("client_id", this.clientId);
162
+ authUrl.searchParams.set("redirect_uri", this.redirectUri);
163
+ authUrl.searchParams.set("response_type", "code");
164
+ authUrl.searchParams.set("scope", this.scope);
165
+ authUrl.searchParams.set("code_challenge", codeChallenge);
166
+ authUrl.searchParams.set("code_challenge_method", "S256");
167
+ authUrl.searchParams.set("state", state);
168
+ const result = await this.onAuthorizationUrl(authUrl.toString());
169
+ let code;
170
+ let returnedState = null;
171
+ try {
172
+ const callbackUrl = new URL(result);
173
+ code = callbackUrl.searchParams.get("code") ?? "";
174
+ returnedState = callbackUrl.searchParams.get("state");
175
+ const error = callbackUrl.searchParams.get("error");
176
+ if (error) {
177
+ const description = callbackUrl.searchParams.get("error_description") ?? error;
178
+ throw new AuthenticationError(`Authorization failed: ${description}`);
179
+ }
180
+ } catch (e) {
181
+ if (e instanceof AuthenticationError) throw e;
182
+ code = result;
183
+ }
184
+ if (!code) {
185
+ throw new AuthenticationError("No authorization code received");
186
+ }
187
+ if (returnedState !== null && returnedState !== state) {
188
+ throw new AuthenticationError("State mismatch \u2014 possible CSRF attack");
189
+ }
190
+ const response = await this.fetchFn(oidc.token_endpoint, {
191
+ method: "POST",
192
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
193
+ body: new URLSearchParams({
194
+ grant_type: "authorization_code",
195
+ client_id: this.clientId,
196
+ code,
197
+ redirect_uri: this.redirectUri,
198
+ code_verifier: codeVerifier
199
+ }).toString()
200
+ });
201
+ if (!response.ok) {
202
+ const body = await response.text().catch(() => "");
203
+ throw new AuthenticationError(`Token exchange failed (${response.status}): ${body}`);
204
+ }
205
+ const data = parseTokenResponse(await response.json());
206
+ this.currentRefreshToken = data.refresh_token ?? null;
207
+ return {
208
+ accessToken: data.access_token,
209
+ expiresAt: computeExpiresAt(data.expires_in),
210
+ refreshToken: data.refresh_token
211
+ };
212
+ }
213
+ /**
214
+ * Try to exchange the stored refresh token for a new access token. Returns the
215
+ * token on success, `null` when the refresh token is definitively rejected
216
+ * (a 4xx — caller should re-authenticate), and throws on transient failures.
217
+ */
218
+ async tryRefresh(tokenEndpoint, refreshToken) {
219
+ const response = await this.fetchFn(tokenEndpoint, {
220
+ method: "POST",
221
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
222
+ body: new URLSearchParams({
223
+ grant_type: "refresh_token",
224
+ client_id: this.clientId,
225
+ refresh_token: refreshToken
226
+ }).toString()
227
+ });
228
+ if (!response.ok) {
229
+ if (response.status >= 400 && response.status < 500) return null;
230
+ const body = await response.text().catch(() => "");
231
+ throw new AuthenticationError(`Token refresh failed (${response.status}): ${body}`);
232
+ }
233
+ const data = parseTokenResponse(await response.json());
234
+ if (data.refresh_token) {
235
+ this.currentRefreshToken = data.refresh_token;
236
+ }
237
+ return {
238
+ accessToken: data.access_token,
239
+ expiresAt: computeExpiresAt(data.expires_in),
240
+ refreshToken: data.refresh_token ?? this.currentRefreshToken ?? void 0
241
+ };
242
+ }
243
+ };
244
+ var OAuth2RefreshTokenProvider = class {
245
+ constructor(clientId, refreshToken, issuerUrl, fetchFn) {
246
+ this.clientId = clientId;
247
+ this.issuerUrl = issuerUrl;
248
+ this.fetchFn = fetchFn;
249
+ this.currentRefreshToken = refreshToken;
250
+ }
251
+ clientId;
252
+ issuerUrl;
253
+ fetchFn;
254
+ type = "oauth2-refresh-token";
255
+ currentRefreshToken;
256
+ async getToken() {
257
+ const oidc = await discoverOidc(this.issuerUrl, this.fetchFn);
258
+ if (!oidc.token_endpoint) {
259
+ throw new AuthenticationError("OIDC discovery returned no token endpoint");
260
+ }
261
+ const response = await this.fetchFn(oidc.token_endpoint, {
262
+ method: "POST",
263
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
264
+ body: new URLSearchParams({
265
+ grant_type: "refresh_token",
266
+ client_id: this.clientId,
267
+ refresh_token: this.currentRefreshToken
268
+ }).toString()
269
+ });
270
+ if (!response.ok) {
271
+ const body = await response.text().catch(() => "");
272
+ if (response.status === 400) {
273
+ throw new AuthenticationError("Refresh token is invalid or expired. Please re-authenticate.");
274
+ }
275
+ throw new AuthenticationError(`Token refresh failed (${response.status}): ${body}`);
276
+ }
277
+ const data = parseTokenResponse(await response.json());
278
+ if (data.refresh_token) {
279
+ this.currentRefreshToken = data.refresh_token;
280
+ }
281
+ return {
282
+ accessToken: data.access_token,
283
+ expiresAt: computeExpiresAt(data.expires_in),
284
+ refreshToken: data.refresh_token ?? this.currentRefreshToken
285
+ };
286
+ }
287
+ };
288
+ var BearerTokenProvider = class {
289
+ constructor(token) {
290
+ this.token = token;
291
+ }
292
+ token;
293
+ type = "bearer";
294
+ getToken() {
295
+ return Promise.resolve({ accessToken: this.token, expiresAt: null });
296
+ }
297
+ };
298
+ var DEFAULT_IDP_ISSUER_URL = "https://idp.gigadrive.de";
299
+ var DEFAULT_BASE_URL = "https://api.gigadrive.network";
300
+ var resolveCredentialProvider = (config) => {
301
+ const fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
302
+ const baseUrl = config.baseUrl ?? readEnv("GIGADRIVE_API_BASE_URL") ?? DEFAULT_BASE_URL;
303
+ const idpIssuerUrl = config.idpIssuerUrl ?? readEnv("GIGADRIVE_IDP_ISSUER_URL") ?? DEFAULT_IDP_ISSUER_URL;
304
+ const tokenUrl = `${baseUrl}/oauth2/token`;
305
+ if (config.bearerToken) {
306
+ return new BearerTokenProvider(config.bearerToken);
307
+ }
308
+ if (config.clientId && config.clientSecret) {
309
+ return new OAuth2ClientCredentialProvider(config.clientId, config.clientSecret, tokenUrl, fetchFn);
310
+ }
311
+ if (config.refreshToken && config.clientId) {
312
+ return new OAuth2RefreshTokenProvider(config.clientId, config.refreshToken, idpIssuerUrl, fetchFn);
313
+ }
314
+ if (config.onAuthorizationUrl && config.clientId) {
315
+ const redirectUri = config.redirectUri ?? "urn:ietf:wg:oauth:2.0:oob";
316
+ const scope = config.scopes && config.scopes.length > 0 ? config.scopes.join(" ") : DEFAULT_SCOPE;
317
+ return new OAuth2AuthorizationCodeProvider(
318
+ config.clientId,
319
+ idpIssuerUrl,
320
+ redirectUri,
321
+ config.onAuthorizationUrl,
322
+ fetchFn,
323
+ scope
324
+ );
325
+ }
326
+ const envBearerToken = readEnv("GIGADRIVE_BEARER_TOKEN");
327
+ if (envBearerToken) {
328
+ return new BearerTokenProvider(envBearerToken);
329
+ }
330
+ const envClientId = readEnv("GIGADRIVE_CLIENT_ID");
331
+ const envClientSecret = readEnv("GIGADRIVE_CLIENT_SECRET");
332
+ if (envClientId && envClientSecret) {
333
+ return new OAuth2ClientCredentialProvider(envClientId, envClientSecret, tokenUrl, fetchFn);
334
+ }
335
+ const envRefreshToken = readEnv("GIGADRIVE_REFRESH_TOKEN");
336
+ if (envRefreshToken && envClientId) {
337
+ return new OAuth2RefreshTokenProvider(envClientId, envRefreshToken, idpIssuerUrl, fetchFn);
338
+ }
339
+ throw new AuthenticationError(
340
+ "No credentials provided. Set one of:\n - GIGADRIVE_BEARER_TOKEN\n - GIGADRIVE_CLIENT_ID + GIGADRIVE_CLIENT_SECRET\n - GIGADRIVE_REFRESH_TOKEN + GIGADRIVE_CLIENT_ID\nOr pass credentials directly to the GigadriveClient constructor."
341
+ );
342
+ };
343
+
344
+ // src/auth/token-manager.ts
345
+ var TokenManager = class {
346
+ constructor(provider) {
347
+ this.provider = provider;
348
+ }
349
+ provider;
350
+ cachedToken = null;
351
+ expiresAt = null;
352
+ pendingRefresh = null;
353
+ /**
354
+ * Returns a valid access token. Uses the cached token if still valid,
355
+ * otherwise fetches a new one from the credential provider.
356
+ *
357
+ * Concurrent calls are deduplicated — only one refresh runs at a time.
358
+ */
359
+ async getToken() {
360
+ if (this.cachedToken && (this.expiresAt === null || Date.now() < this.expiresAt)) {
361
+ return this.cachedToken;
362
+ }
363
+ if (this.pendingRefresh) {
364
+ return this.pendingRefresh;
365
+ }
366
+ this.pendingRefresh = this.refresh();
367
+ try {
368
+ return await this.pendingRefresh;
369
+ } finally {
370
+ this.pendingRefresh = null;
371
+ }
372
+ }
373
+ /**
374
+ * Invalidates the cached token. The next `getToken()` call will trigger a refresh.
375
+ */
376
+ invalidate() {
377
+ this.cachedToken = null;
378
+ this.expiresAt = null;
379
+ }
380
+ async refresh() {
381
+ const result = await this.provider.getToken();
382
+ this.cachedToken = result.accessToken;
383
+ this.expiresAt = result.expiresAt;
384
+ return result.accessToken;
385
+ }
386
+ };
387
+
388
+ // src/http-client.ts
389
+ var isRawBody = (body) => typeof body === "string" || body instanceof ArrayBuffer || typeof Uint8Array !== "undefined" && body instanceof Uint8Array || typeof Blob !== "undefined" && body instanceof Blob || typeof FormData !== "undefined" && body instanceof FormData || typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
390
+ var hasHeader = (headers, name) => Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
391
+ var HttpClient = class {
392
+ constructor(baseUrl, tokenManager, fetchFn) {
393
+ this.baseUrl = baseUrl;
394
+ this.tokenManager = tokenManager;
395
+ this.fetchFn = fetchFn;
396
+ }
397
+ baseUrl;
398
+ tokenManager;
399
+ fetchFn;
400
+ /** @internal */
401
+ async get(path, options) {
402
+ return this.request("GET", path, options);
403
+ }
404
+ /** @internal */
405
+ async post(path, body, options) {
406
+ return this.request("POST", path, { body, ...options });
407
+ }
408
+ /** @internal */
409
+ async put(path, body, options) {
410
+ return this.request("PUT", path, { body, ...options });
411
+ }
412
+ /** @internal */
413
+ async patch(path, body, options) {
414
+ return this.request("PATCH", path, { body, ...options });
415
+ }
416
+ /** @internal */
417
+ async delete(path, options) {
418
+ return this.request("DELETE", path, options);
419
+ }
420
+ /** @internal */
421
+ async postRaw(path, body, headers) {
422
+ return this.request("POST", path, { body, headers });
423
+ }
424
+ /**
425
+ * Make an authenticated request and return the raw `Response` without parsing
426
+ * the body. Used for streamed (SSE) and binary responses.
427
+ *
428
+ * @throws {@link ApiError} if the response status is not OK.
429
+ * @internal
430
+ */
431
+ async requestStream(method, path, options) {
432
+ const response = await this.fetchWithAuth(method, path, options);
433
+ if (!response.ok) {
434
+ throw await this.toApiError(response);
435
+ }
436
+ return response;
437
+ }
438
+ /**
439
+ * Make a raw fetch request to an arbitrary URL (e.g. presigned upload URLs
440
+ * or resumable upload endpoints). No auth header is injected and the base URL
441
+ * is not prepended.
442
+ *
443
+ * @param url - The full URL to fetch.
444
+ * @param init - Standard `RequestInit` options.
445
+ * @returns The raw `Response` object.
446
+ * @throws {@link ApiError} if the response status is not OK.
447
+ *
448
+ * @internal
449
+ */
450
+ async fetchRaw(url, init) {
451
+ const response = await this.fetchFn(url, init);
452
+ if (!response.ok) {
453
+ throw new ApiError(response.statusText, response.status);
454
+ }
455
+ return response;
456
+ }
457
+ async request(method, path, options) {
458
+ const response = await this.fetchWithAuth(method, path, options);
459
+ return this.handleResponse(response);
460
+ }
461
+ /** Perform the request with auth injection and a single 401 refresh-retry. */
462
+ async fetchWithAuth(method, path, options) {
463
+ const url = this.buildUrl(path, options?.query);
464
+ const token = await this.tokenManager.getToken();
465
+ const headers = {
466
+ Authorization: `Bearer ${token}`,
467
+ ...options?.headers
468
+ };
469
+ let requestBody = null;
470
+ if (options?.body !== void 0) {
471
+ if (isRawBody(options.body)) {
472
+ requestBody = options.body;
473
+ } else {
474
+ requestBody = JSON.stringify(options.body);
475
+ if (!hasHeader(headers, "Content-Type")) {
476
+ headers["Content-Type"] = "application/json";
477
+ }
478
+ }
479
+ }
480
+ const signal = options?.signal;
481
+ const response = await this.fetchFn(url, { method, headers, body: requestBody, signal });
482
+ const bodyIsOneShot = typeof ReadableStream !== "undefined" && requestBody instanceof ReadableStream;
483
+ if (response.status === 401 && !bodyIsOneShot) {
484
+ this.tokenManager.invalidate();
485
+ const retryToken = await this.tokenManager.getToken();
486
+ headers.Authorization = `Bearer ${retryToken}`;
487
+ const retryResponse = await this.fetchFn(url, { method, headers, body: requestBody, signal });
488
+ if (retryResponse.status === 401) {
489
+ throw new AuthenticationError("Authentication failed after token refresh");
490
+ }
491
+ return retryResponse;
492
+ }
493
+ return response;
494
+ }
495
+ async handleResponse(response) {
496
+ if (response.ok) {
497
+ if (response.status === 204) {
498
+ return void 0;
499
+ }
500
+ const text = await response.text();
501
+ if (!text) {
502
+ return void 0;
503
+ }
504
+ return JSON.parse(text);
505
+ }
506
+ throw await this.toApiError(response);
507
+ }
508
+ async toApiError(response) {
509
+ let message;
510
+ let code;
511
+ try {
512
+ const body = await response.json();
513
+ if (typeof body.error === "string") {
514
+ message = body.error;
515
+ } else if (body.error && typeof body.error === "object") {
516
+ message = body.error.message ?? response.statusText;
517
+ code = body.error.code;
518
+ } else {
519
+ message = response.statusText;
520
+ }
521
+ } catch {
522
+ message = response.statusText;
523
+ }
524
+ return new ApiError(message, response.status, code);
525
+ }
526
+ buildUrl(path, query) {
527
+ const url = `${this.baseUrl}${path}`;
528
+ if (!query) return url;
529
+ const params = new URLSearchParams();
530
+ for (const [key, value] of Object.entries(query)) {
531
+ if (value !== void 0) {
532
+ params.set(key, String(value));
533
+ }
534
+ }
535
+ const qs = params.toString();
536
+ return qs ? `${url}?${qs}` : url;
537
+ }
538
+ };
539
+
540
+ // src/streaming.ts
541
+ var extractData = (rawEvent) => {
542
+ const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).replace(/^ /, ""));
543
+ if (dataLines.length === 0) return null;
544
+ const joined = dataLines.join("\n");
545
+ return joined.trim().length === 0 ? null : joined;
546
+ };
547
+ var parseEvent = (data) => {
548
+ try {
549
+ return JSON.parse(data);
550
+ } catch (error) {
551
+ const message = error instanceof Error ? error.message : String(error);
552
+ throw new Error(`Failed to parse streamed SSE data as JSON: ${message}`);
553
+ }
554
+ };
555
+ async function* parseSSEStream(response) {
556
+ const body = response.body;
557
+ if (!body) {
558
+ throw new Error("The response has no readable body to stream.");
559
+ }
560
+ const reader = body.getReader();
561
+ const decoder = new TextDecoder();
562
+ let buffer = "";
563
+ const drain = function* (flush) {
564
+ let separator = buffer.indexOf("\n\n");
565
+ while (separator !== -1) {
566
+ const rawEvent = buffer.slice(0, separator);
567
+ buffer = buffer.slice(separator + 2);
568
+ const data = extractData(rawEvent);
569
+ if (data !== null && data !== "[DONE]") {
570
+ yield parseEvent(data);
571
+ }
572
+ separator = buffer.indexOf("\n\n");
573
+ }
574
+ if (flush) {
575
+ const data = extractData(buffer);
576
+ buffer = "";
577
+ if (data !== null && data !== "[DONE]") {
578
+ yield parseEvent(data);
579
+ }
580
+ }
581
+ };
582
+ try {
583
+ for (; ; ) {
584
+ const { done, value } = await reader.read();
585
+ if (done) break;
586
+ buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n/g, "\n");
587
+ yield* drain(false);
588
+ }
589
+ yield* drain(true);
590
+ } finally {
591
+ await reader.cancel().catch(() => void 0);
592
+ }
593
+ }
594
+
595
+ // src/resources/base-resource.ts
596
+ var BaseResource = class {
597
+ constructor(httpClient) {
598
+ this.httpClient = httpClient;
599
+ }
600
+ httpClient;
601
+ };
602
+
603
+ // src/resources/ai-gateway/index.ts
604
+ var BASE = "/ai/v1";
605
+ var toBlob = (file) => {
606
+ if (file instanceof Uint8Array) return new Blob([file]);
607
+ if (typeof Blob !== "undefined" && file instanceof Blob) return file;
608
+ return new Blob([new Uint8Array(file)]);
609
+ };
610
+ var AiGatewayAudioResource = class extends BaseResource {
611
+ /**
612
+ * Synthesize speech from text. Returns the raw audio bytes.
613
+ *
614
+ * @returns The audio content as an `ArrayBuffer` (format per `response_format`).
615
+ */
616
+ async speech(data, options) {
617
+ const response = await this.httpClient.requestStream("POST", `${BASE}/audio/speech`, {
618
+ body: data,
619
+ headers: options?.headers
620
+ });
621
+ return response.arrayBuffer();
622
+ }
623
+ /**
624
+ * Transcribe an audio file to text. The file is sent as multipart form data.
625
+ */
626
+ async transcriptions(data, options) {
627
+ const form = new FormData();
628
+ const { file, filename, ...rest } = data;
629
+ form.set("file", toBlob(file), filename ?? "audio");
630
+ for (const [key, value] of Object.entries(rest)) {
631
+ if (value === void 0 || value === null) continue;
632
+ form.set(key, typeof value === "object" ? JSON.stringify(value) : String(value));
633
+ }
634
+ return this.httpClient.postRaw(`${BASE}/audio/transcriptions`, form, options?.headers);
635
+ }
636
+ };
637
+ var AiGatewayVideosResource = class extends BaseResource {
638
+ /** Generate a video from a prompt. */
639
+ async generations(data, options) {
640
+ return this.httpClient.post(`${BASE}/videos`, data, { headers: options?.headers });
641
+ }
642
+ /** List the video models available through the gateway. */
643
+ async listModels() {
644
+ return this.httpClient.get(`${BASE}/videos/models`);
645
+ }
646
+ };
647
+ var AiGatewayResource = class extends BaseResource {
648
+ /** Text-to-speech and speech-to-text. */
649
+ audio;
650
+ /** Video generation. */
651
+ videos;
652
+ constructor(httpClient) {
653
+ super(httpClient);
654
+ this.audio = new AiGatewayAudioResource(httpClient);
655
+ this.videos = new AiGatewayVideosResource(httpClient);
656
+ }
657
+ /**
658
+ * Create a chat completion using the OpenAI-compatible endpoint.
659
+ *
660
+ * @param data - The chat completion request (model, messages, options, provider routing).
661
+ * @param options - Custom headers (e.g. `X-Gigadrive-Application-Id`).
662
+ */
663
+ async chatCompletions(data, options) {
664
+ return this.httpClient.post(`${BASE}/chat/completions`, { ...data, stream: false }, { headers: options?.headers });
665
+ }
666
+ /**
667
+ * Like {@link chatCompletions}, but also returns the raw response and the
668
+ * gateway request-id / cost headers.
669
+ */
670
+ async chatCompletionsWithResponse(data, options) {
671
+ const response = await this.httpClient.requestStream("POST", `${BASE}/chat/completions`, {
672
+ body: { ...data, stream: false },
673
+ headers: options?.headers
674
+ });
675
+ const body = await response.json();
676
+ const cost = response.headers.get("X-Gigadrive-Cost-Micros");
677
+ const costMicros = cost !== null ? Number(cost) : NaN;
678
+ return {
679
+ data: body,
680
+ response,
681
+ requestId: response.headers.get("X-Gigadrive-Request-Id") ?? void 0,
682
+ costMicros: Number.isFinite(costMicros) ? costMicros : void 0
683
+ };
684
+ }
685
+ /**
686
+ * Stream a chat completion as Server-Sent Events. Yields each chunk as it
687
+ * arrives and ends when the gateway closes the stream.
688
+ */
689
+ async *chatCompletionsStream(data, options) {
690
+ const response = await this.httpClient.requestStream("POST", `${BASE}/chat/completions`, {
691
+ body: { ...data, stream: true },
692
+ headers: options?.headers
693
+ });
694
+ yield* parseSSEStream(response);
695
+ }
696
+ /**
697
+ * Create a response using the Open Responses-compatible endpoint.
698
+ */
699
+ async responses(data, options) {
700
+ return this.httpClient.post(`${BASE}/responses`, { ...data, stream: false }, { headers: options?.headers });
701
+ }
702
+ /**
703
+ * Stream a response using the Open Responses-compatible endpoint.
704
+ */
705
+ async *responsesStream(data, options) {
706
+ const response = await this.httpClient.requestStream("POST", `${BASE}/responses`, {
707
+ body: { ...data, stream: true },
708
+ headers: options?.headers
709
+ });
710
+ yield* parseSSEStream(response);
711
+ }
712
+ /**
713
+ * List all AI models available through the gateway.
714
+ */
715
+ async listModels() {
716
+ return this.httpClient.get(`${BASE}/models`);
717
+ }
718
+ /**
719
+ * Get details for a specific AI model by its ID.
720
+ *
721
+ * @param modelId - The model identifier (e.g. `"openai/gpt-4o"`).
722
+ */
723
+ async getModel(modelId) {
724
+ return this.httpClient.get(`${BASE}/models/${encodeURIComponent(modelId)}`);
725
+ }
726
+ };
727
+
728
+ // src/resources/application-env-vars.ts
729
+ var ApplicationEnvVarsResource = class extends BaseResource {
730
+ /**
731
+ * List all environment variables for an application.
732
+ *
733
+ * @param applicationId - The application ID (UUID).
734
+ * @returns A paginated list of environment variables.
735
+ *
736
+ * @example
737
+ * ```ts
738
+ * const { items } = await client.applications.envVars.list('app-id');
739
+ * for (const v of items) {
740
+ * console.log(`${v.key}=${v.sensitive ? '***' : v.value}`);
741
+ * }
742
+ * ```
743
+ */
744
+ async list(applicationId, query) {
745
+ return this.httpClient.get(`/applications/${applicationId}/env-vars`, {
746
+ query
747
+ });
748
+ }
749
+ /**
750
+ * Create a new environment variable for an application.
751
+ *
752
+ * @param applicationId - The application ID (UUID).
753
+ * @param data - The variable key, value, and optional settings.
754
+ * @returns The newly created environment variable.
755
+ *
756
+ * @example
757
+ * ```ts
758
+ * const envVar = await client.applications.envVars.create('app-id', {
759
+ * key: 'DATABASE_URL',
760
+ * value: 'postgres://...',
761
+ * sensitive: true,
762
+ * });
763
+ * ```
764
+ */
765
+ async create(applicationId, data) {
766
+ return this.httpClient.post(`/applications/${applicationId}/env-vars`, data);
767
+ }
768
+ /**
769
+ * Update an existing environment variable. Only the fields you provide
770
+ * will be changed; omitted fields are left unchanged.
771
+ *
772
+ * @param applicationId - The application ID (UUID).
773
+ * @param envVarId - The environment variable ID (UUID).
774
+ * @param data - The fields to update.
775
+ * @returns The updated environment variable.
776
+ *
777
+ * @example
778
+ * ```ts
779
+ * await client.applications.envVars.update('app-id', 'var-id', {
780
+ * value: 'postgres://new-host/db',
781
+ * });
782
+ * ```
783
+ */
784
+ async update(applicationId, envVarId, data) {
785
+ return this.httpClient.patch(`/applications/${applicationId}/env-vars/${envVarId}`, data);
786
+ }
787
+ /**
788
+ * Permanently delete an environment variable.
789
+ *
790
+ * @param applicationId - The application ID (UUID).
791
+ * @param envVarId - The environment variable ID (UUID).
792
+ *
793
+ * @example
794
+ * ```ts
795
+ * await client.applications.envVars.delete('app-id', 'var-id');
796
+ * ```
797
+ */
798
+ async delete(applicationId, envVarId) {
799
+ return this.httpClient.delete(`/applications/${applicationId}/env-vars/${envVarId}`);
800
+ }
801
+ };
802
+
803
+ // src/resources/application-requests.ts
804
+ var ApplicationRequestsResource = class extends BaseResource {
805
+ /**
806
+ * List requests for an application, with rich filtering and cursor pagination.
807
+ *
808
+ * @param applicationId - The application ID (UUID).
809
+ * @param query - Optional filters and pagination.
810
+ * @returns A page of request summaries.
811
+ */
812
+ async list(applicationId, query) {
813
+ return this.httpClient.get(`/applications/${applicationId}/requests`, {
814
+ query
815
+ });
816
+ }
817
+ /**
818
+ * Get a single request by ID, including sanitized request/response headers.
819
+ *
820
+ * @param applicationId - The application ID (UUID).
821
+ * @param requestId - The request record ID (UUID).
822
+ * @returns The full request record.
823
+ */
824
+ async get(applicationId, requestId) {
825
+ return this.httpClient.get(`/applications/${applicationId}/requests/${requestId}`);
826
+ }
827
+ };
828
+
829
+ // src/upload/checksum.ts
830
+ var toHex = (buffer) => {
831
+ const bytes = new Uint8Array(buffer);
832
+ let hex = "";
833
+ for (let i = 0; i < bytes.length; i++) {
834
+ hex += bytes[i].toString(16).padStart(2, "0");
835
+ }
836
+ return hex;
837
+ };
838
+ var subtleDigest = async (algorithm, bytes) => toHex(await crypto.subtle.digest(algorithm, bytes));
839
+ var importNodeCrypto = async () => {
840
+ if (typeof process === "undefined" || !process.versions?.node) return null;
841
+ try {
842
+ return await import("crypto");
843
+ } catch {
844
+ return null;
845
+ }
846
+ };
847
+ var md5Hex = async (bytes) => {
848
+ const nodeCrypto = await importNodeCrypto();
849
+ if (!nodeCrypto) {
850
+ throw new Error("MD5 checksums are only supported in Node.js. Provide checksumMd5 explicitly in the browser.");
851
+ }
852
+ return nodeCrypto.createHash("md5").update(bytes).digest("hex");
853
+ };
854
+ var computeChecksums = async (bytes, request = {}) => {
855
+ const checksums = { sha256: await subtleDigest("SHA-256", bytes) };
856
+ if (request.sha1) checksums.sha1 = await subtleDigest("SHA-1", bytes);
857
+ if (request.md5) checksums.md5 = await md5Hex(bytes);
858
+ return checksums;
859
+ };
860
+ var hashNodeFile = async (path, request = {}) => {
861
+ const [fs, nodeCrypto] = await Promise.all([import("fs"), import("crypto")]);
862
+ const sha256 = nodeCrypto.createHash("sha256");
863
+ const sha1 = request.sha1 ? nodeCrypto.createHash("sha1") : null;
864
+ const md5 = request.md5 ? nodeCrypto.createHash("md5") : null;
865
+ await new Promise((resolve, reject) => {
866
+ const stream = fs.createReadStream(path);
867
+ stream.on("data", (chunk) => {
868
+ sha256.update(chunk);
869
+ sha1?.update(chunk);
870
+ md5?.update(chunk);
871
+ });
872
+ stream.on("end", () => resolve());
873
+ stream.on("error", reject);
874
+ });
875
+ const checksums = { sha256: sha256.digest("hex") };
876
+ if (sha1) checksums.sha1 = sha1.digest("hex");
877
+ if (md5) checksums.md5 = md5.digest("hex");
878
+ return checksums;
879
+ };
880
+
881
+ // src/upload/content-type.ts
882
+ var MIME_TYPES = {
883
+ // Text & code
884
+ txt: "text/plain",
885
+ csv: "text/csv",
886
+ html: "text/html",
887
+ htm: "text/html",
888
+ css: "text/css",
889
+ js: "text/javascript",
890
+ mjs: "text/javascript",
891
+ json: "application/json",
892
+ xml: "application/xml",
893
+ md: "text/markdown",
894
+ // Images
895
+ png: "image/png",
896
+ jpg: "image/jpeg",
897
+ jpeg: "image/jpeg",
898
+ gif: "image/gif",
899
+ webp: "image/webp",
900
+ svg: "image/svg+xml",
901
+ avif: "image/avif",
902
+ ico: "image/x-icon",
903
+ bmp: "image/bmp",
904
+ tiff: "image/tiff",
905
+ heic: "image/heic",
906
+ // Audio
907
+ mp3: "audio/mpeg",
908
+ wav: "audio/wav",
909
+ ogg: "audio/ogg",
910
+ oga: "audio/ogg",
911
+ flac: "audio/flac",
912
+ aac: "audio/aac",
913
+ m4a: "audio/mp4",
914
+ // Video
915
+ mp4: "video/mp4",
916
+ webm: "video/webm",
917
+ mov: "video/quicktime",
918
+ avi: "video/x-msvideo",
919
+ mkv: "video/x-matroska",
920
+ // Documents
921
+ pdf: "application/pdf",
922
+ doc: "application/msword",
923
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
924
+ xls: "application/vnd.ms-excel",
925
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
926
+ ppt: "application/vnd.ms-powerpoint",
927
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
928
+ // Archives & binaries
929
+ zip: "application/zip",
930
+ gz: "application/gzip",
931
+ tar: "application/x-tar",
932
+ br: "application/x-brotli",
933
+ wasm: "application/wasm",
934
+ // Fonts
935
+ woff: "font/woff",
936
+ woff2: "font/woff2",
937
+ ttf: "font/ttf",
938
+ otf: "font/otf"
939
+ };
940
+ var inferContentType = (nameOrKey) => {
941
+ const lastDot = nameOrKey.lastIndexOf(".");
942
+ if (lastDot === -1 || lastDot === nameOrKey.length - 1) return void 0;
943
+ const ext = nameOrKey.slice(lastDot + 1).toLowerCase();
944
+ return MIME_TYPES[ext];
945
+ };
946
+
947
+ // src/upload/source.ts
948
+ var isNode = () => typeof process !== "undefined" && !!process.versions?.node;
949
+ var toBytes = async (data) => {
950
+ if (typeof Blob !== "undefined" && data instanceof Blob) return new Uint8Array(await data.arrayBuffer());
951
+ if (data instanceof Uint8Array) return data;
952
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
953
+ throw new Error("Unsupported upload data type. Pass a File, Blob, Buffer, Uint8Array, or ArrayBuffer.");
954
+ };
955
+ var sizeOf = (data) => {
956
+ if (typeof Blob !== "undefined" && data instanceof Blob) return data.size;
957
+ if (data instanceof Uint8Array) return data.byteLength;
958
+ if (data instanceof ArrayBuffer) return data.byteLength;
959
+ throw new Error("Cannot determine upload size. Pass a File, Blob, Buffer, Uint8Array, or ArrayBuffer.");
960
+ };
961
+ var toTusFile = (data) => {
962
+ if (data instanceof Uint8Array) return isNode() ? data : new Blob([data]);
963
+ if (typeof Blob !== "undefined" && data instanceof Blob) return data;
964
+ const bytes = new Uint8Array(data);
965
+ return isNode() ? bytes : new Blob([bytes]);
966
+ };
967
+ var buildChecksums = (input, sha256) => {
968
+ const checksums = { sha256 };
969
+ if (input.checksumSha1) checksums.sha1 = input.checksumSha1;
970
+ if (input.checksumMd5) checksums.md5 = input.checksumMd5;
971
+ return checksums;
972
+ };
973
+ var resolveUploadSource = async (input, options = {}) => {
974
+ const sourceCount = [input.data !== void 0, input.path !== void 0, input.stream !== void 0].filter(
975
+ Boolean
976
+ ).length;
977
+ if (sourceCount > 1) {
978
+ throw new Error("Provide only one upload source: data, path, or stream.");
979
+ }
980
+ const hash = options.hash !== false;
981
+ const contentType = input.contentType ?? inferContentType(input.key);
982
+ if (input.data !== void 0) {
983
+ const size = input.contentLength ?? sizeOf(input.data);
984
+ const sha256 = input.checksumSha256 ?? (hash ? (await computeChecksums(await toBytes(input.data))).sha256 : "");
985
+ return {
986
+ tusFile: toTusFile(input.data),
987
+ size,
988
+ contentType,
989
+ checksums: buildChecksums(input, sha256),
990
+ requiresFiniteChunkSize: false
991
+ };
992
+ }
993
+ if (input.path !== void 0) {
994
+ if (!isNode()) throw new Error("Uploading from a file path is only supported in Node.js.");
995
+ const fs = await import("fs");
996
+ const size = input.contentLength ?? fs.statSync(input.path).size;
997
+ const sha256 = input.checksumSha256 ?? (hash ? (await hashNodeFile(input.path)).sha256 : "");
998
+ const tusFile = fs.createReadStream(input.path);
999
+ return { tusFile, size, contentType, checksums: buildChecksums(input, sha256), requiresFiniteChunkSize: true };
1000
+ }
1001
+ if (input.stream !== void 0) {
1002
+ if (input.contentLength === void 0) {
1003
+ throw new Error("Uploading from a stream requires contentLength to be provided.");
1004
+ }
1005
+ if (hash && !input.checksumSha256) {
1006
+ throw new Error("Uploading from a stream requires checksumSha256 to be provided.");
1007
+ }
1008
+ return {
1009
+ tusFile: input.stream,
1010
+ size: input.contentLength,
1011
+ contentType,
1012
+ checksums: buildChecksums(input, input.checksumSha256 ?? ""),
1013
+ requiresFiniteChunkSize: true
1014
+ };
1015
+ }
1016
+ throw new Error("No upload source provided. Pass one of: data, path, or stream.");
1017
+ };
1018
+
1019
+ // src/upload/transport.ts
1020
+ import * as tus from "tus-js-client";
1021
+ var DEFAULT_STREAM_CHUNK_SIZE = 50 * 1024 * 1024;
1022
+ var DEFAULT_RETRY_DELAYS = [0, 1e3, 3e3, 5e3];
1023
+ var createAbortError = () => {
1024
+ const error = new Error("The upload was aborted.");
1025
+ error.name = "AbortError";
1026
+ return error;
1027
+ };
1028
+ var tusUploadTransport = (params) => new Promise((resolve, reject) => {
1029
+ if (params.signal?.aborted) {
1030
+ reject(createAbortError());
1031
+ return;
1032
+ }
1033
+ let settled = false;
1034
+ const cleanup = () => {
1035
+ if (params.signal) params.signal.removeEventListener("abort", onAbort);
1036
+ };
1037
+ const finish = (fn) => {
1038
+ if (settled) return;
1039
+ settled = true;
1040
+ cleanup();
1041
+ fn();
1042
+ };
1043
+ const upload = new tus.Upload(params.data, {
1044
+ uploadUrl: params.uploadUrl,
1045
+ uploadSize: params.uploadSize,
1046
+ headers: params.headers,
1047
+ chunkSize: params.chunkSize ?? Infinity,
1048
+ retryDelays: params.retryDelays === void 0 ? DEFAULT_RETRY_DELAYS : params.retryDelays,
1049
+ storeFingerprintForResuming: params.resume ?? false,
1050
+ removeFingerprintOnSuccess: true,
1051
+ ...params.urlStorage ? { urlStorage: params.urlStorage } : {},
1052
+ onProgress: params.onProgress ?? null,
1053
+ onError: (error) => finish(() => reject(error)),
1054
+ onSuccess: () => finish(() => resolve())
1055
+ });
1056
+ const onAbort = () => {
1057
+ void upload.abort();
1058
+ finish(() => reject(createAbortError()));
1059
+ };
1060
+ if (params.signal) params.signal.addEventListener("abort", onAbort, { once: true });
1061
+ upload.start();
1062
+ });
1063
+ var runResolvedUpload = (transport, uploadUrl, resolved, options = {}, headers = { "Tus-Resumable": "1.0.0" }) => transport({
1064
+ data: resolved.tusFile,
1065
+ uploadUrl,
1066
+ uploadSize: resolved.size,
1067
+ headers,
1068
+ chunkSize: options.chunkSize ?? (resolved.requiresFiniteChunkSize ? DEFAULT_STREAM_CHUNK_SIZE : void 0),
1069
+ retryDelays: options.retryDelays,
1070
+ onProgress: options.onProgress,
1071
+ signal: options.signal,
1072
+ resume: options.resume,
1073
+ urlStorage: options.urlStorage
1074
+ });
1075
+ var tusStatus = (error) => {
1076
+ const response = error?.originalResponse;
1077
+ return response ? response.getStatus() : void 0;
1078
+ };
1079
+ var toUploadError = (error) => {
1080
+ if (error instanceof Error && error.name === "AbortError") throw error;
1081
+ const status = tusStatus(error);
1082
+ if (status === 401 || status === 403 || status === 410) {
1083
+ throw new UploadSessionExpiredError(void 0, error);
1084
+ }
1085
+ throw new UploadError(error instanceof Error ? error.message : "The upload failed.", error);
4
1086
  };
5
1087
 
6
- // src/index.ts
7
- test();
1088
+ // src/resources/storage-buckets.ts
1089
+ var StorageBucketsResource = class extends BaseResource {
1090
+ /**
1091
+ * List all storage buckets for an application.
1092
+ *
1093
+ * @param applicationId - The application ID (UUID).
1094
+ * @param query - Optional pagination parameters.
1095
+ * @returns A paginated list of storage buckets.
1096
+ *
1097
+ * @example
1098
+ * ```ts
1099
+ * const { items, total } = await client.applications.storage.buckets.list('app-id');
1100
+ * console.log(`${total} buckets found`);
1101
+ * ```
1102
+ */
1103
+ async list(applicationId, query) {
1104
+ return this.httpClient.get(`/applications/${applicationId}/storage/buckets`, {
1105
+ query
1106
+ });
1107
+ }
1108
+ /**
1109
+ * Create a new storage bucket.
1110
+ *
1111
+ * @param applicationId - The application ID (UUID).
1112
+ * @param data - Bucket name, environment, and optional slug/visibility.
1113
+ * @returns The newly created bucket.
1114
+ *
1115
+ * @example
1116
+ * ```ts
1117
+ * const bucket = await client.applications.storage.buckets.create('app-id', {
1118
+ * name: 'Assets',
1119
+ * environmentId: 'env-id',
1120
+ * visibility: 'public',
1121
+ * });
1122
+ * console.log(`Bucket CDN: https://${bucket.cdnHostname}`);
1123
+ * ```
1124
+ */
1125
+ async create(applicationId, data) {
1126
+ return this.httpClient.post(`/applications/${applicationId}/storage/buckets`, data);
1127
+ }
1128
+ /**
1129
+ * Get a storage bucket by ID.
1130
+ *
1131
+ * @param applicationId - The application ID (UUID).
1132
+ * @param bucketId - The bucket ID (UUID).
1133
+ * @returns The bucket details.
1134
+ *
1135
+ * @example
1136
+ * ```ts
1137
+ * const bucket = await client.applications.storage.buckets.get('app-id', 'bucket-id');
1138
+ * console.log(`${bucket.name} (${bucket.visibility})`);
1139
+ * ```
1140
+ */
1141
+ async get(applicationId, bucketId) {
1142
+ return this.httpClient.get(`/applications/${applicationId}/storage/buckets/${bucketId}`);
1143
+ }
1144
+ /**
1145
+ * Permanently delete a storage bucket and all its objects.
1146
+ *
1147
+ * @param applicationId - The application ID (UUID).
1148
+ * @param bucketId - The bucket ID (UUID).
1149
+ *
1150
+ * @example
1151
+ * ```ts
1152
+ * await client.applications.storage.buckets.delete('app-id', 'bucket-id');
1153
+ * ```
1154
+ */
1155
+ async delete(applicationId, bucketId) {
1156
+ return this.httpClient.delete(`/applications/${applicationId}/storage/buckets/${bucketId}`);
1157
+ }
1158
+ };
1159
+
1160
+ // src/resources/storage-objects.ts
1161
+ var StorageObjectsResource = class extends BaseResource {
1162
+ /**
1163
+ * List objects in a storage bucket, optionally filtered by key prefix and
1164
+ * grouped into virtual folders by a delimiter.
1165
+ *
1166
+ * @param applicationId - The application ID (UUID).
1167
+ * @param bucketId - The bucket ID (UUID).
1168
+ * @param query - Optional prefix/delimiter/cursor/limit parameters.
1169
+ * @returns A page of objects plus any common (folder) prefixes and a `nextCursor`.
1170
+ *
1171
+ * @example
1172
+ * ```ts
1173
+ * // List the "images/" folder, one level deep
1174
+ * const { items, commonPrefixes, nextCursor } = await client.applications.storage.objects.list(
1175
+ * 'app-id', 'bucket-id', { prefix: 'images/', limit: 100 },
1176
+ * );
1177
+ * ```
1178
+ */
1179
+ async list(applicationId, bucketId, query) {
1180
+ return this.httpClient.get(`/applications/${applicationId}/storage/buckets/${bucketId}/objects`, {
1181
+ query
1182
+ });
1183
+ }
1184
+ /**
1185
+ * Get metadata for a specific object by its object ID.
1186
+ *
1187
+ * @param applicationId - The application ID (UUID).
1188
+ * @param bucketId - The bucket ID (UUID).
1189
+ * @param objectId - The object ID (UUID) — not the object key. Use {@link getByKey} to resolve a key.
1190
+ * @returns The object metadata.
1191
+ *
1192
+ * @example
1193
+ * ```ts
1194
+ * const obj = await client.applications.storage.objects.get('app-id', 'bucket-id', 'object-id');
1195
+ * console.log(`${obj.key}: ${obj.contentLength} bytes, type: ${obj.contentType}`);
1196
+ * ```
1197
+ */
1198
+ async get(applicationId, bucketId, objectId) {
1199
+ return this.httpClient.get(`/applications/${applicationId}/storage/buckets/${bucketId}/objects/${objectId}`);
1200
+ }
1201
+ /**
1202
+ * Resolve an object by its key (path) instead of its ID. Convenience over
1203
+ * {@link list} — lists with the key as the prefix and returns the exact match,
1204
+ * paging through results until found, or `null` if no object with that key
1205
+ * exists. (The API has no get-by-key endpoint.)
1206
+ *
1207
+ * @param applicationId - The application ID (UUID).
1208
+ * @param bucketId - The bucket ID (UUID).
1209
+ * @param key - The object key/path (e.g. `"images/photo.jpg"`).
1210
+ * @returns The matching object, or `null` if not found.
1211
+ */
1212
+ async getByKey(applicationId, bucketId, key) {
1213
+ let cursor;
1214
+ do {
1215
+ const page = await this.list(applicationId, bucketId, { prefix: key, delimiter: "", cursor });
1216
+ const match = page.items.find((object) => object.key === key);
1217
+ if (match) return match;
1218
+ cursor = page.nextCursor;
1219
+ } while (cursor);
1220
+ return null;
1221
+ }
1222
+ /**
1223
+ * Permanently delete an object from a storage bucket.
1224
+ *
1225
+ * @param applicationId - The application ID (UUID).
1226
+ * @param bucketId - The bucket ID (UUID).
1227
+ * @param objectId - The object ID (UUID) — not the object key.
1228
+ *
1229
+ * @example
1230
+ * ```ts
1231
+ * await client.applications.storage.objects.delete('app-id', 'bucket-id', 'object-id');
1232
+ * ```
1233
+ */
1234
+ async delete(applicationId, bucketId, objectId) {
1235
+ return this.httpClient.delete(`/applications/${applicationId}/storage/buckets/${bucketId}/objects/${objectId}`);
1236
+ }
1237
+ /**
1238
+ * Get an access URL for a storage object. For objects in public buckets,
1239
+ * returns a stable CDN URL. For private buckets, returns a time-limited
1240
+ * signed URL.
1241
+ *
1242
+ * @param applicationId - The application ID (UUID).
1243
+ * @param bucketId - The bucket ID (UUID).
1244
+ * @param objectId - The object ID (UUID) — not the object key.
1245
+ * @param options - Optional `expiresInSeconds` for signed URLs (60–86400).
1246
+ * @returns The access URL and its type/expiry.
1247
+ *
1248
+ * @example
1249
+ * ```ts
1250
+ * const access = await client.applications.storage.objects.getAccessUrl('app-id', 'bucket-id', 'object-id', {
1251
+ * expiresInSeconds: 3600,
1252
+ * });
1253
+ * if (access.accessType === 'signed') {
1254
+ * console.log(`Signed URL expires at ${access.expiresAt}`);
1255
+ * }
1256
+ * console.log(`Download: ${access.url}`);
1257
+ * ```
1258
+ */
1259
+ async getAccessUrl(applicationId, bucketId, objectId, options) {
1260
+ return this.httpClient.get(
1261
+ `/applications/${applicationId}/storage/buckets/${bucketId}/objects/${objectId}/access-url`,
1262
+ { query: { expiresInSeconds: options?.expiresInSeconds } }
1263
+ );
1264
+ }
1265
+ };
1266
+
1267
+ // src/resources/storage-upload-sessions.ts
1268
+ var StorageUploadSessionsResource = class extends BaseResource {
1269
+ constructor(httpClient, transport = tusUploadTransport) {
1270
+ super(httpClient);
1271
+ this.transport = transport;
1272
+ }
1273
+ transport;
1274
+ /**
1275
+ * List upload sessions for a bucket.
1276
+ *
1277
+ * @param applicationId - The application ID (UUID).
1278
+ * @param bucketId - The bucket ID (UUID).
1279
+ * @param query - Optional pagination parameters.
1280
+ * @returns A paginated list of upload sessions.
1281
+ */
1282
+ async list(applicationId, bucketId, query) {
1283
+ return this.httpClient.get(`/applications/${applicationId}/storage/buckets/${bucketId}/uploads`, {
1284
+ query
1285
+ });
1286
+ }
1287
+ /**
1288
+ * Create an upload session. Returns the session metadata and a signed upload
1289
+ * URL for sending file data. This is the low-level method — for most use
1290
+ * cases prefer `client.applications.storage.upload()` which also computes the
1291
+ * required SHA-256 checksum for you.
1292
+ *
1293
+ * @param applicationId - The application ID (UUID).
1294
+ * @param bucketId - The bucket ID (UUID).
1295
+ * @param data - Object key, content length, SHA-256 checksum, and optional content type / extra checksums.
1296
+ * @returns The session and resumable upload instructions (URL, method, headers).
1297
+ */
1298
+ async create(applicationId, bucketId, data) {
1299
+ return this.httpClient.post(`/applications/${applicationId}/storage/buckets/${bucketId}/uploads`, data);
1300
+ }
1301
+ /**
1302
+ * Get an upload session by ID. Use this to track server-side processing after
1303
+ * an upload (poll until `state === 'completed'`).
1304
+ *
1305
+ * @param applicationId - The application ID (UUID).
1306
+ * @param bucketId - The bucket ID (UUID).
1307
+ * @param sessionId - The upload session ID (UUID).
1308
+ * @returns The current session state.
1309
+ */
1310
+ async get(applicationId, bucketId, sessionId) {
1311
+ return this.httpClient.get(`/applications/${applicationId}/storage/buckets/${bucketId}/uploads/${sessionId}`);
1312
+ }
1313
+ /**
1314
+ * Upload bytes directly to a pre-existing signed upload URL — for example, a
1315
+ * URL returned by {@link create} or handed to your client by a backend. Skips
1316
+ * session creation and checksum computation.
1317
+ *
1318
+ * @param url - A signed upload URL.
1319
+ * @param source - The bytes to upload.
1320
+ * @param options - Chunk size, retry config, progress, abort signal, resume,
1321
+ * and any required `headers` returned with the upload session.
1322
+ * @throws {@link UploadError} if the upload fails after all retries.
1323
+ */
1324
+ async uploadToUrl(url, source, options) {
1325
+ const resolved = await resolveUploadSource({ key: "", ...source }, { hash: false });
1326
+ const headers = { "Tus-Resumable": "1.0.0", ...options?.headers };
1327
+ await runResolvedUpload(this.transport, url, resolved, options, headers).catch(toUploadError);
1328
+ }
1329
+ /**
1330
+ * Resume an interrupted upload to a previously issued signed upload URL. The
1331
+ * resumable protocol negotiates the current offset and continues from there.
1332
+ *
1333
+ * @param url - The signed upload URL from the original {@link create} call.
1334
+ * @param source - The full bytes to upload (the same content as the original).
1335
+ * @param options - Chunk size, retry config, progress, abort signal.
1336
+ */
1337
+ async resumeFromUrl(url, source, options) {
1338
+ return this.uploadToUrl(url, source, { ...options, resume: true });
1339
+ }
1340
+ };
1341
+
1342
+ // src/resources/application-storage.ts
1343
+ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1344
+ var ApplicationStorageResource = class {
1345
+ /** Create, list, get, and delete storage buckets. */
1346
+ buckets;
1347
+ /** List, get, delete objects, and generate access URLs. */
1348
+ objects;
1349
+ /** Low-level upload sessions and direct-to-URL byte uploads. */
1350
+ uploadSessions;
1351
+ transport;
1352
+ constructor(httpClient, transport = tusUploadTransport) {
1353
+ this.transport = transport;
1354
+ this.buckets = new StorageBucketsResource(httpClient);
1355
+ this.objects = new StorageObjectsResource(httpClient);
1356
+ this.uploadSessions = new StorageUploadSessionsResource(httpClient, transport);
1357
+ }
1358
+ /**
1359
+ * Upload a file to a storage bucket. Handles the full flow: computes the
1360
+ * required SHA-256 checksum, creates an upload session, uploads the bytes with
1361
+ * automatic retries (and optional progress/abort/resume), and returns the
1362
+ * public URL.
1363
+ *
1364
+ * Accepts browser `File`/`Blob`, Node `Buffer`/`Uint8Array`/`ArrayBuffer`, a
1365
+ * Node filesystem `path`, or a Node readable `stream` (with `contentLength`
1366
+ * and `checksumSha256`). The content type is inferred from `key` when omitted.
1367
+ *
1368
+ * @param input - What and where to upload, plus optional transfer options.
1369
+ * @returns The upload session and public object URL (and the finalized object when `waitForCompletion` is set).
1370
+ * @throws {@link UploadError} if the byte upload fails.
1371
+ * @throws {@link UploadSessionExpiredError} if the session expires mid-upload.
1372
+ *
1373
+ * @example
1374
+ * ```ts
1375
+ * const { session, url, object } = await client.applications.storage.upload({
1376
+ * applicationId, bucketId, key: 'reports/q1.pdf', path: './q1.pdf',
1377
+ * waitForCompletion: true,
1378
+ * });
1379
+ * console.log(object?.contentLength, 'bytes available at', url);
1380
+ * ```
1381
+ */
1382
+ async upload(input) {
1383
+ const resolved = await resolveUploadSource({
1384
+ key: input.key,
1385
+ data: input.data,
1386
+ path: input.path,
1387
+ stream: input.stream,
1388
+ contentType: input.contentType,
1389
+ contentLength: input.contentLength,
1390
+ checksumSha256: input.checksumSha256,
1391
+ checksumSha1: input.checksumSha1,
1392
+ checksumMd5: input.checksumMd5
1393
+ });
1394
+ const { session, upload } = await this.uploadSessions.create(input.applicationId, input.bucketId, {
1395
+ key: input.key,
1396
+ contentLength: resolved.size,
1397
+ checksumSha256: resolved.checksums.sha256,
1398
+ contentType: resolved.contentType,
1399
+ checksumSha1: resolved.checksums.sha1,
1400
+ checksumMd5: resolved.checksums.md5
1401
+ });
1402
+ await runResolvedUpload(
1403
+ this.transport,
1404
+ upload.url,
1405
+ resolved,
1406
+ {
1407
+ chunkSize: input.chunkSize,
1408
+ retryDelays: input.retryDelays,
1409
+ onProgress: input.onProgress,
1410
+ signal: input.signal,
1411
+ resume: input.resume,
1412
+ urlStorage: input.urlStorage
1413
+ },
1414
+ // Forward any required headers the API issued with the session.
1415
+ { "Tus-Resumable": "1.0.0", ...upload.headers }
1416
+ ).catch(toUploadError);
1417
+ if (input.waitForCompletion) {
1418
+ const options = typeof input.waitForCompletion === "object" ? input.waitForCompletion : void 0;
1419
+ const completed = await this.waitForCompletion(input.applicationId, input.bucketId, session.id, options);
1420
+ const object = await this.objects.getByKey(input.applicationId, input.bucketId, input.key) ?? void 0;
1421
+ return { session: completed, url: upload.publicObjectUrl, object };
1422
+ }
1423
+ return { session, url: upload.publicObjectUrl };
1424
+ }
1425
+ /**
1426
+ * Upload many files concurrently. Each file is uploaded independently; a
1427
+ * failure for one file does not abort the others — inspect each item's
1428
+ * `error`/`result`.
1429
+ *
1430
+ * @param inputs - The files to upload.
1431
+ * @param options - Concurrency limit and an aggregated progress callback.
1432
+ * @returns One result per input, in the same order.
1433
+ *
1434
+ * @example
1435
+ * ```ts
1436
+ * const results = await client.applications.storage.uploadBatch(
1437
+ * files.map((f) => ({ applicationId, bucketId, key: `uploads/${f.name}`, data: f })),
1438
+ * { concurrency: 6, onProgress: (done, total) => console.log(`${done}/${total}`) },
1439
+ * );
1440
+ * const failed = results.filter((r) => r.error);
1441
+ * ```
1442
+ */
1443
+ async uploadBatch(inputs, options) {
1444
+ const concurrency = Math.max(1, options?.concurrency ?? 4);
1445
+ const results = new Array(inputs.length);
1446
+ let completed = 0;
1447
+ let next = 0;
1448
+ const worker = async () => {
1449
+ for (; ; ) {
1450
+ const index = next++;
1451
+ if (index >= inputs.length) return;
1452
+ try {
1453
+ results[index] = { input: inputs[index], result: await this.upload(inputs[index]) };
1454
+ } catch (error) {
1455
+ results[index] = { input: inputs[index], error };
1456
+ }
1457
+ completed++;
1458
+ options?.onProgress?.(completed, inputs.length);
1459
+ }
1460
+ };
1461
+ await Promise.all(Array.from({ length: Math.min(concurrency, inputs.length) }, () => worker()));
1462
+ return results;
1463
+ }
1464
+ /**
1465
+ * Poll an upload session until the object is finalized server-side.
1466
+ *
1467
+ * @param applicationId - The application ID (UUID).
1468
+ * @param bucketId - The bucket ID (UUID).
1469
+ * @param sessionId - The upload session ID (UUID).
1470
+ * @param options - Timeout and polling interval.
1471
+ * @returns The completed session.
1472
+ * @throws {@link UploadError} if the session fails, expires, or the timeout elapses.
1473
+ */
1474
+ async waitForCompletion(applicationId, bucketId, sessionId, options) {
1475
+ const timeoutMs = options?.timeoutMs ?? 6e4;
1476
+ const pollIntervalMs = options?.pollIntervalMs ?? 1e3;
1477
+ const deadline = Date.now() + timeoutMs;
1478
+ for (; ; ) {
1479
+ const session = await this.uploadSessions.get(applicationId, bucketId, sessionId);
1480
+ if (session.state === "completed") return session;
1481
+ if (session.state === "failed" || session.state === "expired") {
1482
+ throw new UploadError(`Upload session ${session.state}.`);
1483
+ }
1484
+ if (Date.now() >= deadline) {
1485
+ throw new UploadError("Timed out waiting for the upload to complete.");
1486
+ }
1487
+ await delay(pollIntervalMs);
1488
+ }
1489
+ }
1490
+ };
1491
+
1492
+ // src/resources/applications.ts
1493
+ var ApplicationsResource = class extends BaseResource {
1494
+ /**
1495
+ * Manage environment variables scoped to an application.
1496
+ *
1497
+ * @example
1498
+ * ```ts
1499
+ * await client.applications.envVars.create('app-id', {
1500
+ * key: 'DATABASE_URL',
1501
+ * value: 'postgres://...',
1502
+ * sensitive: true,
1503
+ * });
1504
+ * ```
1505
+ */
1506
+ envVars;
1507
+ /**
1508
+ * Manage storage buckets, objects, and file uploads for an application.
1509
+ *
1510
+ * @example
1511
+ * ```ts
1512
+ * // Upload a file
1513
+ * const { url } = await client.applications.storage.upload({
1514
+ * applicationId: 'app-id',
1515
+ * bucketId: 'bucket-id',
1516
+ * key: 'images/logo.png',
1517
+ * data: fileData,
1518
+ * });
1519
+ *
1520
+ * // List objects in a bucket
1521
+ * const { items } = await client.applications.storage.objects.list('app-id', 'bucket-id');
1522
+ * ```
1523
+ */
1524
+ storage;
1525
+ /**
1526
+ * Read observed traffic (request logs) for an application.
1527
+ *
1528
+ * @example
1529
+ * ```ts
1530
+ * const { items } = await client.applications.requests.list('app-id', { statusFamily: 5 });
1531
+ * ```
1532
+ */
1533
+ requests;
1534
+ constructor(...args) {
1535
+ super(...args);
1536
+ this.envVars = new ApplicationEnvVarsResource(this.httpClient);
1537
+ this.storage = new ApplicationStorageResource(this.httpClient);
1538
+ this.requests = new ApplicationRequestsResource(this.httpClient);
1539
+ }
1540
+ /**
1541
+ * List applications the authenticated actor has access to.
1542
+ *
1543
+ * Requires the `network:applications:read` scope.
1544
+ *
1545
+ * @param query - Optional organization filter and pagination.
1546
+ * @returns A paginated list of applications.
1547
+ *
1548
+ * @example
1549
+ * ```ts
1550
+ * const { items, total } = await client.applications.list({ organizationId: 'org-id' });
1551
+ * console.log(`Found ${total} applications`);
1552
+ * ```
1553
+ */
1554
+ async list(query) {
1555
+ return this.httpClient.get("/applications", {
1556
+ query
1557
+ });
1558
+ }
1559
+ /**
1560
+ * List the `*.gigadrive.app` hostnames for an application (production alias
1561
+ * and per-branch aliases).
1562
+ *
1563
+ * @param applicationId - The application ID (UUID).
1564
+ * @returns The hostnames plus the production hostname `label`.
1565
+ *
1566
+ * @example
1567
+ * ```ts
1568
+ * const { items, label } = await client.applications.hostnames('app-id');
1569
+ * console.log(`Production: ${label}.gigadrive.app`);
1570
+ * ```
1571
+ */
1572
+ async hostnames(applicationId) {
1573
+ return this.httpClient.get(`/applications/${applicationId}/hostnames`);
1574
+ }
1575
+ /**
1576
+ * Check whether a candidate production hostname label is allowed and globally
1577
+ * available before setting it with {@link setProductionHostname}.
1578
+ *
1579
+ * Requires the `network:applications:read` scope.
1580
+ *
1581
+ * @param applicationId - The application ID (UUID).
1582
+ * @param label - The candidate production hostname label (e.g. `"my-app"`).
1583
+ * @returns Whether the label is available, plus a `reason` when it is not.
1584
+ *
1585
+ * @example
1586
+ * ```ts
1587
+ * const { available, reason } = await client.applications.checkHostnameAvailability('app-id', 'my-app');
1588
+ * if (!available) console.log(`Unavailable: ${reason}`);
1589
+ * ```
1590
+ */
1591
+ async checkHostnameAvailability(applicationId, label) {
1592
+ return this.httpClient.get(`/applications/${applicationId}/hostname/availability`, {
1593
+ query: { label }
1594
+ });
1595
+ }
1596
+ /**
1597
+ * Set the application's production hostname. The production alias
1598
+ * `{label}.gigadrive.app` is re-pointed to the latest production deployment.
1599
+ *
1600
+ * Requires the `network:applications:write` scope. Deployment- and
1601
+ * function-scoped tokens cannot change the production hostname.
1602
+ *
1603
+ * @param applicationId - The application ID (UUID).
1604
+ * @param label - The production hostname label (e.g. `"my-app"`). It is
1605
+ * normalized (slugified) server-side.
1606
+ * @returns The saved label, the resulting hostname, and whether it is live yet.
1607
+ *
1608
+ * @example
1609
+ * ```ts
1610
+ * const { hostname, live } = await client.applications.setProductionHostname('app-id', 'my-app');
1611
+ * console.log(live ? `Live at ${hostname}` : `Reserved ${hostname} (deploy to go live)`);
1612
+ * ```
1613
+ */
1614
+ async setProductionHostname(applicationId, label) {
1615
+ return this.httpClient.put(`/applications/${applicationId}/hostname`, { label });
1616
+ }
1617
+ };
1618
+
1619
+ // src/resources/deployments.ts
1620
+ var DeploymentsResource = class extends BaseResource {
1621
+ /**
1622
+ * List deployments, optionally filtered by organization, application, or status.
1623
+ *
1624
+ * Requires the `network:deployments:read` scope.
1625
+ *
1626
+ * @param query - Optional filters.
1627
+ * @returns A paginated list of deployments.
1628
+ *
1629
+ * @example
1630
+ * ```ts
1631
+ * // All deployments for an application
1632
+ * const { items } = await client.deployments.list({ applicationId: 'app-id' });
1633
+ *
1634
+ * // Only active deployments
1635
+ * const { items } = await client.deployments.list({ status: 'ACTIVE' });
1636
+ * ```
1637
+ */
1638
+ async list(query) {
1639
+ return this.httpClient.get("/deployments", {
1640
+ query
1641
+ });
1642
+ }
1643
+ /**
1644
+ * Get a deployment by ID, including its current status.
1645
+ *
1646
+ * @param deploymentId - The deployment ID (UUID).
1647
+ * @returns The deployment with its current status.
1648
+ *
1649
+ * @example
1650
+ * ```ts
1651
+ * const deployment = await client.deployments.get('deployment-id');
1652
+ * if (deployment.status === 'ACTIVE') {
1653
+ * console.log('Deployment is live!');
1654
+ * }
1655
+ * ```
1656
+ */
1657
+ async get(deploymentId) {
1658
+ return this.httpClient.get(`/deployments/${deploymentId}`);
1659
+ }
1660
+ /**
1661
+ * Create a new deployment. The deployment starts in `"PENDING"` status,
1662
+ * waiting for artifact upload. Use the multipart upload methods
1663
+ * ({@link startUpload}, {@link getPresignedUrl}, {@link uploadPart},
1664
+ * {@link completeUpload}) to upload build artifacts.
1665
+ *
1666
+ * Requires the `network:deployments:trigger` scope.
1667
+ *
1668
+ * @param data - The application ID and optional git source.
1669
+ * @returns The newly created deployment.
1670
+ *
1671
+ * @example
1672
+ * ```ts
1673
+ * const deployment = await client.deployments.create({
1674
+ * applicationId: 'app-id',
1675
+ * });
1676
+ * console.log(`Created deployment ${deployment.id} (${deployment.status})`);
1677
+ * ```
1678
+ */
1679
+ async create(data) {
1680
+ return this.httpClient.post("/deployments", data);
1681
+ }
1682
+ /**
1683
+ * Start a multipart upload for deployment artifacts. This initiates a new
1684
+ * multipart upload and returns an upload ID for subsequent part uploads.
1685
+ *
1686
+ * The deployment must be in `"PENDING"` status.
1687
+ *
1688
+ * @param deploymentId - The deployment ID (UUID).
1689
+ * @returns An object containing the `uploadId` for subsequent calls.
1690
+ *
1691
+ * @example
1692
+ * ```ts
1693
+ * const { uploadId } = await client.deployments.startUpload('deployment-id');
1694
+ * ```
1695
+ */
1696
+ async startUpload(deploymentId) {
1697
+ return this.httpClient.post(`/deployments/${deploymentId}/upload/start`);
1698
+ }
1699
+ /**
1700
+ * Get a presigned URL for uploading a single part of a multipart upload.
1701
+ * The URL is temporary and should be used immediately with {@link uploadPart}.
1702
+ *
1703
+ * @param deploymentId - The deployment ID (UUID).
1704
+ * @param uploadId - The multipart upload ID from {@link startUpload}.
1705
+ * @param partNumber - The 1-based part number.
1706
+ * @returns An object containing the presigned `url` to PUT the part data to.
1707
+ *
1708
+ * @example
1709
+ * ```ts
1710
+ * const { url } = await client.deployments.getPresignedUrl(
1711
+ * 'deployment-id', uploadId, 1,
1712
+ * );
1713
+ * ```
1714
+ */
1715
+ async getPresignedUrl(deploymentId, uploadId, partNumber) {
1716
+ if (!Number.isInteger(partNumber) || partNumber < 1) {
1717
+ throw new Error(`partNumber must be a positive integer, received ${partNumber}`);
1718
+ }
1719
+ return this.httpClient.post(`/deployments/${deploymentId}/upload/part`, { uploadId, partNumber });
1720
+ }
1721
+ /**
1722
+ * Upload a part to a presigned URL obtained from {@link getPresignedUrl}.
1723
+ * This sends the chunk data directly to the backend storage service, not
1724
+ * to the Gigadrive API. The returned `etag` is needed for
1725
+ * {@link completeUpload}.
1726
+ *
1727
+ * @param presignedUrl - The presigned URL from {@link getPresignedUrl}.
1728
+ * @param data - The chunk data to upload.
1729
+ * @param partNumber - The 1-based part number (used for error messages).
1730
+ * @returns The part number and ETag. Collect these for {@link completeUpload}.
1731
+ * @throws {@link ApiError} if the PUT request fails.
1732
+ * @throws Error if the storage provider does not return an ETag header.
1733
+ *
1734
+ * @example
1735
+ * ```ts
1736
+ * const chunk = fileBuffer.subarray(0, 10 * 1024 * 1024); // 10 MB
1737
+ * const part = await client.deployments.uploadPart(presignedUrl, chunk, 1);
1738
+ * // part = { partNumber: 1, etag: '"abc123..."' }
1739
+ * ```
1740
+ */
1741
+ async uploadPart(presignedUrl, data, partNumber) {
1742
+ if (!Number.isInteger(partNumber) || partNumber < 1) {
1743
+ throw new Error(`partNumber must be a positive integer, received ${partNumber}`);
1744
+ }
1745
+ const response = await this.httpClient.fetchRaw(presignedUrl, {
1746
+ method: "PUT",
1747
+ headers: {
1748
+ // Deployment artifacts are always ZIP archives.
1749
+ "Content-Type": "application/zip",
1750
+ "Content-Length": String(data instanceof Blob ? data.size : data.byteLength)
1751
+ },
1752
+ body: data
1753
+ });
1754
+ const etag = response.headers.get("ETag");
1755
+ if (!etag) {
1756
+ throw new Error(`No ETag received for part ${partNumber}`);
1757
+ }
1758
+ return { partNumber, etag };
1759
+ }
1760
+ /**
1761
+ * Complete a multipart upload after all parts have been uploaded via
1762
+ * {@link uploadPart}. This signals the storage provider to assemble the
1763
+ * parts into a single object and triggers the deployment build pipeline.
1764
+ *
1765
+ * @param deploymentId - The deployment ID (UUID).
1766
+ * @param uploadId - The multipart upload ID from {@link startUpload}.
1767
+ * @param parts - Array of `{ partNumber, etag }` from each {@link uploadPart} call.
1768
+ *
1769
+ * @example
1770
+ * ```ts
1771
+ * await client.deployments.completeUpload(deploymentId, uploadId, [
1772
+ * { partNumber: 1, etag: '"abc..."' },
1773
+ * { partNumber: 2, etag: '"def..."' },
1774
+ * ]);
1775
+ * ```
1776
+ */
1777
+ async completeUpload(deploymentId, uploadId, parts) {
1778
+ return this.httpClient.post(`/deployments/${deploymentId}/upload/complete`, { uploadId, parts });
1779
+ }
1780
+ /**
1781
+ * Fetch deployment logs. Supports pagination and filtering by timestamp
1782
+ * for incremental log tailing.
1783
+ *
1784
+ * @param deploymentId - The deployment ID (UUID).
1785
+ * @param query - Optional pagination and filter parameters.
1786
+ * @returns A page of log entries with total count.
1787
+ *
1788
+ * @example
1789
+ * ```ts
1790
+ * // Get the first page of logs
1791
+ * const page = await client.deployments.getLogs('deployment-id', {
1792
+ * offset: 0,
1793
+ * limit: 100,
1794
+ * });
1795
+ *
1796
+ * for (const entry of page.items) {
1797
+ * const prefix = entry.type === 'ERROR' ? 'ERR' : entry.type === 'WARN' ? 'WRN' : 'INF';
1798
+ * console.log(`[${prefix}] ${entry.message}`);
1799
+ * }
1800
+ *
1801
+ * // Tail new logs since last fetch
1802
+ * const newLogs = await client.deployments.getLogs('deployment-id', {
1803
+ * 'createdAt[gt]': lastEntry.createdAt,
1804
+ * });
1805
+ * ```
1806
+ */
1807
+ async getLogs(deploymentId, query) {
1808
+ return this.httpClient.get(`/deployments/${deploymentId}/logs`, {
1809
+ query
1810
+ });
1811
+ }
1812
+ /**
1813
+ * List the immutable `*.gigadrive.app` hostnames assigned to a deployment.
1814
+ *
1815
+ * @param deploymentId - The deployment ID (UUID).
1816
+ * @returns A paginated list of hostnames.
1817
+ *
1818
+ * @example
1819
+ * ```ts
1820
+ * const { items } = await client.deployments.getHostnames('deployment-id');
1821
+ * console.log(items.map((h) => h.hostname));
1822
+ * ```
1823
+ */
1824
+ async getHostnames(deploymentId) {
1825
+ return this.httpClient.get(`/deployments/${deploymentId}/hostnames`);
1826
+ }
1827
+ };
1828
+
1829
+ // src/resources/organization-ai-gateway.ts
1830
+ var AiGatewayUsageResource = class extends BaseResource {
1831
+ /** Get an aggregated usage summary with breakdowns by model, provider, and user. */
1832
+ async summary(organizationId, query) {
1833
+ return this.httpClient.get(`/organizations/${organizationId}/ai-gateway/usage/summary`, {
1834
+ query
1835
+ });
1836
+ }
1837
+ /** List request-level usage events. */
1838
+ async requests(organizationId, query) {
1839
+ return this.httpClient.get(`/organizations/${organizationId}/ai-gateway/usage/requests`, {
1840
+ query
1841
+ });
1842
+ }
1843
+ /** Export request-level usage events as CSV text. */
1844
+ async export(organizationId, query) {
1845
+ const response = await this.httpClient.requestStream(
1846
+ "GET",
1847
+ `/organizations/${organizationId}/ai-gateway/usage/export`,
1848
+ { query }
1849
+ );
1850
+ return response.text();
1851
+ }
1852
+ };
1853
+ var AiGatewayBudgetsResource = class extends BaseResource {
1854
+ /** List all budgets for an organization. */
1855
+ async list(organizationId) {
1856
+ return this.httpClient.get(`/organizations/${organizationId}/ai-gateway/budgets`);
1857
+ }
1858
+ /**
1859
+ * Replace the organization's entire budget set. Pass an empty array to clear
1860
+ * all budgets.
1861
+ */
1862
+ async replace(organizationId, budgets) {
1863
+ return this.httpClient.put(`/organizations/${organizationId}/ai-gateway/budgets`, { budgets });
1864
+ }
1865
+ };
1866
+ var AiGatewayPoliciesResource = class extends BaseResource {
1867
+ /**
1868
+ * Get the governance policy for an organization, or for a specific
1869
+ * application when `applicationId` is provided. Returns `null` if no policy is set.
1870
+ */
1871
+ async get(organizationId, options) {
1872
+ const { policy } = await this.httpClient.get(
1873
+ `/organizations/${organizationId}/ai-gateway/policies`,
1874
+ { query: { applicationId: options?.applicationId } }
1875
+ );
1876
+ return policy;
1877
+ }
1878
+ /** Create or update an AI Gateway policy. */
1879
+ async put(organizationId, data) {
1880
+ const { policy } = await this.httpClient.put(
1881
+ `/organizations/${organizationId}/ai-gateway/policies`,
1882
+ data
1883
+ );
1884
+ return policy;
1885
+ }
1886
+ };
1887
+ var OrganizationAiGatewayResource = class {
1888
+ /** Usage analytics (summary, request log, CSV export). */
1889
+ usage;
1890
+ /** Spend/usage budgets. */
1891
+ budgets;
1892
+ /** Model/provider governance policies. */
1893
+ policies;
1894
+ constructor(httpClient) {
1895
+ this.usage = new AiGatewayUsageResource(httpClient);
1896
+ this.budgets = new AiGatewayBudgetsResource(httpClient);
1897
+ this.policies = new AiGatewayPoliciesResource(httpClient);
1898
+ }
1899
+ };
1900
+
1901
+ // src/resources/organization-env-vars.ts
1902
+ var OrganizationEnvVarsResource = class extends BaseResource {
1903
+ /**
1904
+ * List all environment variables for an organization.
1905
+ *
1906
+ * @param organizationId - The organization ID (UUID).
1907
+ * @returns A paginated list of environment variables.
1908
+ *
1909
+ * @example
1910
+ * ```ts
1911
+ * const { items } = await client.organizations.envVars.list('org-id');
1912
+ * for (const v of items) {
1913
+ * console.log(`${v.key}=${v.sensitive ? '***' : v.value}`);
1914
+ * }
1915
+ * ```
1916
+ */
1917
+ async list(organizationId, query) {
1918
+ return this.httpClient.get(`/organizations/${organizationId}/env-vars`, {
1919
+ query
1920
+ });
1921
+ }
1922
+ /**
1923
+ * Create a new environment variable for an organization.
1924
+ *
1925
+ * @param organizationId - The organization ID (UUID).
1926
+ * @param data - The variable key, value, and optional settings.
1927
+ * @returns The newly created environment variable.
1928
+ *
1929
+ * @example
1930
+ * ```ts
1931
+ * const envVar = await client.organizations.envVars.create('org-id', {
1932
+ * key: 'STRIPE_SECRET',
1933
+ * value: 'sk_live_...',
1934
+ * sensitive: true,
1935
+ * });
1936
+ * ```
1937
+ */
1938
+ async create(organizationId, data) {
1939
+ return this.httpClient.post(`/organizations/${organizationId}/env-vars`, data);
1940
+ }
1941
+ /**
1942
+ * Update an existing environment variable. Only the fields you provide
1943
+ * will be changed; omitted fields are left unchanged.
1944
+ *
1945
+ * @param organizationId - The organization ID (UUID).
1946
+ * @param envVarId - The environment variable ID (UUID).
1947
+ * @param data - The fields to update.
1948
+ * @returns The updated environment variable.
1949
+ *
1950
+ * @example
1951
+ * ```ts
1952
+ * await client.organizations.envVars.update('org-id', 'var-id', {
1953
+ * value: 'new-value',
1954
+ * });
1955
+ * ```
1956
+ */
1957
+ async update(organizationId, envVarId, data) {
1958
+ return this.httpClient.patch(`/organizations/${organizationId}/env-vars/${envVarId}`, data);
1959
+ }
1960
+ /**
1961
+ * Permanently delete an environment variable.
1962
+ *
1963
+ * @param organizationId - The organization ID (UUID).
1964
+ * @param envVarId - The environment variable ID (UUID).
1965
+ *
1966
+ * @example
1967
+ * ```ts
1968
+ * await client.organizations.envVars.delete('org-id', 'var-id');
1969
+ * ```
1970
+ */
1971
+ async delete(organizationId, envVarId) {
1972
+ return this.httpClient.delete(`/organizations/${organizationId}/env-vars/${envVarId}`);
1973
+ }
1974
+ };
1975
+
1976
+ // src/resources/organizations.ts
1977
+ var OrganizationsResource = class extends BaseResource {
1978
+ /**
1979
+ * Manage environment variables scoped to an organization.
1980
+ *
1981
+ * @example
1982
+ * ```ts
1983
+ * // List all env vars for an organization
1984
+ * const { items } = await client.organizations.envVars.list('org-id');
1985
+ *
1986
+ * // Create a new env var
1987
+ * await client.organizations.envVars.create('org-id', {
1988
+ * key: 'ANALYTICS_KEY',
1989
+ * value: 'ak_live_...',
1990
+ * sensitive: true,
1991
+ * });
1992
+ * ```
1993
+ */
1994
+ envVars;
1995
+ /**
1996
+ * AI Gateway governance for an organization: usage analytics, budgets, and
1997
+ * model/provider policies.
1998
+ *
1999
+ * @example
2000
+ * ```ts
2001
+ * const usage = await client.organizations.aiGateway.usage.summary('org-id', { from: '2026-06-01' });
2002
+ * console.log(usage.summary.totalTokens);
2003
+ * ```
2004
+ */
2005
+ aiGateway;
2006
+ constructor(...args) {
2007
+ super(...args);
2008
+ this.envVars = new OrganizationEnvVarsResource(this.httpClient);
2009
+ this.aiGateway = new OrganizationAiGatewayResource(this.httpClient);
2010
+ }
2011
+ /**
2012
+ * List organizations the authenticated actor has access to.
2013
+ *
2014
+ * @param query - Optional pagination.
2015
+ * @returns A paginated list of organizations.
2016
+ *
2017
+ * @example
2018
+ * ```ts
2019
+ * const { items, total } = await client.organizations.list();
2020
+ * console.log(`Found ${total} organizations`);
2021
+ * ```
2022
+ */
2023
+ async list(query) {
2024
+ return this.httpClient.get("/organizations", {
2025
+ query
2026
+ });
2027
+ }
2028
+ };
2029
+
2030
+ // src/client.ts
2031
+ var DEFAULT_BASE_URL2 = "https://api.gigadrive.network";
2032
+ var GigadriveClient = class {
2033
+ /** Organization management (list, environment variables). */
2034
+ organizations;
2035
+ /** Application management (list, environment variables, storage). */
2036
+ applications;
2037
+ /** Deployment lifecycle (create, upload, status, logs). */
2038
+ deployments;
2039
+ /** AI Gateway — OpenAI-compatible chat completions and model listing. */
2040
+ aiGateway;
2041
+ constructor(config = {}) {
2042
+ const baseUrl = config.baseUrl ?? readEnv("GIGADRIVE_API_BASE_URL") ?? DEFAULT_BASE_URL2;
2043
+ const fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
2044
+ const credentialProvider = resolveCredentialProvider({ ...config, baseUrl, fetch: fetchFn });
2045
+ const tokenManager = new TokenManager(credentialProvider);
2046
+ const httpClient = new HttpClient(baseUrl, tokenManager, fetchFn);
2047
+ this.organizations = new OrganizationsResource(httpClient);
2048
+ this.applications = new ApplicationsResource(httpClient);
2049
+ this.deployments = new DeploymentsResource(httpClient);
2050
+ this.aiGateway = new AiGatewayResource(httpClient);
2051
+ }
2052
+ };
2053
+
2054
+ // src/pagination.ts
2055
+ async function* paginate(fetchPage) {
2056
+ let cursor;
2057
+ do {
2058
+ const page = await fetchPage(cursor);
2059
+ for (const item of page.items) {
2060
+ yield item;
2061
+ }
2062
+ cursor = page.nextCursor;
2063
+ } while (cursor);
2064
+ }
2065
+ export {
2066
+ AiGatewayAudioResource,
2067
+ AiGatewayBudgetsResource,
2068
+ AiGatewayPoliciesResource,
2069
+ AiGatewayResource,
2070
+ AiGatewayUsageResource,
2071
+ AiGatewayVideosResource,
2072
+ ApiError,
2073
+ ApplicationEnvVarsResource,
2074
+ ApplicationRequestsResource,
2075
+ ApplicationStorageResource,
2076
+ ApplicationsResource,
2077
+ AuthenticationError,
2078
+ DeploymentsResource,
2079
+ GigadriveClient,
2080
+ GigadriveError,
2081
+ OrganizationAiGatewayResource,
2082
+ OrganizationEnvVarsResource,
2083
+ OrganizationsResource,
2084
+ StorageBucketsResource,
2085
+ StorageObjectsResource,
2086
+ StorageUploadSessionsResource,
2087
+ UploadError,
2088
+ UploadSessionExpiredError,
2089
+ paginate,
2090
+ parseSSEStream
2091
+ };
8
2092
  //# sourceMappingURL=index.mjs.map