@cstar.help/js 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/dist/index.js ADDED
@@ -0,0 +1,331 @@
1
+ // src/errors.ts
2
+ var ErrorType = {
3
+ AUTHENTICATION_ERROR: "authentication_error",
4
+ INVALID_REQUEST_ERROR: "invalid_request_error",
5
+ NOT_FOUND_ERROR: "not_found_error",
6
+ RATE_LIMIT_ERROR: "rate_limit_error",
7
+ API_ERROR: "api_error"
8
+ };
9
+ var CStarError = class extends Error {
10
+ constructor(statusCode, body) {
11
+ super(body.message);
12
+ this.name = "CStarError";
13
+ this.type = body.type;
14
+ this.code = body.code;
15
+ this.param = body.param;
16
+ this.docUrl = body.doc_url;
17
+ this.requestId = body.request_id;
18
+ this.statusCode = statusCode;
19
+ }
20
+ };
21
+ var CStarAuthenticationError = class extends CStarError {
22
+ constructor(statusCode, body) {
23
+ super(statusCode, body);
24
+ this.name = "CStarAuthenticationError";
25
+ }
26
+ };
27
+ var CStarRateLimitError = class extends CStarError {
28
+ constructor(statusCode, body, retryAfter) {
29
+ super(statusCode, body);
30
+ this.name = "CStarRateLimitError";
31
+ this.retryAfter = retryAfter;
32
+ }
33
+ };
34
+ var CStarNotFoundError = class extends CStarError {
35
+ constructor(statusCode, body) {
36
+ super(statusCode, body);
37
+ this.name = "CStarNotFoundError";
38
+ }
39
+ };
40
+ var CStarValidationError = class extends CStarError {
41
+ constructor(statusCode, body) {
42
+ super(statusCode, body);
43
+ this.name = "CStarValidationError";
44
+ }
45
+ };
46
+ function parseError(statusCode, body, retryAfter) {
47
+ const errorBody = body?.error ?? {
48
+ type: ErrorType.API_ERROR,
49
+ code: "unknown_error",
50
+ message: `Request failed with status ${statusCode}`,
51
+ doc_url: "https://cstar.help/developers/api/errors"
52
+ };
53
+ switch (errorBody.type) {
54
+ case ErrorType.AUTHENTICATION_ERROR:
55
+ return new CStarAuthenticationError(statusCode, errorBody);
56
+ case ErrorType.RATE_LIMIT_ERROR:
57
+ return new CStarRateLimitError(statusCode, errorBody, retryAfter ?? 60);
58
+ case ErrorType.NOT_FOUND_ERROR:
59
+ return new CStarNotFoundError(statusCode, errorBody);
60
+ case ErrorType.INVALID_REQUEST_ERROR:
61
+ return new CStarValidationError(statusCode, errorBody);
62
+ default:
63
+ return new CStarError(statusCode, errorBody);
64
+ }
65
+ }
66
+
67
+ // src/client.ts
68
+ var SDK_VERSION = "0.1.0";
69
+ var DEFAULT_BASE_URL = "https://app.cstar.help";
70
+ var DEFAULT_API_VERSION = "2026-03-01";
71
+ var DEFAULT_MAX_RETRIES = 3;
72
+ var DEFAULT_TIMEOUT = 3e4;
73
+ var HttpClient = class {
74
+ constructor(config) {
75
+ this.config = {
76
+ apiKey: config.apiKey,
77
+ teamId: config.teamId,
78
+ baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
79
+ version: config.version ?? DEFAULT_API_VERSION,
80
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
81
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
82
+ };
83
+ this.environment = config.apiKey.startsWith("sk_test_") || config.apiKey.startsWith("pk_test_") ? "test" : "live";
84
+ }
85
+ /**
86
+ * Make an authenticated request to the cStar API.
87
+ * Automatically retries on 5xx errors and rate limits.
88
+ */
89
+ async request(method, path, options) {
90
+ const url = this.buildUrl(path, options?.query);
91
+ const headers = this.buildHeaders(options?.idempotencyKey);
92
+ const body = options?.body ? JSON.stringify(options.body) : void 0;
93
+ let lastError = null;
94
+ for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
95
+ if (attempt > 0 && lastError) {
96
+ const delay = lastError instanceof CStarRateLimitError ? lastError.retryAfter * 1e3 : this.getBackoffDelay(attempt);
97
+ await this.sleep(delay);
98
+ }
99
+ let response;
100
+ try {
101
+ response = await fetch(url, {
102
+ method,
103
+ headers,
104
+ body,
105
+ signal: AbortSignal.timeout(this.config.timeout)
106
+ });
107
+ } catch (err) {
108
+ lastError = err instanceof Error ? err : new Error(String(err));
109
+ if (attempt < this.config.maxRetries) continue;
110
+ throw lastError;
111
+ }
112
+ if (response.ok) {
113
+ return await response.json();
114
+ }
115
+ const errorBody = await response.json().catch(() => null);
116
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "", 10) || void 0;
117
+ if (response.status < 500 && response.status !== 429) {
118
+ throw parseError(response.status, errorBody, retryAfter);
119
+ }
120
+ if (response.status === 429) {
121
+ lastError = parseError(429, errorBody, retryAfter);
122
+ if (attempt < this.config.maxRetries) continue;
123
+ throw lastError;
124
+ }
125
+ lastError = parseError(response.status, errorBody);
126
+ if (attempt < this.config.maxRetries) continue;
127
+ throw lastError;
128
+ }
129
+ throw lastError ?? new Error("Request failed after retries");
130
+ }
131
+ buildUrl(path, query) {
132
+ const base = `${this.config.baseUrl}/api/v1/teams/${this.config.teamId}${path}`;
133
+ if (!query) return base;
134
+ const params = new URLSearchParams();
135
+ for (const [key, value] of Object.entries(query)) {
136
+ if (value === void 0 || value === null) continue;
137
+ if (Array.isArray(value)) {
138
+ for (const v of value) {
139
+ params.append(`${key}[]`, v);
140
+ }
141
+ } else {
142
+ params.set(key, String(value));
143
+ }
144
+ }
145
+ const qs = params.toString();
146
+ return qs ? `${base}?${qs}` : base;
147
+ }
148
+ buildHeaders(idempotencyKey) {
149
+ const headers = {
150
+ "Authorization": `Bearer ${this.config.apiKey}`,
151
+ "Content-Type": "application/json",
152
+ "CStar-Version": this.config.version,
153
+ "User-Agent": `@cstar.help/js/${SDK_VERSION}`
154
+ };
155
+ if (idempotencyKey) {
156
+ headers["Idempotency-Key"] = idempotencyKey;
157
+ }
158
+ return headers;
159
+ }
160
+ /** Exponential backoff: 500ms, 1s, 2s, 4s... capped at 8s, plus jitter. */
161
+ getBackoffDelay(attempt) {
162
+ const base = Math.min(500 * Math.pow(2, attempt - 1), 8e3);
163
+ const jitter = Math.random() * 200;
164
+ return base + jitter;
165
+ }
166
+ sleep(ms) {
167
+ return new Promise((resolve) => setTimeout(resolve, ms));
168
+ }
169
+ };
170
+
171
+ // src/resources/base.ts
172
+ var BaseResource = class {
173
+ constructor(client, basePath) {
174
+ this.client = client;
175
+ this.basePath = basePath;
176
+ }
177
+ /** List resources with optional filtering and pagination. */
178
+ async list(params) {
179
+ const { idempotencyKey: _, expand, ...rest } = params ?? {};
180
+ return this.client.request("GET", this.basePath, {
181
+ query: { ...this.serializeQuery(rest), expand }
182
+ });
183
+ }
184
+ /** Get a single resource by ID. */
185
+ async get(id, options) {
186
+ return this.client.request("GET", `${this.basePath}/${id}`, {
187
+ query: options?.expand ? { expand: options.expand } : void 0
188
+ });
189
+ }
190
+ /** Create a new resource. */
191
+ async create(params, options) {
192
+ return this.client.request("POST", this.basePath, {
193
+ body: params,
194
+ idempotencyKey: options?.idempotencyKey
195
+ });
196
+ }
197
+ /** Update an existing resource. */
198
+ async update(id, params, options) {
199
+ return this.client.request("PATCH", `${this.basePath}/${id}`, {
200
+ body: params,
201
+ idempotencyKey: options?.idempotencyKey
202
+ });
203
+ }
204
+ /** Delete a resource by ID. */
205
+ async del(id) {
206
+ return this.client.request(
207
+ "DELETE",
208
+ `${this.basePath}/${id}`
209
+ );
210
+ }
211
+ /**
212
+ * Auto-paginating async iterator that yields every item across all pages.
213
+ *
214
+ * ```typescript
215
+ * for await (const ticket of cstar.tickets.listAutoPaginating({ status: 'open' })) {
216
+ * console.log(ticket.title);
217
+ * }
218
+ * ```
219
+ */
220
+ async *listAutoPaginating(params) {
221
+ let page = 1;
222
+ const pageSize = params?.pageSize;
223
+ while (true) {
224
+ const response = await this.list({ ...params, page, pageSize });
225
+ for (const item of response.data) {
226
+ yield item;
227
+ }
228
+ if (!response.pagination.hasMore) break;
229
+ page++;
230
+ }
231
+ }
232
+ /** Convert params to string values for query string serialization. */
233
+ serializeQuery(params) {
234
+ const result = {};
235
+ for (const [key, value] of Object.entries(params)) {
236
+ if (value === void 0 || value === null) continue;
237
+ if (Array.isArray(value)) {
238
+ result[key] = value.map(String);
239
+ } else if (typeof value === "boolean") {
240
+ result[key] = String(value);
241
+ } else {
242
+ result[key] = String(value);
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+ };
248
+
249
+ // src/resources/tickets.ts
250
+ var TicketMessagesResource = class {
251
+ constructor(client) {
252
+ this.client = client;
253
+ }
254
+ /** List all messages on a ticket. */
255
+ async list(ticketId) {
256
+ return this.client.request(
257
+ "GET",
258
+ `/tickets/${ticketId}/messages`
259
+ );
260
+ }
261
+ /** Add a message to a ticket. */
262
+ async create(ticketId, params, options) {
263
+ return this.client.request(
264
+ "POST",
265
+ `/tickets/${ticketId}/messages`,
266
+ {
267
+ body: params,
268
+ idempotencyKey: options?.idempotencyKey
269
+ }
270
+ );
271
+ }
272
+ };
273
+ var TicketsResource = class extends BaseResource {
274
+ constructor(client) {
275
+ super(client, "/tickets");
276
+ this.messages = new TicketMessagesResource(client);
277
+ }
278
+ };
279
+
280
+ // src/resources/customers.ts
281
+ var CustomersResource = class extends BaseResource {
282
+ constructor(client) {
283
+ super(client, "/customers");
284
+ }
285
+ };
286
+
287
+ // src/resources/articles.ts
288
+ var ArticlesResource = class extends BaseResource {
289
+ constructor(client) {
290
+ super(client, "/articles");
291
+ }
292
+ };
293
+
294
+ // src/resources/webhooks.ts
295
+ var WebhooksResource = class extends BaseResource {
296
+ constructor(client) {
297
+ super(client, "/webhooks");
298
+ }
299
+ /** Send a test delivery to a webhook endpoint. */
300
+ async test(webhookId, params) {
301
+ return this.client.request(
302
+ "POST",
303
+ `/webhooks/${webhookId}/test`,
304
+ { body: params ?? {} }
305
+ );
306
+ }
307
+ };
308
+
309
+ // src/index.ts
310
+ var CStarClient = class {
311
+ constructor(config) {
312
+ this.client = new HttpClient(config);
313
+ this.tickets = new TicketsResource(this.client);
314
+ this.customers = new CustomersResource(this.client);
315
+ this.articles = new ArticlesResource(this.client);
316
+ this.webhooks = new WebhooksResource(this.client);
317
+ }
318
+ /** Whether this client is using test mode keys (sk_test_* or pk_test_*). */
319
+ get isTestMode() {
320
+ return this.client.environment === "test";
321
+ }
322
+ };
323
+ export {
324
+ CStarAuthenticationError,
325
+ CStarClient,
326
+ CStarError,
327
+ CStarNotFoundError,
328
+ CStarRateLimitError,
329
+ CStarValidationError,
330
+ ErrorType
331
+ };
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/library/index.ts
21
+ var library_exports = {};
22
+ __export(library_exports, {
23
+ LibraryClient: () => LibraryClient
24
+ });
25
+ module.exports = __toCommonJS(library_exports);
26
+ var DEFAULT_BASE_URL = "https://app.cstar.help";
27
+ var LibraryClient = class {
28
+ constructor(config) {
29
+ this.resolvedTeamId = null;
30
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
31
+ this.teamSlug = config.teamSlug;
32
+ }
33
+ /** List all public categories with article counts. */
34
+ async categories() {
35
+ const response = await this.fetch(
36
+ `/api/library/${this.teamSlug}/categories`
37
+ );
38
+ return response.data;
39
+ }
40
+ /** List published articles, optionally filtered by category. */
41
+ async articles(params) {
42
+ const query = {};
43
+ if (params?.categorySlug) query.categorySlug = params.categorySlug;
44
+ if (params?.limit) query.limit = String(params.limit);
45
+ if (params?.offset) query.offset = String(params.offset);
46
+ const response = await this.fetch(
47
+ `/api/library/${this.teamSlug}/articles`,
48
+ query
49
+ );
50
+ return response.data;
51
+ }
52
+ /** Get a single article by slug. */
53
+ async article(slug) {
54
+ const response = await this.fetch(
55
+ `/api/library/${this.teamSlug}/articles/${slug}`
56
+ );
57
+ return response.data;
58
+ }
59
+ /**
60
+ * Full-text search across article titles, excerpts, and content.
61
+ * Returns matching articles with relevance ranking.
62
+ */
63
+ async search(query, params) {
64
+ const queryParams = {
65
+ q: query
66
+ };
67
+ if (params?.limit) queryParams.limit = String(params.limit);
68
+ return this.fetch(
69
+ `/api/library/${this.teamSlug}/search`,
70
+ queryParams
71
+ );
72
+ }
73
+ async fetch(path, query) {
74
+ let url = `${this.baseUrl}${path}`;
75
+ if (query) {
76
+ const params = new URLSearchParams();
77
+ for (const [key, value] of Object.entries(query)) {
78
+ if (value !== void 0) params.set(key, value);
79
+ }
80
+ const qs = params.toString();
81
+ if (qs) url += `?${qs}`;
82
+ }
83
+ const response = await globalThis.fetch(url, {
84
+ method: "GET",
85
+ headers: { "Content-Type": "application/json" }
86
+ });
87
+ if (!response.ok) {
88
+ const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
89
+ throw new Error(errorBody.error?.message ?? `Library API error: ${response.status}`);
90
+ }
91
+ return response.json();
92
+ }
93
+ };
94
+ // Annotate the CommonJS export names for ESM import in node:
95
+ 0 && (module.exports = {
96
+ LibraryClient
97
+ });
@@ -0,0 +1,82 @@
1
+ /** Configuration for the LibraryClient. */
2
+ interface LibraryConfig {
3
+ /** Team slug (e.g., 'acme'). Identifies the knowledge base to query. */
4
+ teamSlug: string;
5
+ /** Override the base URL (defaults to https://app.cstar.help). */
6
+ baseUrl?: string;
7
+ }
8
+ /** A published article in the public knowledge base. */
9
+ interface LibraryArticle {
10
+ id: string;
11
+ team_id: string;
12
+ title: string;
13
+ slug: string;
14
+ excerpt: string;
15
+ content: string;
16
+ category: string;
17
+ tags: string[];
18
+ view_count: number;
19
+ published_at: string;
20
+ created_at: string;
21
+ updated_at: string;
22
+ meta_description?: string;
23
+ }
24
+ /** A category in the knowledge base. */
25
+ interface Category {
26
+ id: string;
27
+ team_id: string;
28
+ name: string;
29
+ slug: string;
30
+ description?: string;
31
+ icon?: string;
32
+ color?: string;
33
+ sort_order: number;
34
+ is_public: boolean;
35
+ article_count: number;
36
+ }
37
+ /** Parameters for listing articles. */
38
+ interface LibraryArticleListParams {
39
+ categorySlug?: string;
40
+ limit?: number;
41
+ offset?: number;
42
+ }
43
+ /** Parameters for searching articles. */
44
+ interface LibrarySearchParams {
45
+ limit?: number;
46
+ }
47
+ /** Search results. */
48
+ interface LibrarySearchResult {
49
+ data: LibraryArticle[];
50
+ count: number;
51
+ query: string;
52
+ }
53
+
54
+ /**
55
+ * Public knowledge base client. No authentication required.
56
+ *
57
+ * ```typescript
58
+ * const library = new LibraryClient({ teamSlug: 'acme' });
59
+ * const categories = await library.categories();
60
+ * const results = await library.search('reset password');
61
+ * ```
62
+ */
63
+ declare class LibraryClient {
64
+ private readonly baseUrl;
65
+ private readonly teamSlug;
66
+ private resolvedTeamId;
67
+ constructor(config: LibraryConfig);
68
+ /** List all public categories with article counts. */
69
+ categories(): Promise<Category[]>;
70
+ /** List published articles, optionally filtered by category. */
71
+ articles(params?: LibraryArticleListParams): Promise<LibraryArticle[]>;
72
+ /** Get a single article by slug. */
73
+ article(slug: string): Promise<LibraryArticle>;
74
+ /**
75
+ * Full-text search across article titles, excerpts, and content.
76
+ * Returns matching articles with relevance ranking.
77
+ */
78
+ search(query: string, params?: LibrarySearchParams): Promise<LibrarySearchResult>;
79
+ private fetch;
80
+ }
81
+
82
+ export { type Category, type LibraryArticle, type LibraryArticleListParams, LibraryClient, type LibraryConfig, type LibrarySearchParams, type LibrarySearchResult };
@@ -0,0 +1,82 @@
1
+ /** Configuration for the LibraryClient. */
2
+ interface LibraryConfig {
3
+ /** Team slug (e.g., 'acme'). Identifies the knowledge base to query. */
4
+ teamSlug: string;
5
+ /** Override the base URL (defaults to https://app.cstar.help). */
6
+ baseUrl?: string;
7
+ }
8
+ /** A published article in the public knowledge base. */
9
+ interface LibraryArticle {
10
+ id: string;
11
+ team_id: string;
12
+ title: string;
13
+ slug: string;
14
+ excerpt: string;
15
+ content: string;
16
+ category: string;
17
+ tags: string[];
18
+ view_count: number;
19
+ published_at: string;
20
+ created_at: string;
21
+ updated_at: string;
22
+ meta_description?: string;
23
+ }
24
+ /** A category in the knowledge base. */
25
+ interface Category {
26
+ id: string;
27
+ team_id: string;
28
+ name: string;
29
+ slug: string;
30
+ description?: string;
31
+ icon?: string;
32
+ color?: string;
33
+ sort_order: number;
34
+ is_public: boolean;
35
+ article_count: number;
36
+ }
37
+ /** Parameters for listing articles. */
38
+ interface LibraryArticleListParams {
39
+ categorySlug?: string;
40
+ limit?: number;
41
+ offset?: number;
42
+ }
43
+ /** Parameters for searching articles. */
44
+ interface LibrarySearchParams {
45
+ limit?: number;
46
+ }
47
+ /** Search results. */
48
+ interface LibrarySearchResult {
49
+ data: LibraryArticle[];
50
+ count: number;
51
+ query: string;
52
+ }
53
+
54
+ /**
55
+ * Public knowledge base client. No authentication required.
56
+ *
57
+ * ```typescript
58
+ * const library = new LibraryClient({ teamSlug: 'acme' });
59
+ * const categories = await library.categories();
60
+ * const results = await library.search('reset password');
61
+ * ```
62
+ */
63
+ declare class LibraryClient {
64
+ private readonly baseUrl;
65
+ private readonly teamSlug;
66
+ private resolvedTeamId;
67
+ constructor(config: LibraryConfig);
68
+ /** List all public categories with article counts. */
69
+ categories(): Promise<Category[]>;
70
+ /** List published articles, optionally filtered by category. */
71
+ articles(params?: LibraryArticleListParams): Promise<LibraryArticle[]>;
72
+ /** Get a single article by slug. */
73
+ article(slug: string): Promise<LibraryArticle>;
74
+ /**
75
+ * Full-text search across article titles, excerpts, and content.
76
+ * Returns matching articles with relevance ranking.
77
+ */
78
+ search(query: string, params?: LibrarySearchParams): Promise<LibrarySearchResult>;
79
+ private fetch;
80
+ }
81
+
82
+ export { type Category, type LibraryArticle, type LibraryArticleListParams, LibraryClient, type LibraryConfig, type LibrarySearchParams, type LibrarySearchResult };
@@ -0,0 +1,72 @@
1
+ // src/library/index.ts
2
+ var DEFAULT_BASE_URL = "https://app.cstar.help";
3
+ var LibraryClient = class {
4
+ constructor(config) {
5
+ this.resolvedTeamId = null;
6
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
7
+ this.teamSlug = config.teamSlug;
8
+ }
9
+ /** List all public categories with article counts. */
10
+ async categories() {
11
+ const response = await this.fetch(
12
+ `/api/library/${this.teamSlug}/categories`
13
+ );
14
+ return response.data;
15
+ }
16
+ /** List published articles, optionally filtered by category. */
17
+ async articles(params) {
18
+ const query = {};
19
+ if (params?.categorySlug) query.categorySlug = params.categorySlug;
20
+ if (params?.limit) query.limit = String(params.limit);
21
+ if (params?.offset) query.offset = String(params.offset);
22
+ const response = await this.fetch(
23
+ `/api/library/${this.teamSlug}/articles`,
24
+ query
25
+ );
26
+ return response.data;
27
+ }
28
+ /** Get a single article by slug. */
29
+ async article(slug) {
30
+ const response = await this.fetch(
31
+ `/api/library/${this.teamSlug}/articles/${slug}`
32
+ );
33
+ return response.data;
34
+ }
35
+ /**
36
+ * Full-text search across article titles, excerpts, and content.
37
+ * Returns matching articles with relevance ranking.
38
+ */
39
+ async search(query, params) {
40
+ const queryParams = {
41
+ q: query
42
+ };
43
+ if (params?.limit) queryParams.limit = String(params.limit);
44
+ return this.fetch(
45
+ `/api/library/${this.teamSlug}/search`,
46
+ queryParams
47
+ );
48
+ }
49
+ async fetch(path, query) {
50
+ let url = `${this.baseUrl}${path}`;
51
+ if (query) {
52
+ const params = new URLSearchParams();
53
+ for (const [key, value] of Object.entries(query)) {
54
+ if (value !== void 0) params.set(key, value);
55
+ }
56
+ const qs = params.toString();
57
+ if (qs) url += `?${qs}`;
58
+ }
59
+ const response = await globalThis.fetch(url, {
60
+ method: "GET",
61
+ headers: { "Content-Type": "application/json" }
62
+ });
63
+ if (!response.ok) {
64
+ const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
65
+ throw new Error(errorBody.error?.message ?? `Library API error: ${response.status}`);
66
+ }
67
+ return response.json();
68
+ }
69
+ };
70
+ export {
71
+ LibraryClient
72
+ };