@scalepad/sdk-core 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/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ import {
2
+ GeneratedCoreClient,
3
+ type GetclientsbyidResponse as GetClientResponse,
4
+ type ListassetshardwareQuery as ListHardwareAssetsQuery,
5
+ type ListassetshardwareResponse as ListHardwareAssetsResponse,
6
+ type ListclientsQuery as ListClientsQuery,
7
+ type ListclientsResponse as ListClientsResponse,
8
+ type ListintegrationsconfigurationsResponse as ListIntegrationConfigurationsResponse,
9
+ type ListserviceticketsQuery as ListTicketsQuery,
10
+ type ListserviceticketsResponse as ListTicketsResponse
11
+ } from "./generated.js";
12
+ import type { BaseClientConfig } from "./runtime.js";
13
+
14
+ export { coreOperations, type GeneratedOperation as GeneratedCoreOperation } from "./catalog.js";
15
+ export { GeneratedCoreClient } from "./generated.js";
16
+ export { BaseApiError as ScalepadApiError, type FetchLike, type JsonArray, type JsonObject, type JsonPrimitive, type JsonValue } from "./runtime.js";
17
+ export type { paths } from "./schema.js";
18
+
19
+ export interface ListQuery {
20
+ cursor?: string;
21
+ pageSize?: number;
22
+ filters?: Record<string, string>;
23
+ sort?: string;
24
+ }
25
+
26
+ export interface CoreClientConfig extends BaseClientConfig {}
27
+
28
+ export interface PaginatedResponse<T = Record<string, unknown>> {
29
+ data: T[];
30
+ total_count?: number;
31
+ next_cursor?: string | null;
32
+ [key: string]: unknown;
33
+ }
34
+
35
+ function buildListQuery(query: ListQuery = {}): Record<string, string> {
36
+ const params: Record<string, string> = {};
37
+
38
+ if (query.pageSize != null) {
39
+ params.page_size = String(query.pageSize);
40
+ }
41
+
42
+ if (query.cursor) {
43
+ params.cursor = query.cursor;
44
+ }
45
+
46
+ if (query.sort) {
47
+ params.sort = query.sort;
48
+ }
49
+
50
+ for (const [field, expression] of Object.entries(query.filters ?? {})) {
51
+ params[`filter[${field}]`] = expression;
52
+ }
53
+
54
+ return params;
55
+ }
56
+
57
+ export class ScalepadCoreClient {
58
+ private readonly generated: GeneratedCoreClient;
59
+
60
+ public constructor(config: CoreClientConfig) {
61
+ this.generated = new GeneratedCoreClient(config);
62
+ }
63
+
64
+ public listClients(query: ListQuery = {}): Promise<ListClientsResponse> {
65
+ return this.generated.listClients(buildListQuery(query) as ListClientsQuery);
66
+ }
67
+
68
+ public getClient(id: string): Promise<GetClientResponse> {
69
+ return this.generated.getClientsById({ id });
70
+ }
71
+
72
+ public listIntegrationConfigurations(): Promise<ListIntegrationConfigurationsResponse> {
73
+ return this.generated.listIntegrationsConfigurations();
74
+ }
75
+
76
+ public listHardwareAssets(query: ListQuery = {}): Promise<ListHardwareAssetsResponse> {
77
+ return this.generated.listAssetsHardware(buildListQuery(query) as ListHardwareAssetsQuery);
78
+ }
79
+
80
+ public listTickets(query: ListQuery = {}): Promise<ListTicketsResponse> {
81
+ return this.generated.listServiceTickets(buildListQuery(query) as ListTicketsQuery);
82
+ }
83
+ }
84
+
85
+ export function createCoreClient(config: CoreClientConfig): ScalepadCoreClient {
86
+ return new ScalepadCoreClient(config);
87
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,132 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+ export interface JsonObject {
3
+ [key: string]: JsonValue;
4
+ }
5
+ export interface JsonArray extends Array<JsonValue> {}
6
+ export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
7
+ export type FetchLike = typeof fetch;
8
+ export type QueryArrayValue = string | number | boolean;
9
+ export type QueryValue = string | number | boolean | null | undefined | ReadonlyArray<QueryArrayValue>;
10
+ export type QueryParams = Record<string, QueryValue>;
11
+
12
+ export class BaseApiError extends Error {
13
+ public readonly status: number;
14
+ public readonly payload: unknown;
15
+
16
+ public constructor(message: string, status: number, payload: unknown) {
17
+ super(message);
18
+ this.name = "BaseApiError";
19
+ this.status = status;
20
+ this.payload = payload;
21
+ }
22
+ }
23
+
24
+ export interface BaseClientConfig {
25
+ apiKey: string;
26
+ baseUrl?: string;
27
+ fetchImpl?: FetchLike;
28
+ userAgent?: string;
29
+ maxRetries?: number;
30
+ }
31
+
32
+ const DEFAULT_BASE_URL = "https://api.scalepad.com";
33
+
34
+ function delay(ms: number): Promise<void> {
35
+ return new Promise((resolve) => {
36
+ setTimeout(resolve, ms);
37
+ });
38
+ }
39
+
40
+ async function parseResponseBody(response: Response): Promise<unknown> {
41
+ if (response.status === 204 || response.status === 205) {
42
+ return null;
43
+ }
44
+
45
+ const contentType = response.headers.get("content-type") ?? "";
46
+ if (contentType.includes("application/json")) {
47
+ return response.json();
48
+ }
49
+
50
+ const text = await response.text();
51
+ return text.length === 0 ? null : text;
52
+ }
53
+
54
+ function appendQuery(url: URL, query?: QueryParams): void {
55
+ if (!query) {
56
+ return;
57
+ }
58
+
59
+ for (const [key, value] of Object.entries(query)) {
60
+ if (value == null) {
61
+ continue;
62
+ }
63
+
64
+ if (Array.isArray(value)) {
65
+ url.searchParams.set(key, value.join(","));
66
+ continue;
67
+ }
68
+
69
+ url.searchParams.set(key, String(value));
70
+ }
71
+ }
72
+
73
+ export class BaseApiClient {
74
+ private readonly apiKey: string;
75
+ private readonly baseUrl: string;
76
+ private readonly fetchImpl: FetchLike;
77
+ private readonly userAgent: string;
78
+ private readonly maxRetries: number;
79
+
80
+ public constructor(config: BaseClientConfig) {
81
+ this.apiKey = config.apiKey;
82
+ this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
83
+ this.fetchImpl = config.fetchImpl ?? fetch;
84
+ this.userAgent = config.userAgent ?? "@scalepad/cli";
85
+ this.maxRetries = config.maxRetries ?? 3;
86
+ }
87
+
88
+ protected async requestJson(request: {
89
+ method: string;
90
+ path: string;
91
+ query?: QueryParams;
92
+ body?: JsonValue;
93
+ }): Promise<unknown> {
94
+ const url = new URL(request.path, this.baseUrl);
95
+ appendQuery(url, request.query);
96
+
97
+ let attempt = 0;
98
+ while (true) {
99
+ const response = await this.fetchImpl(url, {
100
+ method: request.method,
101
+ headers: {
102
+ accept: "application/json",
103
+ ...(request.body != null ? { "content-type": "application/json" } : {}),
104
+ "x-api-key": this.apiKey,
105
+ "user-agent": this.userAgent
106
+ },
107
+ body: request.body != null ? JSON.stringify(request.body) : undefined
108
+ });
109
+
110
+ if (response.ok) {
111
+ return parseResponseBody(response);
112
+ }
113
+
114
+ if (response.status === 429 && attempt < this.maxRetries) {
115
+ attempt += 1;
116
+ const retryAfterSeconds = Number(response.headers.get("retry-after"));
117
+ const waitMs = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
118
+ ? retryAfterSeconds * 1000
119
+ : 500 * (2 ** (attempt - 1));
120
+ await delay(waitMs);
121
+ continue;
122
+ }
123
+
124
+ const payload = await parseResponseBody(response);
125
+ throw new BaseApiError(
126
+ `API request failed with status ${response.status}`,
127
+ response.status,
128
+ payload
129
+ );
130
+ }
131
+ }
132
+ }