@baziapi/sdk 1.0.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 ADDED
@@ -0,0 +1,793 @@
1
+ class BaziError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = this.constructor.name;
5
+ Object.setPrototypeOf(this, new.target.prototype);
6
+ }
7
+ }
8
+
9
+ var __defProp$5 = Object.defineProperty;
10
+ var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
12
+ class ApiError extends BaziError {
13
+ constructor(statusCode, message, errors, requestId, retryAfter) {
14
+ super(message);
15
+ __publicField$5(this, "kind", "api_error");
16
+ /** HTTP status code returned by the server. */
17
+ __publicField$5(this, "statusCode");
18
+ /** Detailed server-side validation errors, if any. */
19
+ __publicField$5(this, "errors");
20
+ /** Request ID from the `X-Request-ID` response header, if present. */
21
+ __publicField$5(this, "requestId");
22
+ /**
23
+ * Raw `Retry-After` header value from the server response.
24
+ * Used internally by the retry layer — not intended for consumer use.
25
+ * @internal
26
+ */
27
+ __publicField$5(this, "_retryAfter");
28
+ this.statusCode = statusCode;
29
+ this.errors = errors;
30
+ this.requestId = requestId;
31
+ this._retryAfter = retryAfter ?? null;
32
+ }
33
+ }
34
+ class ValidationError extends BaziError {
35
+ constructor(field, message) {
36
+ super(message);
37
+ __publicField$5(this, "kind", "validation_error");
38
+ /** The name of the field that failed validation. */
39
+ __publicField$5(this, "field");
40
+ this.field = field;
41
+ }
42
+ }
43
+ class TimeoutError extends BaziError {
44
+ constructor(timeoutMs) {
45
+ super(`Request timed out after ${timeoutMs}ms`);
46
+ __publicField$5(this, "kind", "timeout_error");
47
+ /** The configured timeout in milliseconds. */
48
+ __publicField$5(this, "timeoutMs");
49
+ this.timeoutMs = timeoutMs;
50
+ }
51
+ }
52
+ class NetworkError extends BaziError {
53
+ constructor(cause) {
54
+ const message = cause instanceof Error ? `Network error: ${cause.message}` : "Network error: unknown failure";
55
+ super(message);
56
+ __publicField$5(this, "kind", "network_error");
57
+ /** The original error thrown by `fetch()`. */
58
+ __publicField$5(this, "cause");
59
+ this.cause = cause;
60
+ }
61
+ }
62
+
63
+ function sleep(ms) {
64
+ return new Promise((resolve) => setTimeout(resolve, ms));
65
+ }
66
+
67
+ const RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
68
+ const NON_RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([400, 401, 403, 404, 409, 422]);
69
+ function isRetryableStatus(statusCode) {
70
+ if (NON_RETRYABLE_STATUS_CODES.has(statusCode)) return false;
71
+ return RETRYABLE_STATUS_CODES.has(statusCode);
72
+ }
73
+ function computeBackoffDelay(attemptIndex, retryDelay, retryAfterMs) {
74
+ const backoff = retryDelay * 2 ** attemptIndex;
75
+ return retryAfterMs !== void 0 ? Math.max(backoff, retryAfterMs) : backoff;
76
+ }
77
+ function parseRetryAfterMs(value) {
78
+ if (value === null) return void 0;
79
+ const seconds = Number(value);
80
+ if (!Number.isNaN(seconds)) return seconds * 1e3;
81
+ const date = new Date(value);
82
+ if (!Number.isNaN(date.getTime())) {
83
+ return Math.max(0, date.getTime() - Date.now());
84
+ }
85
+ return void 0;
86
+ }
87
+
88
+ var __defProp$4 = Object.defineProperty;
89
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
90
+ var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, key + "" , value);
91
+ class HttpClient {
92
+ constructor(config) {
93
+ __publicField$4(this, "config");
94
+ this.config = config;
95
+ }
96
+ /**
97
+ * Performs a GET request.
98
+ * @param path - URL path relative to baseUrl.
99
+ * @param bearerToken - Optional JWT bearer token.
100
+ */
101
+ async get(path, bearerToken) {
102
+ return this.request({
103
+ method: "GET",
104
+ path,
105
+ authenticated: true,
106
+ ...bearerToken !== void 0 ? { bearerToken } : {}
107
+ });
108
+ }
109
+ /**
110
+ * Performs a POST request.
111
+ * @param path - URL path relative to baseUrl.
112
+ * @param body - JSON request body.
113
+ * @param bearerToken - Optional JWT bearer token.
114
+ */
115
+ async post(path, body, bearerToken) {
116
+ return this.request({
117
+ method: "POST",
118
+ path,
119
+ authenticated: true,
120
+ ...body !== void 0 ? { body } : {},
121
+ ...bearerToken !== void 0 ? { bearerToken } : {}
122
+ });
123
+ }
124
+ /**
125
+ * Performs a PATCH request.
126
+ * @param path - URL path relative to baseUrl.
127
+ * @param body - JSON request body.
128
+ * @param bearerToken - Optional JWT bearer token.
129
+ */
130
+ async patch(path, body, bearerToken) {
131
+ return this.request({
132
+ method: "PATCH",
133
+ path,
134
+ authenticated: true,
135
+ ...body !== void 0 ? { body } : {},
136
+ ...bearerToken !== void 0 ? { bearerToken } : {}
137
+ });
138
+ }
139
+ /**
140
+ * Performs a DELETE request.
141
+ * @param path - URL path relative to baseUrl.
142
+ * @param bearerToken - Optional JWT bearer token.
143
+ */
144
+ async delete(path, bearerToken) {
145
+ return this.request({
146
+ method: "DELETE",
147
+ path,
148
+ authenticated: true,
149
+ ...bearerToken !== void 0 ? { bearerToken } : {}
150
+ });
151
+ }
152
+ buildHeaders(options) {
153
+ const headers = {
154
+ "Content-Type": "application/json",
155
+ Accept: "application/json"
156
+ };
157
+ if (this.config.apiKey) {
158
+ headers["x-api-key"] = this.config.apiKey;
159
+ }
160
+ if (options.authenticated) {
161
+ const token = options.bearerToken ?? this.config.apiKey;
162
+ if (token) {
163
+ headers["Authorization"] = `Bearer ${token}`;
164
+ }
165
+ }
166
+ return headers;
167
+ }
168
+ buildUrl(path) {
169
+ const base = this.config.baseUrl.replace(/\/$/, "");
170
+ return `${base}${path}`;
171
+ }
172
+ async executeRequest(options) {
173
+ const url = this.buildUrl(options.path);
174
+ const headers = this.buildHeaders(options);
175
+ const controller = new AbortController();
176
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
177
+ let response;
178
+ try {
179
+ const fetchBody = options.body !== void 0 ? JSON.stringify(options.body) : null;
180
+ response = await fetch(url, {
181
+ method: options.method,
182
+ headers,
183
+ body: fetchBody,
184
+ signal: controller.signal
185
+ });
186
+ } catch (error) {
187
+ if (error instanceof Error && error.name === "AbortError") {
188
+ throw new TimeoutError(this.config.timeout);
189
+ }
190
+ throw new NetworkError(error);
191
+ } finally {
192
+ clearTimeout(timeoutId);
193
+ }
194
+ const requestId = response.headers.get("x-request-id") ?? void 0;
195
+ const retryAfter = response.headers.get("retry-after");
196
+ if (!response.ok) {
197
+ let body = {};
198
+ try {
199
+ body = await response.json();
200
+ } catch {
201
+ }
202
+ const apiError = new ApiError(
203
+ response.status,
204
+ typeof body.message === "string" ? body.message : `HTTP ${response.status}`,
205
+ Array.isArray(body["errors"]) ? body["errors"] : void 0,
206
+ requestId,
207
+ retryAfter
208
+ );
209
+ this.config.onError?.(apiError);
210
+ throw apiError;
211
+ }
212
+ const json = await response.json();
213
+ return json.data;
214
+ }
215
+ async request(options) {
216
+ let lastError;
217
+ for (let attempt = 0; attempt <= this.config.retries; attempt++) {
218
+ try {
219
+ return await this.executeRequest(options);
220
+ } catch (error) {
221
+ lastError = error;
222
+ const shouldRetry = (() => {
223
+ if (error instanceof ApiError) return isRetryableStatus(error.statusCode);
224
+ if (error instanceof NetworkError) return true;
225
+ return false;
226
+ })();
227
+ if (!shouldRetry || attempt >= this.config.retries) break;
228
+ const retryAfterMs = (() => {
229
+ if (error instanceof ApiError) {
230
+ return parseRetryAfterMs(error._retryAfter);
231
+ }
232
+ return void 0;
233
+ })();
234
+ const delay = computeBackoffDelay(attempt, this.config.retryDelay, retryAfterMs);
235
+ await sleep(delay);
236
+ }
237
+ }
238
+ throw lastError;
239
+ }
240
+ }
241
+
242
+ const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
243
+ const TIME_REGEX = /^\d{2}:\d{2}$/;
244
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
245
+ function assertRequired(field, value) {
246
+ if (value === void 0 || value === null || value === "") {
247
+ throw new ValidationError(field, `${field} is required`);
248
+ }
249
+ }
250
+ function assertString(field, value) {
251
+ if (typeof value !== "string") {
252
+ throw new ValidationError(field, `${field} must be a string`);
253
+ }
254
+ }
255
+ function assertMinLength(field, value, min) {
256
+ if (value.length < min) {
257
+ throw new ValidationError(field, `${field} must be at least ${min} characters`);
258
+ }
259
+ }
260
+ function assertExactLength(field, value, length) {
261
+ if (value.length !== length) {
262
+ throw new ValidationError(field, `${field} must be exactly ${length} characters`);
263
+ }
264
+ }
265
+ function assertPattern(field, value, pattern, hint) {
266
+ if (!pattern.test(value)) {
267
+ throw new ValidationError(field, `${field} ${hint}`);
268
+ }
269
+ }
270
+ function assertEnum(field, value, allowed) {
271
+ if (!allowed.includes(value)) {
272
+ throw new ValidationError(field, `${field} must be one of: ${allowed.join(", ")}`);
273
+ }
274
+ }
275
+ function assertCalendarDate(field, value) {
276
+ const [yearStr, monthStr, dayStr] = value.split("-");
277
+ const year = Number(yearStr);
278
+ const month = Number(monthStr);
279
+ const day = Number(dayStr);
280
+ const date = new Date(year, month - 1, day);
281
+ if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) {
282
+ throw new ValidationError(field, `${field} is not a valid calendar date`);
283
+ }
284
+ }
285
+ function validateBaziRequest(input) {
286
+ assertRequired("birthDate", input.birthDate);
287
+ assertString("birthDate", input.birthDate);
288
+ assertPattern("birthDate", input.birthDate, DATE_REGEX, "must be in YYYY-MM-DD format");
289
+ assertCalendarDate("birthDate", input.birthDate);
290
+ if (input.birthTime !== void 0 && input.birthTime !== "") {
291
+ assertString("birthTime", input.birthTime);
292
+ assertPattern("birthTime", input.birthTime, TIME_REGEX, "must be in HH:mm format");
293
+ }
294
+ assertRequired("gender", input.gender);
295
+ assertEnum("gender", input.gender, ["male", "female"]);
296
+ if (input.timezone !== void 0) {
297
+ assertString("timezone", input.timezone);
298
+ }
299
+ if (input.language !== void 0) {
300
+ assertEnum("language", input.language, ["en", "zh"]);
301
+ }
302
+ }
303
+ function validateRegisterRequest(input) {
304
+ assertRequired("name", input.name);
305
+ assertString("name", input.name);
306
+ assertRequired("email", input.email);
307
+ assertString("email", input.email);
308
+ assertPattern("email", input.email, EMAIL_REGEX, "must be a valid email address");
309
+ assertRequired("password", input.password);
310
+ assertString("password", input.password);
311
+ assertMinLength("password", input.password, 6);
312
+ if (input.country !== void 0) {
313
+ assertString("country", input.country);
314
+ }
315
+ }
316
+ function validateLoginRequest(input) {
317
+ assertRequired("email", input.email);
318
+ assertString("email", input.email);
319
+ assertPattern("email", input.email, EMAIL_REGEX, "must be a valid email address");
320
+ assertRequired("password", input.password);
321
+ assertString("password", input.password);
322
+ }
323
+ function validateVerifyEmailRequest(input) {
324
+ assertRequired("email", input.email);
325
+ assertString("email", input.email);
326
+ assertPattern("email", input.email, EMAIL_REGEX, "must be a valid email address");
327
+ assertRequired("otp", input.otp);
328
+ assertString("otp", input.otp);
329
+ assertExactLength("otp", input.otp, 6);
330
+ }
331
+ function validateForgotPasswordRequest(input) {
332
+ assertRequired("email", input.email);
333
+ assertString("email", input.email);
334
+ assertPattern("email", input.email, EMAIL_REGEX, "must be a valid email address");
335
+ }
336
+ function validateResetPasswordRequest(input) {
337
+ assertRequired("email", input.email);
338
+ assertString("email", input.email);
339
+ assertPattern("email", input.email, EMAIL_REGEX, "must be a valid email address");
340
+ assertRequired("otp", input.otp);
341
+ assertString("otp", input.otp);
342
+ assertExactLength("otp", input.otp, 6);
343
+ assertRequired("newPassword", input.newPassword);
344
+ assertString("newPassword", input.newPassword);
345
+ assertMinLength("newPassword", input.newPassword, 6);
346
+ }
347
+ function validateChangePasswordRequest(input) {
348
+ assertRequired("oldPassword", input.oldPassword);
349
+ assertString("oldPassword", input.oldPassword);
350
+ assertRequired("newPassword", input.newPassword);
351
+ assertString("newPassword", input.newPassword);
352
+ assertMinLength("newPassword", input.newPassword, 6);
353
+ }
354
+
355
+ const API_VERSION = "v1";
356
+
357
+ var __defProp$3 = Object.defineProperty;
358
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
359
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, key + "" , value);
360
+ class BaziResource {
361
+ /** @internal */
362
+ constructor(http) {
363
+ __publicField$3(this, "http");
364
+ this.http = http;
365
+ }
366
+ /**
367
+ * Calculate BaZi (Four Pillars of Destiny) for a given birth date and time.
368
+ *
369
+ * Validates input client-side before sending the request.
370
+ * Requires an API key set on the client.
371
+ *
372
+ * @param input - The birth information required for calculation.
373
+ * @returns A promise resolving to the full BaZi analysis result.
374
+ *
375
+ * @throws {ValidationError} If the input fails client-side validation.
376
+ * @throws {ApiError} If the server rejects the request (4xx/5xx).
377
+ * @throws {TimeoutError} If the request exceeds the configured timeout.
378
+ * @throws {NetworkError} If a network-level failure occurs.
379
+ *
380
+ * @example
381
+ * ```typescript
382
+ * const result = await client.bazi.calculate({
383
+ * birthDate: '1998-08-12',
384
+ * birthTime: '10:30',
385
+ * gender: 'male',
386
+ * timezone: 'Asia/Dhaka',
387
+ * language: 'en',
388
+ * });
389
+ *
390
+ * console.log(result.pillars);
391
+ * console.log(result.luckPillars?.pillars);
392
+ * ```
393
+ */
394
+ async calculate(input) {
395
+ validateBaziRequest(input);
396
+ return this.http.post(`/api/${API_VERSION}/bazi/calculate`, input);
397
+ }
398
+ }
399
+
400
+ var __defProp$2 = Object.defineProperty;
401
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
402
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, key + "" , value);
403
+ const AUTH_BASE = `/api/${API_VERSION}/auth`;
404
+ class AuthResource {
405
+ /** @internal */
406
+ constructor(http) {
407
+ __publicField$2(this, "http");
408
+ this.http = http;
409
+ }
410
+ /**
411
+ * Register a new user account.
412
+ *
413
+ * @param input - Registration data including name, email, and password.
414
+ * @returns A promise resolving to `null` on success (email verification required).
415
+ *
416
+ * @throws {ValidationError} If input is invalid.
417
+ * @throws {ApiError} If the email is already registered (409).
418
+ *
419
+ * @example
420
+ * ```typescript
421
+ * await client.auth.register({
422
+ * name: 'Jane Doe',
423
+ * email: 'jane@example.com',
424
+ * password: 'secret123',
425
+ * });
426
+ * ```
427
+ */
428
+ async register(input) {
429
+ validateRegisterRequest(input);
430
+ return this.http.post(`${AUTH_BASE}/register`, input);
431
+ }
432
+ /**
433
+ * Verify an email address using the OTP sent during registration.
434
+ *
435
+ * @param input - Email address and 6-digit OTP.
436
+ * @returns A promise resolving to `null` on success.
437
+ *
438
+ * @throws {ValidationError} If input is invalid.
439
+ * @throws {ApiError} If OTP is incorrect or expired (400).
440
+ */
441
+ async verifyEmail(input) {
442
+ validateVerifyEmailRequest(input);
443
+ return this.http.post(`${AUTH_BASE}/verify-email`, input);
444
+ }
445
+ /**
446
+ * Log in with email and password.
447
+ *
448
+ * @param input - Email and password credentials.
449
+ * @returns A promise resolving to access token, refresh token, and user data.
450
+ *
451
+ * @throws {ValidationError} If input is invalid.
452
+ * @throws {ApiError} If credentials are incorrect (401).
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * const { accessToken, user } = await client.auth.login({
457
+ * email: 'jane@example.com',
458
+ * password: 'secret123',
459
+ * });
460
+ * ```
461
+ */
462
+ async login(input) {
463
+ validateLoginRequest(input);
464
+ return this.http.post(`${AUTH_BASE}/login`, input);
465
+ }
466
+ /**
467
+ * Refresh an expired access token using a valid refresh token.
468
+ *
469
+ * @param refreshToken - A valid refresh token previously issued by `login()`.
470
+ * @returns A promise resolving to a new access token.
471
+ *
472
+ * @throws {ApiError} If the refresh token is invalid or expired (401).
473
+ */
474
+ async refreshToken(refreshToken) {
475
+ return this.http.post(
476
+ `${AUTH_BASE}/refresh-token`,
477
+ void 0,
478
+ refreshToken
479
+ );
480
+ }
481
+ /**
482
+ * Initiate a password reset flow by requesting an OTP via email.
483
+ *
484
+ * @param input - Email address of the account to reset.
485
+ * @returns A promise resolving to `null` on success.
486
+ *
487
+ * @throws {ValidationError} If the email is invalid.
488
+ * @throws {ApiError} If the email is not registered (404).
489
+ */
490
+ async forgotPassword(input) {
491
+ validateForgotPasswordRequest(input);
492
+ return this.http.post(`${AUTH_BASE}/forgot-password`, input);
493
+ }
494
+ /**
495
+ * Complete a password reset using the OTP received via email.
496
+ *
497
+ * @param input - Email, OTP, and new password.
498
+ * @returns A promise resolving to `null` on success.
499
+ *
500
+ * @throws {ValidationError} If input is invalid.
501
+ * @throws {ApiError} If OTP is incorrect or expired (400).
502
+ */
503
+ async resetPassword(input) {
504
+ validateResetPasswordRequest(input);
505
+ return this.http.post(`${AUTH_BASE}/reset-password`, input);
506
+ }
507
+ /**
508
+ * Change the authenticated user's password.
509
+ * Requires a valid JWT access token.
510
+ *
511
+ * @param input - Old password and new password.
512
+ * @param bearerToken - Valid JWT access token from `login()`.
513
+ * @returns A promise resolving to `null` on success.
514
+ *
515
+ * @throws {ValidationError} If input is invalid.
516
+ * @throws {ApiError} If old password is incorrect (401) or token is invalid.
517
+ */
518
+ async changePassword(input, bearerToken) {
519
+ validateChangePasswordRequest(input);
520
+ return this.http.post(`${AUTH_BASE}/change-password`, input, bearerToken);
521
+ }
522
+ /**
523
+ * Retrieve the authenticated user's profile.
524
+ * Requires a valid JWT access token.
525
+ *
526
+ * @param bearerToken - Valid JWT access token from `login()`.
527
+ * @returns A promise resolving to the user profile.
528
+ *
529
+ * @throws {ApiError} If the token is invalid or expired (401).
530
+ *
531
+ * @example
532
+ * ```typescript
533
+ * const profile = await client.auth.me(accessToken);
534
+ * console.log(profile.email);
535
+ * ```
536
+ */
537
+ async me(bearerToken) {
538
+ return this.http.get(`${AUTH_BASE}/me`, bearerToken);
539
+ }
540
+ /**
541
+ * Log out the authenticated user, invalidating their session.
542
+ * Requires a valid JWT access token.
543
+ *
544
+ * @param bearerToken - Valid JWT access token from `login()`.
545
+ * @returns A promise resolving to `null` on success.
546
+ *
547
+ * @throws {ApiError} If the token is invalid or expired (401).
548
+ */
549
+ async logout(bearerToken) {
550
+ return this.http.post(`${AUTH_BASE}/logout`, void 0, bearerToken);
551
+ }
552
+ }
553
+
554
+ var __defProp$1 = Object.defineProperty;
555
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
556
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, key + "" , value);
557
+ const API_KEY_BASE = `/api/${API_VERSION}/api-keys`;
558
+ class ApiKeyResource {
559
+ /** @internal */
560
+ constructor(http) {
561
+ __publicField$1(this, "http");
562
+ this.http = http;
563
+ }
564
+ /**
565
+ * List all API keys associated with the authenticated user's account.
566
+ * Only the key prefix is returned — not the full raw key.
567
+ *
568
+ * @param bearerToken - Valid JWT access token from `client.auth.login()`.
569
+ * @returns A promise resolving to an array of API key metadata.
570
+ *
571
+ * @throws {ApiError} If the token is invalid or expired (401).
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * const keys = await client.apiKeys.list(accessToken);
576
+ * keys.forEach(key => console.log(key.prefix, key.isActive));
577
+ * ```
578
+ */
579
+ async list(bearerToken) {
580
+ return this.http.get(API_KEY_BASE, bearerToken);
581
+ }
582
+ /**
583
+ * Generate a new API key for the authenticated user.
584
+ *
585
+ * **Important:** The full raw key is returned only once in this response.
586
+ * Store it securely — it cannot be retrieved again.
587
+ *
588
+ * @param bearerToken - Valid JWT access token from `client.auth.login()`.
589
+ * @returns A promise resolving to the newly created API key including the full raw key.
590
+ *
591
+ * @throws {ApiError} If the token is invalid or the user's plan limit is reached.
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * const { key, prefix } = await client.apiKeys.create(accessToken);
596
+ * // Store `key` securely — it won't be shown again
597
+ * console.log('Your API key:', key);
598
+ * ```
599
+ */
600
+ async create(bearerToken) {
601
+ return this.http.post(`${API_KEY_BASE}/generate`, void 0, bearerToken);
602
+ }
603
+ /**
604
+ * Revoke (deactivate) an API key by its ID.
605
+ * Revoked keys are permanently disabled and cannot be re-activated.
606
+ *
607
+ * @param keyId - The ID of the API key to revoke.
608
+ * @param bearerToken - Valid JWT access token from `client.auth.login()`.
609
+ * @returns A promise resolving to the revoked API key metadata.
610
+ *
611
+ * @throws {ApiError} If the key is not found (404) or doesn't belong to the user (403).
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * await client.apiKeys.revoke(keyId, accessToken);
616
+ * ```
617
+ */
618
+ async revoke(keyId, bearerToken) {
619
+ return this.http.patch(`${API_KEY_BASE}/${keyId}/revoke`, void 0, bearerToken);
620
+ }
621
+ }
622
+
623
+ function constantTimeEqual(a, b) {
624
+ if (a.length !== b.length) {
625
+ return false;
626
+ }
627
+ let result = 0;
628
+ for (let i = 0; i < a.length; i++) {
629
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
630
+ }
631
+ return result === 0;
632
+ }
633
+ function toUint8Array(data) {
634
+ if (typeof data === "string") {
635
+ return new TextEncoder().encode(data);
636
+ }
637
+ return data;
638
+ }
639
+ function bufferToHex(buffer) {
640
+ const byteArray = new Uint8Array(buffer);
641
+ let hex = "";
642
+ for (const byte of byteArray) {
643
+ hex += byte.toString(16).padStart(2, "0");
644
+ }
645
+ return hex;
646
+ }
647
+ class WebhookService {
648
+ /**
649
+ * Verifies an incoming webhook HMAC-SHA256 signature.
650
+ *
651
+ * @param options - Verification options including payload, signature, and secret.
652
+ * @returns `true` if the signature is valid; `false` otherwise.
653
+ *
654
+ * @example
655
+ * ```typescript
656
+ * import { BaziClient } from '@bazi/sdk';
657
+ *
658
+ * const client = new BaziClient();
659
+ * const isValid = await client.webhooks.verifySignature({
660
+ * payload: rawBody,
661
+ * signature: req.headers['x-bazi-signature'],
662
+ * secret: process.env.BAZI_WEBHOOK_SECRET!,
663
+ * });
664
+ *
665
+ * if (!isValid) {
666
+ * return res.status(401).send('Invalid signature');
667
+ * }
668
+ * ```
669
+ */
670
+ async verifySignature(options) {
671
+ const { payload, signature, secret } = options;
672
+ if (!payload || !signature || !secret) {
673
+ return false;
674
+ }
675
+ try {
676
+ const cleanSignature = signature.trim().toLowerCase();
677
+ const expectedSignature = await this.computeHmac(payload, secret);
678
+ return constantTimeEqual(cleanSignature, expectedSignature.toLowerCase());
679
+ } catch {
680
+ return false;
681
+ }
682
+ }
683
+ /**
684
+ * Computes an HMAC-SHA256 digest in hex string format using Web Crypto API.
685
+ *
686
+ * @param payload - Raw request payload.
687
+ * @param secret - Secret key.
688
+ * @returns Hexadecimal digest string.
689
+ */
690
+ async computeHmac(payload, secret) {
691
+ const encoder = new TextEncoder();
692
+ const keyData = encoder.encode(secret);
693
+ const dataBytes = toUint8Array(payload);
694
+ const cryptoSubtle = globalThis.crypto?.subtle;
695
+ if (!cryptoSubtle) {
696
+ throw new Error(
697
+ "Crypto API is not available in the current environment. Ensure Node.js 18+ or an environment with Web Crypto API support."
698
+ );
699
+ }
700
+ const cryptoKey = await cryptoSubtle.importKey(
701
+ "raw",
702
+ keyData,
703
+ { name: "HMAC", hash: "SHA-256" },
704
+ false,
705
+ ["sign"]
706
+ );
707
+ const signatureBuffer = await cryptoSubtle.sign(
708
+ "HMAC",
709
+ cryptoKey,
710
+ dataBytes
711
+ );
712
+ return bufferToHex(signatureBuffer);
713
+ }
714
+ }
715
+ async function verifyWebhookSignature(options) {
716
+ const service = new WebhookService();
717
+ return service.verifySignature(options);
718
+ }
719
+
720
+ var __defProp = Object.defineProperty;
721
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
722
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
723
+ const DEFAULT_BASE_URL = "https://api.bazi.dev";
724
+ const DEFAULT_TIMEOUT = 1e4;
725
+ const DEFAULT_RETRIES = 3;
726
+ const DEFAULT_RETRY_DELAY = 500;
727
+ class BaziClient {
728
+ /**
729
+ * Creates a new BaziClient instance.
730
+ *
731
+ * @param options - Client configuration options.
732
+ *
733
+ * @example
734
+ * ```typescript
735
+ * // Production usage
736
+ * const client = new BaziClient({ apiKey: 'bazi_xxxx' });
737
+ *
738
+ * // Development / self-hosted
739
+ * const client = new BaziClient({
740
+ * apiKey: 'bazi_xxxx',
741
+ * baseUrl: 'http://localhost:5000',
742
+ * timeout: 30_000,
743
+ * retries: 1,
744
+ * });
745
+ * ```
746
+ */
747
+ constructor(options = {}) {
748
+ __publicField(this, "httpClient");
749
+ /**
750
+ * BaZi calculation resource.
751
+ * Endpoint: `POST /api/v1/bazi/calculate`
752
+ */
753
+ __publicField(this, "bazi");
754
+ /**
755
+ * Authentication resource.
756
+ * Endpoints: register, login, logout, verifyEmail, forgotPassword, resetPassword, changePassword, me
757
+ */
758
+ __publicField(this, "auth");
759
+ /**
760
+ * API Key management resource.
761
+ * Endpoints: list, create, revoke
762
+ */
763
+ __publicField(this, "apiKeys");
764
+ /**
765
+ * Webhook utilities for verifying HMAC-SHA256 signatures.
766
+ */
767
+ __publicField(this, "webhooks");
768
+ this.httpClient = new HttpClient({
769
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
770
+ apiKey: options.apiKey ?? "",
771
+ timeout: options.timeout ?? DEFAULT_TIMEOUT,
772
+ retries: options.retries ?? DEFAULT_RETRIES,
773
+ retryDelay: options.retryDelay ?? DEFAULT_RETRY_DELAY,
774
+ onError: options.onError
775
+ });
776
+ this.bazi = new BaziResource(this.httpClient);
777
+ this.auth = new AuthResource(this.httpClient);
778
+ this.apiKeys = new ApiKeyResource(this.httpClient);
779
+ this.webhooks = new WebhookService();
780
+ }
781
+ /**
782
+ * Direct convenience method to calculate BaZi (Four Pillars of Destiny).
783
+ * Shortcut for `client.bazi.calculate(input)`.
784
+ *
785
+ * @param input - The birth information required for calculation.
786
+ * @returns A promise resolving to the full BaZi analysis result.
787
+ */
788
+ async calculate(input) {
789
+ return this.bazi.calculate(input);
790
+ }
791
+ }
792
+
793
+ export { API_VERSION, ApiError, BaziClient, BaziError, NetworkError, TimeoutError, ValidationError, WebhookService, verifyWebhookSignature };