zlient 2.1.10 → 3.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.js CHANGED
@@ -1,5 +1,3 @@
1
- import { z } from "zod";
2
-
3
1
  //#region lib/auth.ts
4
2
  /**
5
3
  * No-op authentication provider (no authentication applied).
@@ -73,7 +71,6 @@ var BearerTokenAuth = class {
73
71
  };
74
72
  }
75
73
  };
76
-
77
74
  //#endregion
78
75
  //#region lib/types.ts
79
76
  const HTTPMethod = {
@@ -162,23 +159,20 @@ const HTTPStatusCode = {
162
159
  * ```
163
160
  */
164
161
  var ApiError = class ApiError extends Error {
165
- status;
166
- details;
167
- zodError;
168
162
  constructor(message, options) {
169
163
  super(message);
170
164
  this.name = "ApiError";
171
165
  this.status = options?.status;
172
166
  this.details = options?.details;
173
167
  this.cause = options?.cause;
174
- this.zodError = options?.zodError;
168
+ this.validationIssues = options?.validationIssues;
175
169
  if (Error.captureStackTrace) Error.captureStackTrace(this, ApiError);
176
170
  }
177
171
  /**
178
- * Check if this is a validation error (has zodError)
172
+ * Check if this is a validation error (has validationIssues)
179
173
  */
180
174
  isValidationError() {
181
- return !!this.zodError;
175
+ return !!this.validationIssues && this.validationIssues.length > 0;
182
176
  }
183
177
  /**
184
178
  * Check if this is a client error (4xx status)
@@ -201,7 +195,7 @@ var ApiError = class ApiError extends Error {
201
195
  message: this.message,
202
196
  status: this.status,
203
197
  details: this.details,
204
- zodError: this.zodError?.issues,
198
+ validationIssues: this.validationIssues,
205
199
  stack: this.stack
206
200
  };
207
201
  }
@@ -219,15 +213,6 @@ var SchemaDefinitionError = class SchemaDefinitionError extends Error {
219
213
  }
220
214
  };
221
215
  /**
222
- * Schema for paginated responses
223
- */
224
- const PaginationSchema = z.object({
225
- items: z.array(z.unknown()),
226
- total: z.number().int().nonnegative(),
227
- page: z.number().int().nonnegative(),
228
- pageSize: z.number().int().positive()
229
- });
230
- /**
231
216
  * Converts query parameters to a URL query string.
232
217
  * Filters out undefined values automatically.
233
218
  *
@@ -243,8 +228,8 @@ const PaginationSchema = z.object({
243
228
  function toQueryString(q) {
244
229
  if (!q) return "";
245
230
  if (q instanceof URLSearchParams) {
246
- const s$1 = q.toString();
247
- return s$1 ? `?${s$1}` : "";
231
+ const s = q.toString();
232
+ return s ? `?${s}` : "";
248
233
  }
249
234
  const params = new URLSearchParams();
250
235
  Object.entries(q).forEach(([k, v]) => {
@@ -253,36 +238,88 @@ function toQueryString(q) {
253
238
  const s = params.toString();
254
239
  return s ? `?${s}` : "";
255
240
  }
256
-
257
241
  //#endregion
258
242
  //#region lib/validation.ts
259
243
  /**
260
- * Parse data with a Zod schema, throwing an ApiError on validation failure.
244
+ * Safely parse/validate data with any Standard Schema-compatible library (Zod, Valibot, ArkType, etc.).
245
+ * Returns a result object with success status and data or issues.
246
+ *
247
+ * @param schema - A Standard Schema-compatible validator
248
+ * @param data - Data to validate
249
+ * @returns Result object with success flag and data/issues
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * // Works with Zod
254
+ * import { z } from 'zod';
255
+ * const result = await safeParse(z.object({ name: z.string() }), userData);
256
+ *
257
+ * // Works with Valibot
258
+ * import * as v from 'valibot';
259
+ * const result = await safeParse(v.object({ name: v.string() }), userData);
260
+ *
261
+ * // Works with ArkType
262
+ * import { type } from 'arktype';
263
+ * const result = await safeParse(type({ name: 'string' }), userData);
264
+ *
265
+ * if (result.success) {
266
+ * console.log(result.data);
267
+ * } else {
268
+ * console.error(result.issues);
269
+ * }
270
+ * ```
271
+ */
272
+ async function safeParse(schema, data) {
273
+ const result = await schema["~standard"].validate(data);
274
+ if (result.issues) return {
275
+ success: false,
276
+ issues: result.issues
277
+ };
278
+ return {
279
+ success: true,
280
+ data: result.value
281
+ };
282
+ }
283
+ /**
284
+ * Parse/validate data with any Standard Schema-compatible library, throwing an ApiError on failure.
261
285
  * Use this when you want to fail fast on invalid data.
262
286
  *
263
- * @param schema - Zod schema to validate against
287
+ * @param schema - A Standard Schema-compatible validator
264
288
  * @param data - Data to validate
265
289
  * @returns Validated and typed data
266
- * @throws {ApiError} If validation fails
290
+ * @throws {ApiError} If validation fails (with validationIssues property)
267
291
  *
268
292
  * @example
269
293
  * ```ts
294
+ * // Works with any Standard Schema-compatible library
270
295
  * try {
271
- * const user = parseOrThrow(UserSchema, userData);
296
+ * const user = await parseOrThrow(UserSchema, userData);
272
297
  * console.log(user);
273
298
  * } catch (error) {
274
- * if (error instanceof ApiError && error.zodError) {
275
- * console.error('Validation failed:', error.zodError.issues);
299
+ * if (error instanceof ApiError && error.isValidationError()) {
300
+ * console.error('Validation failed:', error.validationIssues);
276
301
  * }
277
302
  * }
278
303
  * ```
279
304
  */
280
- function parseOrThrow(schema, data) {
281
- const res = schema.safeParse(data);
282
- if (!res.success) throw new ApiError("Response validation failed", { zodError: res.error });
283
- return res.data;
305
+ async function parseOrThrow(schema, data) {
306
+ const result = await schema["~standard"].validate(data);
307
+ if (result.issues) throw new ApiError(`Validation failed: ${result.issues.map((issue) => issue.message).join(", ")}`, { validationIssues: result.issues });
308
+ return result.value;
309
+ }
310
+ /**
311
+ * Type guard to check if a value is a Standard Schema-compatible validator.
312
+ * Handles both object-based schemas (Zod, Valibot) and function-based schemas (ArkType).
313
+ *
314
+ * @param value - Value to check
315
+ * @returns True if the value implements Standard Schema v1
316
+ */
317
+ function isStandardSchema(value) {
318
+ if (value === null || value === void 0) return false;
319
+ if (typeof value !== "object" && typeof value !== "function") return false;
320
+ const schema = value;
321
+ return "~standard" in schema && typeof schema["~standard"] === "object" && schema["~standard"] !== null && schema["~standard"].version === 1 && typeof schema["~standard"].validate === "function";
284
322
  }
285
-
286
323
  //#endregion
287
324
  //#region lib/endpoint/base-endpoint.ts
288
325
  var EndpointImpl = class {
@@ -291,21 +328,17 @@ var EndpointImpl = class {
291
328
  this.config = config;
292
329
  }
293
330
  async call(params) {
294
- const { data, query, pathParams, headers, signal } = params;
331
+ const { data, query, pathParams, signal } = params;
332
+ const headers = "headers" in params ? params.headers : void 0;
295
333
  const skipRequestValidation = this.config.advanced?.skipRequestValidation ?? false;
296
334
  const skipResponseValidation = this.config.advanced?.skipResponseValidation ?? false;
297
- if (!skipRequestValidation && this.config.request && data !== void 0) {
298
- const parsed = this.config.request.safeParse(data);
299
- if (!parsed.success) throw parsed.error;
300
- }
301
- if (!skipRequestValidation && this.config.query && query !== void 0) {
302
- const parsed = this.config.query.safeParse(query);
303
- if (!parsed.success) throw parsed.error;
304
- }
305
- if (!skipRequestValidation && this.config.pathParams && pathParams !== void 0) {
306
- const parsed = this.config.pathParams.safeParse(pathParams);
307
- if (!parsed.success) throw parsed.error;
335
+ if (this.config.mustHeaderKeys && this.config.mustHeaderKeys.length > 0) {
336
+ const missingHeaders = this.config.mustHeaderKeys.filter((key) => !headers || !(key in headers));
337
+ if (missingHeaders.length > 0) throw new Error(`Missing required header(s): ${missingHeaders.join(", ")}`);
308
338
  }
339
+ if (!skipRequestValidation && this.config.request && data !== void 0) await parseOrThrow(this.config.request, data);
340
+ if (!skipRequestValidation && this.config.query && query !== void 0) await parseOrThrow(this.config.query, query);
341
+ if (!skipRequestValidation && this.config.pathParams && pathParams !== void 0) await parseOrThrow(this.config.pathParams, pathParams);
309
342
  if (this.config.request && data === void 0) throw new Error("Missing required request body (data)");
310
343
  if (this.config.pathParams && pathParams === void 0) throw new Error("Missing required path parameters (pathParams)");
311
344
  let pathStr;
@@ -323,24 +356,23 @@ var EndpointImpl = class {
323
356
  });
324
357
  const schema = this.config.response;
325
358
  if (skipResponseValidation) return responseData;
326
- if (schema instanceof z.ZodType) return parseOrThrow(schema, responseData);
359
+ if (isStandardSchema(schema)) return await parseOrThrow(schema, responseData);
327
360
  const specificSchema = schema[status];
328
361
  if (!specificSchema) throw new SchemaDefinitionError(status);
329
- return parseOrThrow(specificSchema, responseData);
362
+ return await parseOrThrow(specificSchema, responseData);
330
363
  }
331
364
  };
332
-
333
365
  //#endregion
334
366
  //#region lib/logger.ts
335
367
  /**
336
368
  * Log levels for structured logging.
337
369
  */
338
- let LogLevel = /* @__PURE__ */ function(LogLevel$1) {
339
- LogLevel$1["DEBUG"] = "debug";
340
- LogLevel$1["INFO"] = "info";
341
- LogLevel$1["WARN"] = "warn";
342
- LogLevel$1["ERROR"] = "error";
343
- return LogLevel$1;
370
+ let LogLevel = /* @__PURE__ */ function(LogLevel) {
371
+ LogLevel["DEBUG"] = "debug";
372
+ LogLevel["INFO"] = "info";
373
+ LogLevel["WARN"] = "warn";
374
+ LogLevel["ERROR"] = "error";
375
+ return LogLevel;
344
376
  }({});
345
377
  /**
346
378
  * Default console logger implementation.
@@ -357,9 +389,7 @@ var ConsoleLogger = class {
357
389
  LogLevel.WARN,
358
390
  LogLevel.ERROR
359
391
  ];
360
- const entryLevelIndex = levels.indexOf(entry.level);
361
- const minLevelIndex = levels.indexOf(this.minLevel);
362
- if (entryLevelIndex < minLevelIndex) return;
392
+ if (levels.indexOf(entry.level) < levels.indexOf(this.minLevel)) return;
363
393
  const output = {
364
394
  ...entry,
365
395
  error: entry.error ? {
@@ -402,7 +432,7 @@ var LoggerUtil = class {
402
432
  this.logger.log({
403
433
  level: LogLevel.DEBUG,
404
434
  message,
405
- timestamp: new Date().toISOString(),
435
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
406
436
  context
407
437
  });
408
438
  }
@@ -410,7 +440,7 @@ var LoggerUtil = class {
410
440
  this.logger.log({
411
441
  level: LogLevel.INFO,
412
442
  message,
413
- timestamp: new Date().toISOString(),
443
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
414
444
  context
415
445
  });
416
446
  }
@@ -418,7 +448,7 @@ var LoggerUtil = class {
418
448
  this.logger.log({
419
449
  level: LogLevel.WARN,
420
450
  message,
421
- timestamp: new Date().toISOString(),
451
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
422
452
  context
423
453
  });
424
454
  }
@@ -426,13 +456,12 @@ var LoggerUtil = class {
426
456
  this.logger.log({
427
457
  level: LogLevel.ERROR,
428
458
  message,
429
- timestamp: new Date().toISOString(),
459
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
430
460
  context,
431
461
  error
432
462
  });
433
463
  }
434
464
  };
435
-
436
465
  //#endregion
437
466
  //#region lib/metrics.ts
438
467
  /**
@@ -446,9 +475,8 @@ var NoOpMetricsCollector = class {
446
475
  * Stores metrics in memory with configurable retention.
447
476
  */
448
477
  var InMemoryMetricsCollector = class {
449
- metrics = [];
450
- maxEntries;
451
478
  constructor(maxEntries = 1e3) {
479
+ this.metrics = [];
452
480
  this.maxEntries = maxEntries;
453
481
  }
454
482
  collect(metrics) {
@@ -507,7 +535,6 @@ var ConsoleMetricsCollector = class {
507
535
  console.log("[METRICS]", JSON.stringify(metrics));
508
536
  }
509
537
  };
510
-
511
538
  //#endregion
512
539
  //#region lib/http/http-client.ts
513
540
  /**
@@ -527,16 +554,6 @@ var ConsoleMetricsCollector = class {
527
554
  * ```
528
555
  */
529
556
  var HttpClient = class {
530
- fetchImpl;
531
- baseUrls;
532
- headers;
533
- interceptors;
534
- retry;
535
- timeoutMs;
536
- auth;
537
- logger;
538
- metrics;
539
- onUnauthenticated;
540
557
  /**
541
558
  * Creates a new HTTP client instance.
542
559
  *
@@ -677,8 +694,7 @@ var HttpClient = class {
677
694
  */
678
695
  async request(method, path, body, options) {
679
696
  const startTime = Date.now();
680
- const base = this.resolveBaseUrl(options?.baseUrlKey);
681
- let url = `${base}${path}${toQueryString(options?.query)}`;
697
+ let url = `${this.resolveBaseUrl(options?.baseUrlKey)}${path}${toQueryString(options?.query)}`;
682
698
  this.logger.debug("HTTP request initiated", {
683
699
  method,
684
700
  path,
@@ -716,7 +732,7 @@ var HttpClient = class {
716
732
  while (true) {
717
733
  let timeoutId;
718
734
  if (this.timeoutMs && !options?.signal) timeoutId = setTimeout(() => {
719
- const timeoutError = new Error("Request timeout");
735
+ const timeoutError = /* @__PURE__ */ new Error("Request timeout");
720
736
  timeoutError.name = "TimeoutError";
721
737
  controller.abort(timeoutError);
722
738
  }, this.timeoutMs);
@@ -733,8 +749,7 @@ var HttpClient = class {
733
749
  const req = new Request(url, init);
734
750
  const res = await this.fetchImpl(req);
735
751
  if (res.status === 401 && this.onUnauthenticated && !refreshAttempted) {
736
- const shouldRetry = await this.onUnauthenticated(res.clone());
737
- if (shouldRetry) {
752
+ if (await this.onUnauthenticated(res.clone())) {
738
753
  refreshAttempted = true;
739
754
  if (timeoutId) clearTimeout(timeoutId);
740
755
  continue;
@@ -759,7 +774,7 @@ var HttpClient = class {
759
774
  path,
760
775
  status: res.status,
761
776
  durationMs: duration,
762
- timestamp: new Date().toISOString(),
777
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
763
778
  success: true
764
779
  });
765
780
  return {
@@ -778,7 +793,7 @@ var HttpClient = class {
778
793
  path,
779
794
  status: error instanceof ApiError ? error.status : void 0,
780
795
  durationMs: duration,
781
- timestamp: new Date().toISOString(),
796
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
782
797
  success: false,
783
798
  error: error instanceof Error ? error.message : String(error)
784
799
  });
@@ -794,8 +809,7 @@ var HttpClient = class {
794
809
  if (errorName === "AbortError" || errorName === "TimeoutError") return false;
795
810
  }
796
811
  if (error instanceof ApiError && error.status) {
797
- const retryCodes = this.retry.retryStatusCodes;
798
- if (retryCodes?.some((codeKey) => HTTPStatusCode[codeKey] === error.status)) return true;
812
+ if (this.retry.retryStatusCodes?.some((codeKey) => HTTPStatusCode[codeKey] === error.status)) return true;
799
813
  }
800
814
  return false;
801
815
  };
@@ -859,98 +873,38 @@ var HttpClient = class {
859
873
  }
860
874
  /**
861
875
  * Create a strongly-typed endpoint builder.
876
+ * Works with any Standard Schema-compatible library (Zod, Valibot, ArkType, etc.)
877
+ *
878
+ * @param config - Endpoint configuration with schemas
879
+ * @returns Endpoint call function
880
+ *
881
+ * @example
882
+ * ```ts
883
+ * // With Zod
884
+ * import { z } from 'zod';
885
+ * const getUser = client.createEndpoint({
886
+ * method: 'GET',
887
+ * path: '/users/:id',
888
+ * response: z.object({ id: z.string(), name: z.string() }),
889
+ * pathParams: z.object({ id: z.string() }),
890
+ * });
862
891
  *
863
- * @param config - Endpoint configuration
864
- * @returns Endpoint instance
892
+ * // With Valibot
893
+ * import * as v from 'valibot';
894
+ * const getUser = client.createEndpoint({
895
+ * method: 'GET',
896
+ * path: '/users/:id',
897
+ * response: v.object({ id: v.string(), name: v.string() }),
898
+ * pathParams: v.object({ id: v.string() }),
899
+ * });
900
+ * ```
865
901
  */
866
902
  createEndpoint(config) {
867
903
  const endpoint = new EndpointImpl(this, config);
868
904
  return (params) => endpoint.call(params);
869
905
  }
870
906
  };
871
-
872
907
  //#endregion
873
- //#region lib/schemas/common.ts
874
- /**
875
- * Common ID type that supports strings, numbers, or UUIDs.
876
- * Use this for entity identifiers in your schemas.
877
- *
878
- * @example
879
- * ```ts
880
- * const UserSchema = z.object({ id: Id, name: z.string() });
881
- * ```
882
- */
883
- const Id = z.union([
884
- z.string().min(1),
885
- z.number(),
886
- z.uuid({ version: "v4" })
887
- ]);
888
- /**
889
- * Common timestamp fields for entities.
890
- * Use this for database models with creation/update tracking.
891
- *
892
- * @example
893
- * ```ts
894
- * const UserSchema = z.object({
895
- * id: Id,
896
- * name: z.string(),
897
- * ...Timestamps.shape
898
- * });
899
- * ```
900
- */
901
- const Timestamps = z.object({
902
- createdAt: z.iso.datetime(),
903
- updatedAt: z.iso.datetime()
904
- });
905
- /**
906
- * Metadata information typically included in API responses.
907
- * Contains request tracking and debugging information.
908
- */
909
- const Meta = z.object({ timestamp: z.iso.datetime().optional() });
910
- /**
911
- * Detailed error information for a specific field or path.
912
- */
913
- const ErrorDetail = z.object({
914
- path: z.string().optional(),
915
- message: z.string()
916
- });
917
- /**
918
- * Standard API error response schema.
919
- * Use this for consistent error handling across your API.
920
- */
921
- const ApiErrorSchema = z.object({
922
- code: z.string(),
923
- message: z.string(),
924
- details: z.array(ErrorDetail).optional()
925
- });
926
- /**
927
- * Generic envelope wrapper for API responses.
928
- * Provides consistent structure with success flag, data, error, and metadata.
929
- *
930
- * @param inner - Zod schema for the response data
931
- * @returns Envelope schema wrapping the inner schema
932
- *
933
- * @example
934
- * ```ts
935
- * const UserResponseSchema = Envelope(z.object({ id: Id, name: z.string() }));
936
- *
937
- * // Response structure:
938
- * // {
939
- * // success: true,
940
- * // data: { id: 1, name: 'John' },
941
- * // meta: { timestamp: '...' }
942
- * // }
943
- * ```
944
- */
945
- const Envelope = (inner, meta) => {
946
- return z.object({
947
- success: z.boolean(),
948
- data: inner.optional().nullable(),
949
- error: ApiErrorSchema.optional(),
950
- meta: meta !== void 0 ? meta : Meta.optional()
951
- });
952
- };
908
+ export { ApiError, ApiKeyAuth, BearerTokenAuth, ConsoleLogger, ConsoleMetricsCollector, EndpointImpl, HTTPMethod, HTTPStatusCode, HttpClient, InMemoryMetricsCollector, LogLevel, LoggerUtil, NoAuth, NoOpLogger, NoOpMetricsCollector, SchemaDefinitionError, isStandardSchema, parseOrThrow, safeParse, toQueryString };
953
909
 
954
- //#endregion
955
- export { ApiError, ApiErrorSchema, ApiKeyAuth, BearerTokenAuth, ConsoleLogger, ConsoleMetricsCollector, EndpointImpl, Envelope, ErrorDetail, HTTPMethod, HTTPStatusCode, HttpClient, Id, InMemoryMetricsCollector, LogLevel, LoggerUtil, Meta, NoAuth, NoOpLogger, NoOpMetricsCollector, PaginationSchema, SchemaDefinitionError, Timestamps, toQueryString };
956
910
  //# sourceMappingURL=index.js.map