@adpal/shared-lib 1.0.7 → 1.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.d.mts CHANGED
@@ -841,4 +841,103 @@ declare function safeParse<T>(schema: z.ZodType<T>, data: unknown): T | null;
841
841
  */
842
842
  declare function parseOrThrow<T>(schema: z.ZodType<T>, data: unknown, errorMessage?: string): T;
843
843
 
844
- export { AnthropicApiError, ApiError, AppError, type ChatRequest, ChatRequestSchema, type ClaudeMessage, ClaudeMessageSchema, ConfigError, type FallbackOptions, type FallbackResult, type LogLevel, type LogMeta, Logger, type RetryOptions, type RetryResult, type Source, SourceSchema, TurbopufferApiError, type TurbopufferSearchResult, TurbopufferSearchResultSchema, ValidationError, type VectorAttributes, VectorAttributesSchema, VoyageApiError, type VoyageEmbeddingResponse, VoyageEmbeddingResponseSchema, type VoyageRerankResponse, VoyageRerankResponseSchema, anthropicLogger, apiLogger, createTimer, isAppError, logger, parseOrThrow, retryApiCall, retryCritical, safeParse, sleep, turbopufferLogger, voyageLogger, withFallback, withFallbackInfo, withRetry, wrapError };
844
+ /**
845
+ * Стандартизированные типы API ответов
846
+ *
847
+ * Обеспечивает единый формат для всех API endpoints.
848
+ *
849
+ * @module api-response
850
+ */
851
+ /**
852
+ * Успешный ответ API
853
+ */
854
+ interface ApiSuccessResponse<T> {
855
+ success: true;
856
+ data: T;
857
+ meta?: {
858
+ requestId?: string;
859
+ timestamp?: string;
860
+ duration_ms?: number;
861
+ pagination?: PaginationMeta;
862
+ };
863
+ }
864
+ /**
865
+ * Ответ API с ошибкой
866
+ */
867
+ interface ApiErrorResponse {
868
+ success: false;
869
+ error: {
870
+ code: string;
871
+ message: string;
872
+ details?: unknown;
873
+ };
874
+ meta?: {
875
+ requestId?: string;
876
+ timestamp?: string;
877
+ };
878
+ }
879
+ /**
880
+ * Объединённый тип ответа API
881
+ */
882
+ type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
883
+ /**
884
+ * Метаданные пагинации
885
+ */
886
+ interface PaginationMeta {
887
+ page: number;
888
+ pageSize: number;
889
+ totalItems: number;
890
+ totalPages: number;
891
+ hasMore: boolean;
892
+ }
893
+ /**
894
+ * Создать успешный ответ
895
+ *
896
+ * @example
897
+ * return Response.json(success(users, { pagination }));
898
+ */
899
+ declare function success<T>(data: T, meta?: ApiSuccessResponse<T>['meta']): ApiSuccessResponse<T>;
900
+ /**
901
+ * Создать ответ с ошибкой
902
+ *
903
+ * @example
904
+ * return Response.json(error('NOT_FOUND', 'User not found'), { status: 404 });
905
+ */
906
+ declare function error(code: string, message: string, details?: unknown, requestId?: string): ApiErrorResponse;
907
+ /**
908
+ * Создать метаданные пагинации
909
+ *
910
+ * @example
911
+ * const pagination = paginate(1, 20, 100);
912
+ * // { page: 1, pageSize: 20, totalItems: 100, totalPages: 5, hasMore: true }
913
+ */
914
+ declare function paginate(page: number, pageSize: number, totalItems: number): PaginationMeta;
915
+ /**
916
+ * Ответ со списком и пагинацией
917
+ */
918
+ interface ListResponse<T> {
919
+ items: T[];
920
+ pagination: PaginationMeta;
921
+ }
922
+ /**
923
+ * Создать ответ со списком
924
+ *
925
+ * @example
926
+ * return Response.json(success(list(users, 1, 20, 100)));
927
+ */
928
+ declare function list<T>(items: T[], page: number, pageSize: number, totalItems: number): ListResponse<T>;
929
+ declare const ErrorCodes: {
930
+ readonly BAD_REQUEST: "BAD_REQUEST";
931
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
932
+ readonly FORBIDDEN: "FORBIDDEN";
933
+ readonly NOT_FOUND: "NOT_FOUND";
934
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
935
+ readonly RATE_LIMITED: "RATE_LIMITED";
936
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
937
+ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
938
+ readonly EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR";
939
+ readonly DATABASE_ERROR: "DATABASE_ERROR";
940
+ };
941
+ type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
942
+
943
+ export { AnthropicApiError, ApiError, type ApiErrorResponse, type ApiResponse, type ApiSuccessResponse, AppError, type ChatRequest, ChatRequestSchema, type ClaudeMessage, ClaudeMessageSchema, ConfigError, type ErrorCode, ErrorCodes, type FallbackOptions, type FallbackResult, type ListResponse, type LogLevel, type LogMeta, Logger, type PaginationMeta, type RetryOptions, type RetryResult, type Source, SourceSchema, TurbopufferApiError, type TurbopufferSearchResult, TurbopufferSearchResultSchema, ValidationError, type VectorAttributes, VectorAttributesSchema, VoyageApiError, type VoyageEmbeddingResponse, VoyageEmbeddingResponseSchema, type VoyageRerankResponse, VoyageRerankResponseSchema, anthropicLogger, apiLogger, createTimer, error, isAppError, list, logger, paginate, parseOrThrow, retryApiCall, retryCritical, safeParse, sleep, success, turbopufferLogger, voyageLogger, withFallback, withFallbackInfo, withRetry, wrapError };
package/dist/index.d.ts CHANGED
@@ -841,4 +841,103 @@ declare function safeParse<T>(schema: z.ZodType<T>, data: unknown): T | null;
841
841
  */
