brigent-sdk 0.1.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/README.md +184 -0
- package/dist/client.d.ts +81 -0
- package/dist/client.js +190 -0
- package/dist/cost.d.ts +13 -0
- package/dist/cost.js +106 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +18 -0
- package/dist/events.d.ts +29 -0
- package/dist/events.js +145 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.js +93 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +7 -0
- package/dist/monitor.d.ts +15 -0
- package/dist/monitor.js +58 -0
- package/dist/types.d.ts +356 -0
- package/dist/types.js +3 -0
- package/dist/webhooks.d.ts +10 -0
- package/dist/webhooks.js +17 -0
- package/package.json +48 -0
package/dist/events.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// SDK helper for consuming SSE deployment event streams.
|
|
2
|
+
import { BrigentApiError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Stream deployment events via SSE.
|
|
5
|
+
* Returns an async iterable that yields DeploymentEvent objects.
|
|
6
|
+
* Supports auto-reconnect with exponential backoff.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* for await (const event of streamDeploymentEvents(baseUrl, apiKey, "dep_123")) {
|
|
11
|
+
* console.log(event.status);
|
|
12
|
+
* if (event.status === "live") break;
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export async function* streamDeploymentEvents(baseUrl, apiKey, deploymentId, options = {}) {
|
|
17
|
+
const maxAttempts = options.maxReconnectAttempts ?? 5;
|
|
18
|
+
const baseDelay = options.reconnectDelayMs ?? 1000;
|
|
19
|
+
let reconnectCount = 0;
|
|
20
|
+
while (reconnectCount <= maxAttempts) {
|
|
21
|
+
// Check if aborted before connecting
|
|
22
|
+
if (options.signal?.aborted)
|
|
23
|
+
return;
|
|
24
|
+
const url = `${baseUrl}/v1/deployments/${deploymentId}/events`;
|
|
25
|
+
let response;
|
|
26
|
+
try {
|
|
27
|
+
response = await fetch(url, {
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${apiKey}`,
|
|
30
|
+
Accept: "text/event-stream",
|
|
31
|
+
},
|
|
32
|
+
signal: options.signal,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// Network error during fetch
|
|
37
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
38
|
+
return;
|
|
39
|
+
if (reconnectCount >= maxAttempts)
|
|
40
|
+
throw err;
|
|
41
|
+
reconnectCount++;
|
|
42
|
+
yield {
|
|
43
|
+
deployment_id: deploymentId,
|
|
44
|
+
status: "reconnecting",
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
await delay(baseDelay * Math.pow(2, reconnectCount - 1));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new BrigentApiError(response.status, {
|
|
52
|
+
error: {
|
|
53
|
+
code: "SSE_CONNECTION_FAILED",
|
|
54
|
+
message: `SSE connection failed: ${response.status} ${response.statusText}`,
|
|
55
|
+
suggested_action: "Check the deployment ID and API key.",
|
|
56
|
+
documentation_url: "https://docs.brigent.io/sse",
|
|
57
|
+
},
|
|
58
|
+
request_id: "",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!response.body) {
|
|
62
|
+
throw new BrigentApiError(0, {
|
|
63
|
+
error: {
|
|
64
|
+
code: "SSE_NO_BODY",
|
|
65
|
+
message: "SSE response has no body",
|
|
66
|
+
suggested_action: "Unexpected response from server.",
|
|
67
|
+
documentation_url: "https://docs.brigent.io/sse",
|
|
68
|
+
},
|
|
69
|
+
request_id: "",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const reader = response.body.getReader();
|
|
73
|
+
const decoder = new TextDecoder();
|
|
74
|
+
let buffer = "";
|
|
75
|
+
let streamEnded = false;
|
|
76
|
+
try {
|
|
77
|
+
while (true) {
|
|
78
|
+
let result;
|
|
79
|
+
try {
|
|
80
|
+
result = await reader.read();
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// Read error (network disconnect)
|
|
84
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
85
|
+
return;
|
|
86
|
+
streamEnded = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (result.done) {
|
|
90
|
+
streamEnded = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
94
|
+
const lines = buffer.split("\n");
|
|
95
|
+
buffer = lines.pop() ?? "";
|
|
96
|
+
let currentEvent = "";
|
|
97
|
+
let currentData = "";
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
if (line.startsWith("event: ")) {
|
|
100
|
+
currentEvent = line.slice(7).trim();
|
|
101
|
+
}
|
|
102
|
+
else if (line.startsWith("data: ")) {
|
|
103
|
+
currentData = line.slice(6).trim();
|
|
104
|
+
}
|
|
105
|
+
else if (line === "" && currentEvent && currentData) {
|
|
106
|
+
if (currentEvent === "status" && currentData) {
|
|
107
|
+
try {
|
|
108
|
+
const event = JSON.parse(currentData);
|
|
109
|
+
yield event;
|
|
110
|
+
// Reset reconnect counter on successful event
|
|
111
|
+
reconnectCount = 0;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Skip malformed events
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
currentEvent = "";
|
|
118
|
+
currentData = "";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
reader.releaseLock();
|
|
125
|
+
}
|
|
126
|
+
// Stream ended — attempt reconnect
|
|
127
|
+
if (streamEnded && reconnectCount < maxAttempts) {
|
|
128
|
+
if (options.signal?.aborted)
|
|
129
|
+
return;
|
|
130
|
+
reconnectCount++;
|
|
131
|
+
yield {
|
|
132
|
+
deployment_id: deploymentId,
|
|
133
|
+
status: "reconnecting",
|
|
134
|
+
timestamp: new Date().toISOString(),
|
|
135
|
+
};
|
|
136
|
+
await delay(baseDelay * Math.pow(2, reconnectCount - 1));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// Max reconnects exhausted or clean exit
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function delay(ms) {
|
|
144
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
145
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface HttpClientOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
timeout: number;
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class HttpClient {
|
|
8
|
+
private readonly options;
|
|
9
|
+
constructor(options: HttpClientOptions);
|
|
10
|
+
get baseUrl(): string;
|
|
11
|
+
get apiKey(): string;
|
|
12
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
13
|
+
get<T>(path: string): Promise<T>;
|
|
14
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
15
|
+
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
16
|
+
delete(path: string): Promise<void>;
|
|
17
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { BrigentApiError } from "./errors.js";
|
|
2
|
+
const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
3
|
+
export class HttpClient {
|
|
4
|
+
options;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
get baseUrl() {
|
|
9
|
+
return this.options.baseUrl;
|
|
10
|
+
}
|
|
11
|
+
get apiKey() {
|
|
12
|
+
return this.options.apiKey;
|
|
13
|
+
}
|
|
14
|
+
async request(method, path, body) {
|
|
15
|
+
const url = `${this.options.baseUrl}${path}`;
|
|
16
|
+
let lastError;
|
|
17
|
+
for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {
|
|
18
|
+
if (attempt > 0) {
|
|
19
|
+
const delayMs = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
|
|
20
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
21
|
+
}
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeoutId = setTimeout(() => controller.abort(), this.options.timeout);
|
|
24
|
+
try {
|
|
25
|
+
const headers = {
|
|
26
|
+
"Authorization": `Bearer ${this.options.apiKey}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
"Accept": "application/json",
|
|
29
|
+
};
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
method,
|
|
32
|
+
headers,
|
|
33
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
});
|
|
36
|
+
clearTimeout(timeoutId);
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
let errorBody;
|
|
39
|
+
try {
|
|
40
|
+
errorBody = (await response.json());
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
errorBody = {
|
|
44
|
+
error: {
|
|
45
|
+
code: "UNKNOWN",
|
|
46
|
+
message: response.statusText || `HTTP ${response.status}`,
|
|
47
|
+
suggested_action: "Check the API status.",
|
|
48
|
+
documentation_url: "https://docs.brigent.io",
|
|
49
|
+
},
|
|
50
|
+
request_id: "unknown",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const apiError = new BrigentApiError(response.status, errorBody);
|
|
54
|
+
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.options.maxRetries) {
|
|
55
|
+
lastError = apiError;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
throw apiError;
|
|
59
|
+
}
|
|
60
|
+
// 204 No Content
|
|
61
|
+
if (response.status === 204) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return (await response.json());
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
if (error instanceof BrigentApiError) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
// Network / timeout errors
|
|
72
|
+
if (attempt < this.options.maxRetries) {
|
|
73
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw lastError ?? new Error("Request failed after retries");
|
|
80
|
+
}
|
|
81
|
+
async get(path) {
|
|
82
|
+
return this.request("GET", path);
|
|
83
|
+
}
|
|
84
|
+
async post(path, body) {
|
|
85
|
+
return this.request("POST", path, body);
|
|
86
|
+
}
|
|
87
|
+
async patch(path, body) {
|
|
88
|
+
return this.request("PATCH", path, body);
|
|
89
|
+
}
|
|
90
|
+
async delete(path) {
|
|
91
|
+
await this.request("DELETE", path);
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { BrigentClient } from "./client.js";
|
|
2
|
+
export { BrigentApiError } from "./errors.js";
|
|
3
|
+
export { HttpClient } from "./http.js";
|
|
4
|
+
export { DeploymentMonitor } from "./monitor.js";
|
|
5
|
+
export { verifyWebhookSignature } from "./webhooks.js";
|
|
6
|
+
export { estimateCost } from "./cost.js";
|
|
7
|
+
export { streamDeploymentEvents } from "./events.js";
|
|
8
|
+
export type { ListDeploymentsOptions, GetLogsOptions } from "./client.js";
|
|
9
|
+
export type { WaitForLiveOptions } from "./monitor.js";
|
|
10
|
+
export type { EstimateOptions } from "./cost.js";
|
|
11
|
+
export type { StreamEventsOptions } from "./events.js";
|
|
12
|
+
export type { DeployRequest, DeploymentType, SourceMethod, Runtime, DurationMode, BudgetAction, Region, ScaleConfig, ApiKeyCreateParams, WebhookCreateParams, WebhookUpdateParams, DeployResponse, DeploymentDetail, DeploymentListItem, DeploymentStatus, ResourceStatus, PaginatedResponse, CostEstimate, CostLineItem, BudgetStatus, CostResponse, BillingEvent, LogEntry, ApiKeyResponse, ApiKeyListItem, WebhookResponse, WebhookListItem, HealthResponse, ReadinessResponse, BrigentClientOptions, BrigentErrorBody, ErrorCode, ServerErrorCode, SdkErrorCode, AnalyticsOverview, DeploymentMetrics, MetricsSnapshot, UsageMetrics, AuditLogEntry, AuditLogOptions, BillingSummary, BillingSummaryOptions, TemplateCreateParams, TemplateResponse, CustomDomainResponse, DeploymentEvent, WebhookDeliveryItem, PricingTier, PricingResponse, TierResponse, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { BrigentClient } from "./client.js";
|
|
2
|
+
export { BrigentApiError } from "./errors.js";
|
|
3
|
+
export { HttpClient } from "./http.js";
|
|
4
|
+
export { DeploymentMonitor } from "./monitor.js";
|
|
5
|
+
export { verifyWebhookSignature } from "./webhooks.js";
|
|
6
|
+
export { estimateCost } from "./cost.js";
|
|
7
|
+
export { streamDeploymentEvents } from "./events.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HttpClient } from "./http.js";
|
|
2
|
+
import type { DeploymentDetail } from "./types.js";
|
|
3
|
+
export interface WaitForLiveOptions {
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
pollIntervalMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class DeploymentMonitor {
|
|
8
|
+
private readonly http;
|
|
9
|
+
constructor(http: HttpClient);
|
|
10
|
+
/**
|
|
11
|
+
* Poll a deployment until it reaches "live" status.
|
|
12
|
+
* Throws BrigentApiError on timeout, failure, or termination.
|
|
13
|
+
*/
|
|
14
|
+
waitForLive(deploymentId: string, options?: WaitForLiveOptions): Promise<DeploymentDetail>;
|
|
15
|
+
}
|
package/dist/monitor.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BrigentApiError } from "./errors.js";
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 300_000; // 5 minutes
|
|
3
|
+
const DEFAULT_POLL_INTERVAL_MS = 3_000; // 3 seconds
|
|
4
|
+
const TERMINAL_STATUSES = new Set(["live", "failed", "terminated"]);
|
|
5
|
+
export class DeploymentMonitor {
|
|
6
|
+
http;
|
|
7
|
+
constructor(http) {
|
|
8
|
+
this.http = http;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Poll a deployment until it reaches "live" status.
|
|
12
|
+
* Throws BrigentApiError on timeout, failure, or termination.
|
|
13
|
+
*/
|
|
14
|
+
async waitForLive(deploymentId, options) {
|
|
15
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
16
|
+
const pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
17
|
+
const deadline = Date.now() + timeoutMs;
|
|
18
|
+
while (Date.now() < deadline) {
|
|
19
|
+
const detail = await this.http.get(`/v1/deployments/${deploymentId}`);
|
|
20
|
+
if (detail.status === "live") {
|
|
21
|
+
return detail;
|
|
22
|
+
}
|
|
23
|
+
if (detail.status === "failed") {
|
|
24
|
+
throw new BrigentApiError(422, {
|
|
25
|
+
error: {
|
|
26
|
+
code: "DEPLOYMENT_FAILED",
|
|
27
|
+
message: detail.failure_reason ?? "Deployment failed",
|
|
28
|
+
suggested_action: "Check deployment logs for details.",
|
|
29
|
+
documentation_url: "https://docs.brigent.io/errors/DEPLOYMENT_FAILED",
|
|
30
|
+
},
|
|
31
|
+
request_id: "monitor",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (detail.status === "terminated") {
|
|
35
|
+
throw new BrigentApiError(410, {
|
|
36
|
+
error: {
|
|
37
|
+
code: "DEPLOYMENT_TERMINATED",
|
|
38
|
+
message: "Deployment was terminated before reaching live status",
|
|
39
|
+
suggested_action: "Create a new deployment.",
|
|
40
|
+
documentation_url: "https://docs.brigent.io/errors/DEPLOYMENT_TERMINATED",
|
|
41
|
+
},
|
|
42
|
+
request_id: "monitor",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Wait before next poll
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
47
|
+
}
|
|
48
|
+
throw new BrigentApiError(408, {
|
|
49
|
+
error: {
|
|
50
|
+
code: "MONITOR_TIMEOUT",
|
|
51
|
+
message: `Deployment did not become live within ${timeoutMs}ms`,
|
|
52
|
+
suggested_action: "Increase the timeout or check deployment status manually.",
|
|
53
|
+
documentation_url: "https://docs.brigent.io/errors/MONITOR_TIMEOUT",
|
|
54
|
+
},
|
|
55
|
+
request_id: "monitor",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|