842
842
  declare function parseOrThrow<T>(schema: z.ZodType<T>, data: unknown, errorMessage?: string): T;
843
843
 
844
- export { AnthropicApiError, ApiError, AppError, type ChatRequest, ChatRequestSchema, type ClaudeMessage, ClaudeMessageSchema, ConfigError, type FallbackOptions, type FallbackResult, type LogLevel, type LogMeta, Logger, type RetryOptions, type RetryResult, type Source, SourceSchema, TurbopufferApiError, type TurbopufferSearchResult, TurbopufferSearchResultSchema, ValidationError, type VectorAttributes, VectorAttributesSchema, VoyageApiError, type VoyageEmbeddingResponse, VoyageEmbeddingResponseSchema, type VoyageRerankResponse, VoyageRerankResponseSchema, anthropicLogger, apiLogger, createTimer, isAppError, logger, parseOrThrow, retryApiCall, retryCritical, safeParse, sleep, turbopufferLogger, voyageLogger, withFallback, withFallbackInfo, withRetry, wrapError };
844
+ /**
845
+ * Стандартизированные типы API ответов
846
+ *
847
+ * Обеспечивает единый формат для всех API endpoints.
848
+ *
849
+ * @module api-response
850
+ */
851
+ /**
852
+ * Успешный ответ API
853
+ */
854
+ interface ApiSuccessResponse<T> {
855
+ success: true;
856
+ data: T;
857
+ meta?: {
858
+ requestId?: string;
859
+ timestamp?: string;
860
+ duration_ms?: number;
861
+ pagination?: PaginationMeta;
862
+ };
863
+ }
864
+ /**
865
+ * Ответ API с ошибкой
866
+ */
867
+ interface ApiErrorResponse {
868
+ success: false;
869
+ error: {
870
+ code: string;
871
+ message: string;
872
+ details?: unknown;
873
+ };
874
+ meta?: {
875
+ requestId?: string;
876
+ timestamp?: string;
877
+ };
878
+ }
879
+ /**
880
+ * Объединённый тип ответа API
881
+ */
882
+ type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
883
+ /**
884
+ * Метаданные пагинации
885
+ */
886
+ interface PaginationMeta {
887
+ page: number;
888
+ pageSize: number;
889
+ totalItems: number;
890
+ totalPages: number;
891
+ hasMore: boolean;
892
+ }
893
+ /**
894
+ * Создать успешный ответ
895
+ *
896
+ * @example
897
+ * return Response.json(success(users, { pagination }));
898
+ */
899
+ declare function success<T>(data: T, meta?: ApiSuccessResponse<T>['meta']): ApiSuccessResponse<T>;
900
+ /**
901
+ * Создать ответ с ошибкой
902
+ *
903
+ * @example
904
+ * return Response.json(error('NOT_FOUND', 'User not found'), { status: 404 });
905
+ */
906
+ declare function error(code: string, message: string, details?: unknown, requestId?: string): ApiErrorResponse;
907
+ /**
908
+ * Создать метаданные пагинации
909
+ *
910
+ * @example
911
+ * const pagination = paginate(1, 20, 100);
912
+ * // { page: 1, pageSize: 20, totalItems: 100, totalPages: 5, hasMore: true }
913
+ */
914
+ declare function paginate(page: number, pageSize: number, totalItems: number): PaginationMeta;
915
+ /**
916
+ * Ответ со списком и пагинацией
917
+ */
918
+ interface ListResponse<T> {
919
+ items: T[];
920
+ pagination: PaginationMeta;
921
+ }
922
+ /**
923
+ * Создать ответ со списком
924
+ *
925
+ * @example
926
+ * return Response.json(success(list(users, 1, 20, 100)));
927
+ */
928
+ declare function list<T>(items: T[], page: number, pageSize: number, totalItems: number): ListResponse<T>;
929
+ declare const ErrorCodes: {
930
+ readonly BAD_REQUEST: "BAD_REQUEST";
931
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
932
+ readonly FORBIDDEN: "FORBIDDEN";
933
+ readonly NOT_FOUND: "NOT_FOUND";
934
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
935
+ readonly RATE_LIMITED: "RATE_LIMITED";
936
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
937
+ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
938
+ readonly EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR";
939
+ readonly DATABASE_ERROR: "DATABASE_ERROR";
940
+ };
941
+ type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
942
+
943
+ export { AnthropicApiError, ApiError, type ApiErrorResponse, type ApiResponse, type ApiSuccessResponse, AppError, type ChatRequest, ChatRequestSchema, type ClaudeMessage, ClaudeMessageSchema, ConfigError, type ErrorCode, ErrorCodes, type FallbackOptions, type FallbackResult, type ListResponse, type LogLevel, type LogMeta, Logger, type PaginationMeta, type RetryOptions, type RetryResult, type Source, SourceSchema, TurbopufferApiError, type TurbopufferSearchResult, TurbopufferSearchResultSchema, ValidationError, type VectorAttributes, VectorAttributesSchema, VoyageApiError, type VoyageEmbeddingResponse, VoyageEmbeddingResponseSchema, type VoyageRerankResponse, VoyageRerankResponseSchema, anthropicLogger, apiLogger, createTimer, error, isAppError, list, logger, paginate, parseOrThrow, retryApiCall, retryCritical, safeParse, sleep, success, turbopufferLogger, voyageLogger, withFallback, withFallbackInfo, withRetry, wrapError };
package/dist/index.js CHANGED
@@ -122,21 +122,21 @@ var ConfigError = class extends AppError {
122
122
  function isRetryableStatus(statusCode) {
123
123
  return [408, 429, 500, 502, 503, 504].includes(statusCode);
124
124
  }
125
- function isAppError(error) {
126
- return error instanceof AppError;
125
+ function isAppError(error2) {
126
+ return error2 instanceof AppError;
127
127
  }
128
- function wrapError(error, defaultMessage = "\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430") {
129
- if (error instanceof AppError) {
130
- return error;
131
- }
132
- if (error instanceof Error) {
133
- return new AppError(error.message || defaultMessage, {
134
- cause: error,
135
- context: { originalName: error.name }
128
+ function wrapError(error2, defaultMessage = "\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430") {
129
+ if (error2 instanceof AppError) {
130
+ return error2;
131
+ }
132
+ if (error2 instanceof Error) {
133
+ return new AppError(error2.message || defaultMessage, {
134
+ cause: error2,
135
+ context: { originalName: error2.name }
136
136
  });
137
137
  }
138
138
  return new AppError(defaultMessage, {
139
- context: { originalError: error }
139
+ context: { originalError: error2 }
140
140
  });
141
141
  }
142
142
 
@@ -189,11 +189,11 @@ function formatLogEntry(entry) {
189
189
  }
190
190
  return JSON.stringify(entry);
191
191
  }
192
- function serializeError(error) {
192
+ function serializeError(error2) {
193
193
  return {
194
- name: error.name,
195
- message: error.message,
196
- stack: error.stack
194
+ name: error2.name,
195
+ message: error2.message,
196
+ stack: error2.stack
197
197
  };
198
198
  }
199
199
  var Logger = class {
@@ -212,7 +212,7 @@ var Logger = class {
212
212
  /**
213
213
  * Записать лог
214
214
  */
215
- log(level, message, meta, error) {
215
+ log(level, message, meta, error2) {
216
216
  if (!this.isEnabled(level)) return;
217
217
  const entry = {
218
218
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -220,8 +220,8 @@ var Logger = class {
220
220
  message,
221
221
  meta: { module: this.module, ...meta }
222
222
  };
223
- if (error) {
224
- entry.error = serializeError(error);
223
+ if (error2) {
224
+ entry.error = serializeError(error2);
225
225
  }
226
226
  const formatted = formatLogEntry(entry);
227
227
  switch (level) {
@@ -256,8 +256,8 @@ var Logger = class {
256
256
  /**
257
257
  * Error уровень — ошибки
258
258
  */
259
- error(message, error, meta) {
260
- const err = error instanceof Error ? error : void 0;
259
+ error(message, error2, meta) {
260
+ const err = error2 instanceof Error ? error2 : void 0;
261
261
  this.log("error", message, meta, err);
262
262
  }
263
263
  /**
@@ -281,8 +281,8 @@ var ChildLogger = class {
281
281
  warn(message, meta) {
282
282
  this.parent.warn(message, { ...this.context, ...meta });
283
283
  }
284
- error(message, error, meta) {
285
- this.parent.error(message, error, { ...this.context, ...meta });
284
+ error(message, error2, meta) {
285
+ this.parent.error(message, error2, { ...this.context, ...meta });
286
286
  }
287
287
  };
288
288
  var voyageLogger = new Logger("voyage");
@@ -309,12 +309,12 @@ function calculateDelay(attempt, baseDelayMs, maxDelayMs, jitter) {
309
309
  const jitterAmount = cappedDelay * jitter * Math.random();
310
310
  return Math.round(cappedDelay + jitterAmount);
311
311
  }
312
- function defaultShouldRetry(error) {
313
- if (isAppError(error) && error.retryable) {
312
+ function defaultShouldRetry(error2) {
313
+ if (isAppError(error2) && error2.retryable) {
314
314
  return true;
315
315
  }
316
- if (error instanceof Error) {
317
- const message = error.message.toLowerCase();
316
+ if (error2 instanceof Error) {
317
+ const message = error2.message.toLowerCase();
318
318
  const networkErrors = [
319
319
  "fetch failed",
320
320
  "network error",
@@ -347,24 +347,24 @@ async function withRetry(fn, options = {}) {
347
347
  attempts: attempt,
348
348
  totalTimeMs: Math.round(performance.now() - startTime)
349
349
  };
350
- } catch (error) {
351
- lastError = error;
350
+ } catch (error2) {
351
+ lastError = error2;
352
352
  if (attempt >= maxAttempts) {
353
- logger2?.error(`\u0412\u0441\u0435 ${maxAttempts} \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B`, error);
353
+ logger2?.error(`\u0412\u0441\u0435 ${maxAttempts} \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B`, error2);
354
354
  break;
355
355
  }
356
- if (!shouldRetry(error, attempt)) {
356
+ if (!shouldRetry(error2, attempt)) {
357
357
  logger2?.warn(`\u041E\u0448\u0438\u0431\u043A\u0430 \u043D\u0435 \u043F\u043E\u0434\u043B\u0435\u0436\u0438\u0442 \u043F\u043E\u0432\u0442\u043E\u0440\u0435\u043D\u0438\u044E \u043D\u0430 \u043F\u043E\u043F\u044B\u0442\u043A\u0435 ${attempt}`, {
358
- errorMessage: error instanceof Error ? error.message : String(error)
358
+ errorMessage: error2 instanceof Error ? error2.message : String(error2)
359
359
  });
360
360
  break;
361
361
  }
362
362
  const delayMs = calculateDelay(attempt, baseDelayMs, maxDelayMs, jitter);
363
363
  logger2?.info(`\u041F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempt}/${maxAttempts} \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C, \u043F\u043E\u0432\u0442\u043E\u0440 \u0447\u0435\u0440\u0435\u0437 ${delayMs}\u043C\u0441`, {
364
- errorMessage: error instanceof Error ? error.message : String(error),
364
+ errorMessage: error2 instanceof Error ? error2.message : String(error2),
365
365
  delayMs
366
366
  });
367
- onRetry?.(error, attempt, delayMs);
367
+ onRetry?.(error2, attempt, delayMs);
368
368
  await sleep(delayMs);
369
369
  }
370
370
  }
@@ -399,11 +399,11 @@ async function withFallback(fn, options) {
399
399
  try {
400
400
  const result = await withRetry(fn, { ...retryOptions, logger: logger2 });
401
401
  return result.data;
402
- } catch (error) {
402
+ } catch (error2) {
403
403
  if (!silent && logger2) {
404
404
  logger2.warn("\u041E\u043F\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C fallback", {
405
- errorMessage: error instanceof Error ? error.message : String(error),
406
- errorType: error instanceof Error ? error.constructor.name : typeof error
405
+ errorMessage: error2 instanceof Error ? error2.message : String(error2),
406
+ errorType: error2 instanceof Error ? error2.constructor.name : typeof error2
407
407
  });
408
408
  }
409
409
  return typeof fallback === "function" ? fallback() : fallback;
@@ -417,17 +417,17 @@ async function withFallbackInfo(fn, options) {
417
417
  data: result.data,
418
418
  usedFallback: false
419
419
  };
420
- } catch (error) {
420
+ } catch (error2) {
421
421
  if (!silent && logger2) {
422
422
  logger2.warn("\u041E\u043F\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C fallback", {
423
- errorMessage: error instanceof Error ? error.message : String(error)
423
+ errorMessage: error2 instanceof Error ? error2.message : String(error2)
424
424
  });
425
425
  }
426
426
  const fallbackValue = typeof fallback === "function" ? fallback() : fallback;
427
427
  return {
428
428
  data: fallbackValue,
429
429
  usedFallback: true,
430
- error: error instanceof Error ? error : new Error(String(error))
430
+ error: error2 instanceof Error ? error2 : new Error(String(error2))
431
431
  };
432
432
  }
433
433
  }
@@ -581,12 +581,72 @@ function parseOrThrow(schema, data, errorMessage = "Validation failed") {
581
581
  return result.data;
582
582
  }
583
583
 
584
+ // src/api-response.ts
585
+ function success(data, meta) {
586
+ return {
587
+ success: true,
588
+ data,
589
+ meta: {
590
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
591
+ ...meta
592
+ }
593
+ };
594
+ }
595
+ function error(code, message, details, requestId) {
596
+ const errorObj = { code, message };
597
+ if (details !== void 0) {
598
+ errorObj.details = details;
599
+ }
600
+ const meta = {
601
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
602
+ };
603
+ if (requestId) {
604
+ meta.requestId = requestId;
605
+ }
606
+ return {
607
+ success: false,
608
+ error: errorObj,
609
+ meta
610
+ };
611
+ }
612
+ function paginate(page, pageSize, totalItems) {
613
+ const totalPages = Math.ceil(totalItems / pageSize);
614
+ return {
615
+ page,
616
+ pageSize,
617
+ totalItems,
618
+ totalPages,
619
+ hasMore: page < totalPages
620
+ };
621
+ }
622
+ function list(items, page, pageSize, totalItems) {
623
+ return {
624
+ items,
625
+ pagination: paginate(page, pageSize, totalItems)
626
+ };
627
+ }
628
+ var ErrorCodes = {
629
+ // Client errors (4xx)
630
+ BAD_REQUEST: "BAD_REQUEST",
631
+ UNAUTHORIZED: "UNAUTHORIZED",
632
+ FORBIDDEN: "FORBIDDEN",
633
+ NOT_FOUND: "NOT_FOUND",
634
+ VALIDATION_ERROR: "VALIDATION_ERROR",
635
+ RATE_LIMITED: "RATE_LIMITED",
636
+ // Server errors (5xx)
637
+ INTERNAL_ERROR: "INTERNAL_ERROR",
638
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
639
+ EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR",
640
+ DATABASE_ERROR: "DATABASE_ERROR"
641
+ };
642
+
584
643
  exports.AnthropicApiError = AnthropicApiError;
585
644
  exports.ApiError = ApiError;
586
645
  exports.AppError = AppError;
587
646
  exports.ChatRequestSchema = ChatRequestSchema;
588
647
  exports.ClaudeMessageSchema = ClaudeMessageSchema;
589
648
  exports.ConfigError = ConfigError;
649
+ exports.ErrorCodes = ErrorCodes;
590
650
  exports.Logger = Logger;
591
651
  exports.SourceSchema = SourceSchema;
592
652
  exports.TurbopufferApiError = TurbopufferApiError;
@@ -599,13 +659,17 @@ exports.VoyageRerankResponseSchema = VoyageRerankResponseSchema;
599
659
  exports.anthropicLogger = anthropicLogger;
600
660
  exports.apiLogger = apiLogger;
601
661
  exports.createTimer = createTimer;
662
+ exports.error = error;
602
663
  exports.isAppError = isAppError;
664
+ exports.list = list;
603
665
  exports.logger = logger;
666
+ exports.paginate = paginate;
604
667
  exports.parseOrThrow = parseOrThrow;
605
668
  exports.retryApiCall = retryApiCall;
606
669
  exports.retryCritical = retryCritical;
607
670
  exports.safeParse = safeParse;
608
671
  exports.sleep = sleep;
672
+ exports.success = success;
609
673
  exports.turbopufferLogger = turbopufferLogger;
610
674
  exports.voyageLogger = voyageLogger;
611
675
  exports.withFallback = withFallback;