bytekit 0.1.12
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/LICENSE +21 -0
- package/README.md +2241 -0
- package/bin/sutils.js +9 -0
- package/dist/api-client.d.ts +2 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +2 -0
- package/dist/api-client.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +401 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/type-generator.d.ts +12 -0
- package/dist/cli/type-generator.d.ts.map +1 -0
- package/dist/cli/type-generator.js +152 -0
- package/dist/cli/type-generator.js.map +1 -0
- package/dist/date-utils.d.ts +2 -0
- package/dist/date-utils.d.ts.map +1 -0
- package/dist/date-utils.js +2 -0
- package/dist/date-utils.js.map +1 -0
- package/dist/debug.d.ts +2 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +2 -0
- package/dist/debug.js.map +1 -0
- package/dist/env-manager.d.ts +2 -0
- package/dist/env-manager.d.ts.map +1 -0
- package/dist/env-manager.js +2 -0
- package/dist/env-manager.js.map +1 -0
- package/dist/file-upload.d.ts +2 -0
- package/dist/file-upload.d.ts.map +1 -0
- package/dist/file-upload.js +2 -0
- package/dist/file-upload.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +2 -0
- package/dist/logger.js.map +1 -0
- package/dist/profiler.d.ts +2 -0
- package/dist/profiler.d.ts.map +1 -0
- package/dist/profiler.js +2 -0
- package/dist/profiler.js.map +1 -0
- package/dist/response-validator.d.ts +2 -0
- package/dist/response-validator.d.ts.map +1 -0
- package/dist/response-validator.js +2 -0
- package/dist/response-validator.js.map +1 -0
- package/dist/retry-policy.d.ts +2 -0
- package/dist/retry-policy.d.ts.map +1 -0
- package/dist/retry-policy.js +2 -0
- package/dist/retry-policy.js.map +1 -0
- package/dist/storage-utils.d.ts +2 -0
- package/dist/storage-utils.d.ts.map +1 -0
- package/dist/storage-utils.js +2 -0
- package/dist/storage-utils.js.map +1 -0
- package/dist/streaming.d.ts +2 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +2 -0
- package/dist/streaming.js.map +1 -0
- package/dist/string-utils.d.ts +2 -0
- package/dist/string-utils.d.ts.map +1 -0
- package/dist/string-utils.js +2 -0
- package/dist/string-utils.js.map +1 -0
- package/dist/utils/core/ApiClient.d.ts +94 -0
- package/dist/utils/core/ApiClient.d.ts.map +1 -0
- package/dist/utils/core/ApiClient.js +291 -0
- package/dist/utils/core/ApiClient.js.map +1 -0
- package/dist/utils/core/ErrorBoundary.d.ts +141 -0
- package/dist/utils/core/ErrorBoundary.d.ts.map +1 -0
- package/dist/utils/core/ErrorBoundary.js +322 -0
- package/dist/utils/core/ErrorBoundary.js.map +1 -0
- package/dist/utils/core/Logger.d.ts +39 -0
- package/dist/utils/core/Logger.d.ts.map +1 -0
- package/dist/utils/core/Logger.js +154 -0
- package/dist/utils/core/Logger.js.map +1 -0
- package/dist/utils/core/Profiler.d.ts +8 -0
- package/dist/utils/core/Profiler.d.ts.map +1 -0
- package/dist/utils/core/Profiler.js +18 -0
- package/dist/utils/core/Profiler.js.map +1 -0
- package/dist/utils/core/RateLimiter.d.ts +74 -0
- package/dist/utils/core/RateLimiter.d.ts.map +1 -0
- package/dist/utils/core/RateLimiter.js +170 -0
- package/dist/utils/core/RateLimiter.js.map +1 -0
- package/dist/utils/core/RequestCache.d.ts +64 -0
- package/dist/utils/core/RequestCache.d.ts.map +1 -0
- package/dist/utils/core/RequestCache.js +139 -0
- package/dist/utils/core/RequestCache.js.map +1 -0
- package/dist/utils/core/RequestDeduplicator.d.ts +41 -0
- package/dist/utils/core/RequestDeduplicator.d.ts.map +1 -0
- package/dist/utils/core/RequestDeduplicator.js +83 -0
- package/dist/utils/core/RequestDeduplicator.js.map +1 -0
- package/dist/utils/core/ResponseValidator.d.ts +26 -0
- package/dist/utils/core/ResponseValidator.d.ts.map +1 -0
- package/dist/utils/core/ResponseValidator.js +140 -0
- package/dist/utils/core/ResponseValidator.js.map +1 -0
- package/dist/utils/core/RetryPolicy.d.ts +43 -0
- package/dist/utils/core/RetryPolicy.d.ts.map +1 -0
- package/dist/utils/core/RetryPolicy.js +119 -0
- package/dist/utils/core/RetryPolicy.js.map +1 -0
- package/dist/utils/core/debug.d.ts +46 -0
- package/dist/utils/core/debug.d.ts.map +1 -0
- package/dist/utils/core/debug.js +83 -0
- package/dist/utils/core/debug.js.map +1 -0
- package/dist/utils/core/index.d.ts +11 -0
- package/dist/utils/core/index.d.ts.map +1 -0
- package/dist/utils/core/index.js +11 -0
- package/dist/utils/core/index.js.map +1 -0
- package/dist/utils/helpers/ArrayUtils.d.ts +134 -0
- package/dist/utils/helpers/ArrayUtils.d.ts.map +1 -0
- package/dist/utils/helpers/ArrayUtils.js +301 -0
- package/dist/utils/helpers/ArrayUtils.js.map +1 -0
- package/dist/utils/helpers/CacheManager.d.ts +67 -0
- package/dist/utils/helpers/CacheManager.d.ts.map +1 -0
- package/dist/utils/helpers/CacheManager.js +222 -0
- package/dist/utils/helpers/CacheManager.js.map +1 -0
- package/dist/utils/helpers/CompressionUtils.d.ts +80 -0
- package/dist/utils/helpers/CompressionUtils.d.ts.map +1 -0
- package/dist/utils/helpers/CompressionUtils.js +224 -0
- package/dist/utils/helpers/CompressionUtils.js.map +1 -0
- package/dist/utils/helpers/CryptoUtils.d.ts +70 -0
- package/dist/utils/helpers/CryptoUtils.d.ts.map +1 -0
- package/dist/utils/helpers/CryptoUtils.js +215 -0
- package/dist/utils/helpers/CryptoUtils.js.map +1 -0
- package/dist/utils/helpers/DateUtils.d.ts +31 -0
- package/dist/utils/helpers/DateUtils.d.ts.map +1 -0
- package/dist/utils/helpers/DateUtils.js +104 -0
- package/dist/utils/helpers/DateUtils.js.map +1 -0
- package/dist/utils/helpers/DiffUtils.d.ts +61 -0
- package/dist/utils/helpers/DiffUtils.d.ts.map +1 -0
- package/dist/utils/helpers/DiffUtils.js +249 -0
- package/dist/utils/helpers/DiffUtils.js.map +1 -0
- package/dist/utils/helpers/EnvManager.d.ts +7 -0
- package/dist/utils/helpers/EnvManager.d.ts.map +1 -0
- package/dist/utils/helpers/EnvManager.js +19 -0
- package/dist/utils/helpers/EnvManager.js.map +1 -0
- package/dist/utils/helpers/EventEmitter.d.ts +78 -0
- package/dist/utils/helpers/EventEmitter.d.ts.map +1 -0
- package/dist/utils/helpers/EventEmitter.js +208 -0
- package/dist/utils/helpers/EventEmitter.js.map +1 -0
- package/dist/utils/helpers/FileUploadHelper.d.ts +44 -0
- package/dist/utils/helpers/FileUploadHelper.d.ts.map +1 -0
- package/dist/utils/helpers/FileUploadHelper.js +127 -0
- package/dist/utils/helpers/FileUploadHelper.js.map +1 -0
- package/dist/utils/helpers/FormUtils.d.ts +162 -0
- package/dist/utils/helpers/FormUtils.d.ts.map +1 -0
- package/dist/utils/helpers/FormUtils.js +378 -0
- package/dist/utils/helpers/FormUtils.js.map +1 -0
- package/dist/utils/helpers/ObjectUtils.d.ts +102 -0
- package/dist/utils/helpers/ObjectUtils.d.ts.map +1 -0
- package/dist/utils/helpers/ObjectUtils.js +297 -0
- package/dist/utils/helpers/ObjectUtils.js.map +1 -0
- package/dist/utils/helpers/PaginationHelper.d.ts +127 -0
- package/dist/utils/helpers/PaginationHelper.d.ts.map +1 -0
- package/dist/utils/helpers/PaginationHelper.js +259 -0
- package/dist/utils/helpers/PaginationHelper.js.map +1 -0
- package/dist/utils/helpers/PollingHelper.d.ts +64 -0
- package/dist/utils/helpers/PollingHelper.d.ts.map +1 -0
- package/dist/utils/helpers/PollingHelper.js +131 -0
- package/dist/utils/helpers/PollingHelper.js.map +1 -0
- package/dist/utils/helpers/StorageUtils.d.ts +9 -0
- package/dist/utils/helpers/StorageUtils.d.ts.map +1 -0
- package/dist/utils/helpers/StorageUtils.js +33 -0
- package/dist/utils/helpers/StorageUtils.js.map +1 -0
- package/dist/utils/helpers/StreamingHelper.d.ts +35 -0
- package/dist/utils/helpers/StreamingHelper.d.ts.map +1 -0
- package/dist/utils/helpers/StreamingHelper.js +167 -0
- package/dist/utils/helpers/StreamingHelper.js.map +1 -0
- package/dist/utils/helpers/StringUtils.d.ts +42 -0
- package/dist/utils/helpers/StringUtils.d.ts.map +1 -0
- package/dist/utils/helpers/StringUtils.js +173 -0
- package/dist/utils/helpers/StringUtils.js.map +1 -0
- package/dist/utils/helpers/TimeUtils.d.ts +87 -0
- package/dist/utils/helpers/TimeUtils.d.ts.map +1 -0
- package/dist/utils/helpers/TimeUtils.js +234 -0
- package/dist/utils/helpers/TimeUtils.js.map +1 -0
- package/dist/utils/helpers/Validator.d.ts +31 -0
- package/dist/utils/helpers/Validator.d.ts.map +1 -0
- package/dist/utils/helpers/Validator.js +156 -0
- package/dist/utils/helpers/Validator.js.map +1 -0
- package/dist/utils/helpers/WebSocketHelper.d.ts +63 -0
- package/dist/utils/helpers/WebSocketHelper.d.ts.map +1 -0
- package/dist/utils/helpers/WebSocketHelper.js +200 -0
- package/dist/utils/helpers/WebSocketHelper.js.map +1 -0
- package/dist/utils/helpers/index.d.ts +20 -0
- package/dist/utils/helpers/index.d.ts.map +1 -0
- package/dist/utils/helpers/index.js +20 -0
- package/dist/utils/helpers/index.js.map +1 -0
- package/dist/utils/index.d.ts +21 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/validator.d.ts +2 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +2 -0
- package/dist/validator.js.map +1 -0
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +2 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +189 -0
package/README.md
ADDED
|
@@ -0,0 +1,2241 @@
|
|
|
1
|
+
# bytekit
|
|
2
|
+
|
|
3
|
+
> **Previously known as:** `@sebamar88/utils` (v0.1.9 and earlier)
|
|
4
|
+
|
|
5
|
+
**EN:** Modern TypeScript utilities: an isomorphic **ApiClient**, structured logging/profiling helpers, and ready-to-use modules (`DateUtils`, `StringUtils`, `StorageManager`, etc.).
|
|
6
|
+
**ES:** Colección moderna de utilidades TypeScript: **ApiClient** isomórfico, logging/profiling estructurado y helpers listos (`DateUtils`, `StringUtils`, `StorageManager`, etc.).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview / Resumen
|
|
11
|
+
|
|
12
|
+
**EN:** Ship consistent networking, logging, and helper APIs across Node.js and browsers with zero setup—everything is published as ESM plus typings.
|
|
13
|
+
**ES:** Centralizá networking, logging y helpers tanto en Node.js como en navegadores sin configuración extra: todo se publica en ESM con definiciones de tipos.
|
|
14
|
+
|
|
15
|
+
## Highlights / Características
|
|
16
|
+
|
|
17
|
+
- ✅ **EN:** Fully ESM with `.d.ts` definitions. **ES:** Build 100 % ESM con tipos listos.
|
|
18
|
+
- 🌐 **EN:** Works on Node.js 18+ and modern browsers (via `cross-fetch`). **ES:** Compatible con Node.js 18+ y navegadores modernos (usa `cross-fetch`).
|
|
19
|
+
- 🔁 **EN:** ApiClient with retries, localized errors, flexible options. **ES:** ApiClient con reintentos, errores localizados y configuración flexible.
|
|
20
|
+
- 🧩 **EN:** Helper modules (strings, dates, validators, env, storage). **ES:** Helpers para strings, fechas, validadores, env y storage.
|
|
21
|
+
- 🪵 **EN:** Structured logging/profiling: `createLogger`, `Profiler`, `withTiming`. **ES:** Logging/profiling estructurado: `createLogger`, `Profiler`, `withTiming`.
|
|
22
|
+
|
|
23
|
+
## Installation / Instalación
|
|
24
|
+
|
|
25
|
+
### Global Installation / Instalación Global
|
|
26
|
+
|
|
27
|
+
**EN:** Install the package globally to use the CLI tool (`sutils`) from anywhere.
|
|
28
|
+
**ES:** Instalá el paquete globalmente para usar la herramienta CLI (`sutils`) desde cualquier lugar.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g bytekit
|
|
32
|
+
# or / o
|
|
33
|
+
pnpm add -g bytekit
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**EN:** After global installation, you can use the `sutils` command:
|
|
37
|
+
**ES:** Después de la instalación global, podés usar el comando `sutils`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
sutils create users
|
|
41
|
+
sutils types https://api.example.com/users
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Project Installation / Instalación en Proyecto
|
|
45
|
+
|
|
46
|
+
**EN:** Install as a project dependency to use all utilities in your application.
|
|
47
|
+
**ES:** Instalá como dependencia del proyecto para usar todos los utilities en tu aplicación.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install bytekit
|
|
51
|
+
# or / o
|
|
52
|
+
pnpm add bytekit
|
|
53
|
+
# or / o
|
|
54
|
+
yarn add bytekit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Modular Installation / Instalación Modular
|
|
58
|
+
|
|
59
|
+
**EN:** Import only the modules you need to reduce bundle size. Each utility can be imported individually.
|
|
60
|
+
**ES:** Importá solo los módulos que necesitás para reducir el tamaño del bundle. Cada utility se puede importar individualmente.
|
|
61
|
+
|
|
62
|
+
#### Core Modules / Módulos Core
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// HTTP Client
|
|
66
|
+
import { ApiClient, createApiClient } from "bytekit/api-client";
|
|
67
|
+
|
|
68
|
+
// Retry & Circuit Breaker
|
|
69
|
+
import { RetryPolicy, CircuitBreaker } from "bytekit/retry-policy";
|
|
70
|
+
|
|
71
|
+
// Response Validation
|
|
72
|
+
import { ResponseValidator } from "bytekit/response-validator";
|
|
73
|
+
|
|
74
|
+
// Logging
|
|
75
|
+
import { Logger, createLogger } from "bytekit/logger";
|
|
76
|
+
|
|
77
|
+
// Profiling
|
|
78
|
+
import { Profiler } from "bytekit/profiler";
|
|
79
|
+
|
|
80
|
+
// Debug Utilities
|
|
81
|
+
import { createStopwatch, withTiming, measureAsync } from "bytekit/debug";
|
|
82
|
+
|
|
83
|
+
// Request Caching
|
|
84
|
+
import { RequestCache } from "bytekit/request-cache";
|
|
85
|
+
|
|
86
|
+
// Rate Limiting
|
|
87
|
+
import { RateLimiter, SlidingWindowRateLimiter } from "bytekit/rate-limiter";
|
|
88
|
+
|
|
89
|
+
// Request Deduplication
|
|
90
|
+
import { RequestDeduplicator } from "bytekit/request-deduplicator";
|
|
91
|
+
|
|
92
|
+
// Error Boundary
|
|
93
|
+
import {
|
|
94
|
+
ErrorBoundary,
|
|
95
|
+
getGlobalErrorBoundary,
|
|
96
|
+
} from "bytekit/error-boundary";
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Helper Modules / Módulos Helpers
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// Date Utilities
|
|
103
|
+
import { DateUtils } from "bytekit/date-utils";
|
|
104
|
+
|
|
105
|
+
// String Utilities
|
|
106
|
+
import { StringUtils } from "bytekit/string-utils";
|
|
107
|
+
|
|
108
|
+
// Validation
|
|
109
|
+
import { Validator } from "bytekit/validator";
|
|
110
|
+
|
|
111
|
+
// Environment Manager
|
|
112
|
+
import { EnvManager } from "bytekit/env-manager";
|
|
113
|
+
|
|
114
|
+
// Storage Utilities
|
|
115
|
+
import { StorageUtils } from "bytekit/storage-utils";
|
|
116
|
+
|
|
117
|
+
// File Upload
|
|
118
|
+
import { FileUploadHelper } from "bytekit/file-upload";
|
|
119
|
+
|
|
120
|
+
// Streaming
|
|
121
|
+
import { StreamingHelper } from "bytekit/streaming";
|
|
122
|
+
|
|
123
|
+
// WebSocket
|
|
124
|
+
import { WebSocketHelper } from "bytekit/websocket";
|
|
125
|
+
|
|
126
|
+
// Array Utilities
|
|
127
|
+
import { ArrayUtils } from "bytekit/array-utils";
|
|
128
|
+
|
|
129
|
+
// Object Utilities
|
|
130
|
+
import { ObjectUtils } from "bytekit/object-utils";
|
|
131
|
+
|
|
132
|
+
// Form Utilities
|
|
133
|
+
import { FormUtils, createForm } from "bytekit/form-utils";
|
|
134
|
+
|
|
135
|
+
// Time Utilities
|
|
136
|
+
import { TimeUtils } from "bytekit/time-utils";
|
|
137
|
+
|
|
138
|
+
// Event Emitter
|
|
139
|
+
import { EventEmitter, createEventEmitter } from "bytekit/event-emitter";
|
|
140
|
+
|
|
141
|
+
// Diff Utilities
|
|
142
|
+
import { DiffUtils } from "bytekit/diff-utils";
|
|
143
|
+
|
|
144
|
+
// Polling Helper
|
|
145
|
+
import { PollingHelper, createPoller } from "bytekit/polling-helper";
|
|
146
|
+
|
|
147
|
+
// Crypto Utilities
|
|
148
|
+
import { CryptoUtils } from "bytekit/crypto-utils";
|
|
149
|
+
|
|
150
|
+
// Pagination Helper
|
|
151
|
+
import {
|
|
152
|
+
PaginationHelper,
|
|
153
|
+
createPaginator,
|
|
154
|
+
} from "bytekit/pagination-helper";
|
|
155
|
+
|
|
156
|
+
// Cache Manager
|
|
157
|
+
import { CacheManager, createCacheManager } from "bytekit/cache-manager";
|
|
158
|
+
|
|
159
|
+
// Compression Utilities
|
|
160
|
+
import { CompressionUtils } from "bytekit/compression-utils";
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Import Everything / Importar Todo
|
|
164
|
+
|
|
165
|
+
**EN:** You can also import everything from the main entry point:
|
|
166
|
+
**ES:** También podés importar todo desde el punto de entrada principal:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import {
|
|
170
|
+
// Core
|
|
171
|
+
ApiClient,
|
|
172
|
+
Logger,
|
|
173
|
+
Profiler,
|
|
174
|
+
RetryPolicy,
|
|
175
|
+
ResponseValidator,
|
|
176
|
+
RequestCache,
|
|
177
|
+
RateLimiter,
|
|
178
|
+
RequestDeduplicator,
|
|
179
|
+
ErrorBoundary,
|
|
180
|
+
|
|
181
|
+
// Helpers
|
|
182
|
+
DateUtils,
|
|
183
|
+
StringUtils,
|
|
184
|
+
Validator,
|
|
185
|
+
EnvManager,
|
|
186
|
+
StorageUtils,
|
|
187
|
+
FileUploadHelper,
|
|
188
|
+
StreamingHelper,
|
|
189
|
+
WebSocketHelper,
|
|
190
|
+
ArrayUtils,
|
|
191
|
+
ObjectUtils,
|
|
192
|
+
FormUtils,
|
|
193
|
+
TimeUtils,
|
|
194
|
+
EventEmitter,
|
|
195
|
+
DiffUtils,
|
|
196
|
+
PollingHelper,
|
|
197
|
+
CryptoUtils,
|
|
198
|
+
PaginationHelper,
|
|
199
|
+
CacheManager,
|
|
200
|
+
CompressionUtils,
|
|
201
|
+
|
|
202
|
+
// Factory functions
|
|
203
|
+
createLogger,
|
|
204
|
+
createApiClient,
|
|
205
|
+
createForm,
|
|
206
|
+
createEventEmitter,
|
|
207
|
+
createPoller,
|
|
208
|
+
createPaginator,
|
|
209
|
+
createCacheManager,
|
|
210
|
+
} from "bytekit";
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Quick Start / Inicio rápido
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { ApiClient, createLogger, DateUtils, StringUtils } from "bytekit";
|
|
217
|
+
|
|
218
|
+
const http = new ApiClient({
|
|
219
|
+
baseUrl: "https://api.my-service.com",
|
|
220
|
+
defaultHeaders: { "X-Team": "@sebamar88" },
|
|
221
|
+
locale: "es",
|
|
222
|
+
errorMessages: {
|
|
223
|
+
es: { 418: "Soy una tetera ☕" },
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const users = await http.get<{ id: string; name: string }[]>("/users");
|
|
228
|
+
|
|
229
|
+
const logger = createLogger({ namespace: "users-service", level: "info" });
|
|
230
|
+
logger.info("Users synced", { count: users.length });
|
|
231
|
+
|
|
232
|
+
logger.debug("Next sync ETA (days)", {
|
|
233
|
+
etaDays: DateUtils.diffInDays(
|
|
234
|
+
new Date(),
|
|
235
|
+
DateUtils.add(new Date(), { days: 7 })
|
|
236
|
+
),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const slug = StringUtils.slugify("New Users – October 2024");
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**EN:** Import everything from the root entry, configure the ApiClient once, reuse helpers everywhere.
|
|
243
|
+
**ES:** Importá desde la raíz, configurá el ApiClient una sola vez y reutilizá los helpers en todos tus servicios.
|
|
244
|
+
|
|
245
|
+
## API surface / Métodos expuestos
|
|
246
|
+
|
|
247
|
+
**EN:** Complete reference of all exported methods and classes. Use `npm info bytekit` to see the full list.
|
|
248
|
+
**ES:** Referencia completa de todos los métodos y clases exportados. Usa `npm info bytekit` para ver la lista completa.
|
|
249
|
+
|
|
250
|
+
### Core Modules / Módulos Core
|
|
251
|
+
|
|
252
|
+
#### ApiClient
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
class ApiClient {
|
|
256
|
+
get<T>(url: string, options?: RequestOptions): Promise<T>;
|
|
257
|
+
post<T>(url: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
258
|
+
put<T>(url: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
259
|
+
patch<T>(url: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
260
|
+
delete<T>(url: string, options?: RequestOptions): Promise<T>;
|
|
261
|
+
getList<T>(
|
|
262
|
+
url: string,
|
|
263
|
+
options?: GetListOptions
|
|
264
|
+
): Promise<PaginatedResponse<T>>;
|
|
265
|
+
request<T>(
|
|
266
|
+
method: string,
|
|
267
|
+
url: string,
|
|
268
|
+
options?: RequestOptions
|
|
269
|
+
): Promise<T>;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function createApiClient(config: ApiClientConfig): ApiClient;
|
|
273
|
+
class HttpError extends Error {
|
|
274
|
+
status: number;
|
|
275
|
+
body: unknown;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Logger
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
class Logger {
|
|
283
|
+
setLevel(level: LogLevel): void;
|
|
284
|
+
child(namespace: string): Logger;
|
|
285
|
+
debug(message: string, data?: unknown): void;
|
|
286
|
+
info(message: string, data?: unknown): void;
|
|
287
|
+
warn(message: string, data?: unknown): void;
|
|
288
|
+
error(message: string, data?: unknown): void;
|
|
289
|
+
log(level: LogLevel, message: string, data?: unknown): void;
|
|
290
|
+
silent(): void;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function createLogger(config: LoggerConfig): Logger;
|
|
294
|
+
const consoleTransportNode: Transport;
|
|
295
|
+
const consoleTransportBrowser: Transport;
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### Profiler
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
class Profiler {
|
|
302
|
+
start(label: string): void;
|
|
303
|
+
end(label: string): number;
|
|
304
|
+
summary(): ProfilerSummary[];
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### Debug Utilities
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
function createStopwatch(options?: StopwatchOptions): Stopwatch;
|
|
312
|
+
interface Stopwatch {
|
|
313
|
+
stop(): number;
|
|
314
|
+
elapsed(): number;
|
|
315
|
+
log(data?: unknown): void;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function withTiming<T>(label: string, fn: () => Promise<T>): Promise<T>;
|
|
319
|
+
function measureSync<T>(
|
|
320
|
+
label: string,
|
|
321
|
+
fn: () => T
|
|
322
|
+
): { result: T; durationMs: number };
|
|
323
|
+
async function measureAsync<T>(
|
|
324
|
+
label: string,
|
|
325
|
+
fn: () => Promise<T>
|
|
326
|
+
): Promise<{ result: T; durationMs: number }>;
|
|
327
|
+
function captureDebug(label: string, data?: unknown): void;
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### RetryPolicy
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
class RetryPolicy {
|
|
334
|
+
constructor(config: RetryPolicyConfig);
|
|
335
|
+
async execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
336
|
+
getAttempts(): number;
|
|
337
|
+
getRemainingAttempts(): number;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
class CircuitBreaker {
|
|
341
|
+
constructor(config: CircuitBreakerConfig);
|
|
342
|
+
async execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
343
|
+
getState(): CircuitBreakerState;
|
|
344
|
+
reset(): void;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### ResponseValidator
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
class ResponseValidator {
|
|
352
|
+
static validate(data: unknown, schema: ValidationSchema): ValidationResult;
|
|
353
|
+
static validateArray(
|
|
354
|
+
data: unknown[],
|
|
355
|
+
schema: ValidationSchema
|
|
356
|
+
): ValidationResult;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
interface ValidationSchema {
|
|
360
|
+
type: string;
|
|
361
|
+
properties?: Record<string, ValidationSchema>;
|
|
362
|
+
required?: string[];
|
|
363
|
+
pattern?: RegExp;
|
|
364
|
+
minimum?: number;
|
|
365
|
+
maximum?: number;
|
|
366
|
+
minLength?: number;
|
|
367
|
+
maxLength?: number;
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
#### RequestCache
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
class RequestCache {
|
|
375
|
+
set(key: string, value: unknown, ttl?: number): void;
|
|
376
|
+
get<T>(key: string): T | null;
|
|
377
|
+
has(key: string): boolean;
|
|
378
|
+
remove(key: string): void;
|
|
379
|
+
clear(): void;
|
|
380
|
+
invalidate(pattern: string): void;
|
|
381
|
+
invalidatePattern(pattern: string): void;
|
|
382
|
+
getStats(): CacheStats;
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### RateLimiter
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
class RateLimiter {
|
|
390
|
+
isAllowed(key: string): boolean;
|
|
391
|
+
async waitForAllowance(key: string): Promise<void>;
|
|
392
|
+
getStats(key: string): RateLimiterStats;
|
|
393
|
+
reset(key?: string): void;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
class SlidingWindowRateLimiter {
|
|
397
|
+
isAllowed(key: string): boolean;
|
|
398
|
+
async waitForAllowance(key: string): Promise<void>;
|
|
399
|
+
getStats(key: string): RateLimiterStats;
|
|
400
|
+
reset(key?: string): void;
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### RequestDeduplicator
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
class RequestDeduplicator {
|
|
408
|
+
async execute<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
409
|
+
getStats(): DeduplicatorStats;
|
|
410
|
+
getInFlightCount(): number;
|
|
411
|
+
clear(): void;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### ErrorBoundary
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
class ErrorBoundary {
|
|
419
|
+
async execute<T>(fn: () => Promise<T>, context?: ErrorContext): Promise<T>;
|
|
420
|
+
executeSync<T>(fn: () => T, context?: ErrorContext): T;
|
|
421
|
+
wrap<T extends (...args: unknown[]) => Promise<unknown>>(fn: T): T;
|
|
422
|
+
wrapSync<T extends (...args: unknown[]) => unknown>(fn: T): T;
|
|
423
|
+
addHandler(handler: ErrorHandler): void;
|
|
424
|
+
getErrorHistory(limit?: number): ErrorEntry[];
|
|
425
|
+
createErrorReport(): ErrorReport;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function getGlobalErrorBoundary(config?: ErrorBoundaryConfig): ErrorBoundary;
|
|
429
|
+
|
|
430
|
+
class AppError extends Error {
|
|
431
|
+
code: string;
|
|
432
|
+
context?: Record<string, unknown>;
|
|
433
|
+
}
|
|
434
|
+
class AppValidationError extends AppError {}
|
|
435
|
+
class NotFoundError extends AppError {}
|
|
436
|
+
class TimeoutError extends AppError {}
|
|
437
|
+
class RateLimitError extends AppError {
|
|
438
|
+
retryAfter?: number;
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Helper Modules / Módulos Helpers
|
|
443
|
+
|
|
444
|
+
#### DateUtils
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
class DateUtils {
|
|
448
|
+
static parse(date: Date | string | number): Date;
|
|
449
|
+
static isValid(date: unknown): boolean;
|
|
450
|
+
static toISODate(date: Date | string): string;
|
|
451
|
+
static startOfDay(date: Date | string): Date;
|
|
452
|
+
static endOfDay(date: Date | string): Date;
|
|
453
|
+
static add(date: Date | string, duration: DateDuration): Date;
|
|
454
|
+
static diff(
|
|
455
|
+
from: Date | string,
|
|
456
|
+
to: Date | string,
|
|
457
|
+
options?: DiffOptions
|
|
458
|
+
): number;
|
|
459
|
+
static diffInDays(
|
|
460
|
+
from: Date | string,
|
|
461
|
+
to: Date | string,
|
|
462
|
+
options?: DiffOptions
|
|
463
|
+
): number;
|
|
464
|
+
static isSameDay(date1: Date | string, date2: Date | string): boolean;
|
|
465
|
+
static isBefore(date1: Date | string, date2: Date | string): boolean;
|
|
466
|
+
static isAfter(date1: Date | string, date2: Date | string): boolean;
|
|
467
|
+
static format(date: Date | string, locale?: string): string;
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### StringUtils
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
class StringUtils {
|
|
475
|
+
static removeDiacritics(str: string): string;
|
|
476
|
+
static slugify(str: string, options?: SlugifyOptions): string;
|
|
477
|
+
static compactWhitespace(str: string): string;
|
|
478
|
+
static capitalize(str: string): string;
|
|
479
|
+
static capitalizeWords(str: string): string;
|
|
480
|
+
static truncate(
|
|
481
|
+
str: string,
|
|
482
|
+
length: number,
|
|
483
|
+
options?: TruncateOptions
|
|
484
|
+
): string;
|
|
485
|
+
static mask(str: string, options?: MaskOptions): string;
|
|
486
|
+
static interpolate(
|
|
487
|
+
template: string,
|
|
488
|
+
values: Record<string, unknown>,
|
|
489
|
+
options?: InterpolateOptions
|
|
490
|
+
): string;
|
|
491
|
+
static initials(str: string, limit?: number): string;
|
|
492
|
+
static toQueryString(
|
|
493
|
+
obj: Record<string, unknown>,
|
|
494
|
+
options?: QueryStringOptions
|
|
495
|
+
): string;
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### Validator
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
class Validator {
|
|
503
|
+
static isEmail(email: string): boolean;
|
|
504
|
+
static isEmpty(value: unknown): boolean;
|
|
505
|
+
static minLength(value: string, min: number): boolean;
|
|
506
|
+
static maxLength(value: string, max: number): boolean;
|
|
507
|
+
static matches(value: string, pattern: RegExp): boolean;
|
|
508
|
+
static isUrl(url: string): boolean;
|
|
509
|
+
static isInternationalPhone(phone: string): boolean;
|
|
510
|
+
static isPhoneE164(phone: string): boolean;
|
|
511
|
+
static isUUIDv4(uuid: string): boolean;
|
|
512
|
+
static isLocalPhone(phone: string, locale?: string): boolean;
|
|
513
|
+
static isDni(dni: string, locale?: string): boolean;
|
|
514
|
+
static isCuit(cuit: string): boolean;
|
|
515
|
+
static isCbu(cbu: string): boolean;
|
|
516
|
+
static isStrongPassword(
|
|
517
|
+
password: string,
|
|
518
|
+
options?: PasswordOptions
|
|
519
|
+
): boolean;
|
|
520
|
+
static isDateRange(
|
|
521
|
+
date: Date | string,
|
|
522
|
+
from: Date | string,
|
|
523
|
+
to: Date | string
|
|
524
|
+
): boolean;
|
|
525
|
+
static isOneTimeCode(code: string, length?: number): boolean;
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
#### EnvManager
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
class EnvManager {
|
|
533
|
+
get(key: string, defaultValue?: string): string | undefined;
|
|
534
|
+
require(key: string): string;
|
|
535
|
+
isProd(): boolean;
|
|
536
|
+
isDev(): boolean;
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### StorageUtils
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
class StorageUtils {
|
|
544
|
+
constructor(storage?: Storage);
|
|
545
|
+
set<T>(key: string, value: T, ttl?: number): void;
|
|
546
|
+
get<T>(key: string): T | null;
|
|
547
|
+
remove(key: string): void;
|
|
548
|
+
clear(): void;
|
|
549
|
+
has(key: string): boolean;
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
#### FileUploadHelper
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
class FileUploadHelper {
|
|
557
|
+
static validateFile(
|
|
558
|
+
file: File,
|
|
559
|
+
options?: FileValidationOptions
|
|
560
|
+
): FileValidationResult;
|
|
561
|
+
static async uploadFile(
|
|
562
|
+
file: File,
|
|
563
|
+
url: string,
|
|
564
|
+
options?: UploadOptions
|
|
565
|
+
): Promise<UploadResponse>;
|
|
566
|
+
static async uploadChunked(
|
|
567
|
+
file: File,
|
|
568
|
+
url: string,
|
|
569
|
+
options?: ChunkedUploadOptions
|
|
570
|
+
): Promise<UploadResponse>;
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
#### StreamingHelper
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
class StreamingHelper {
|
|
578
|
+
static async streamJsonLines<T>(
|
|
579
|
+
url: string,
|
|
580
|
+
options?: StreamOptions<T>
|
|
581
|
+
): Promise<StreamResult<T>>;
|
|
582
|
+
static streamSSE<T>(
|
|
583
|
+
url: string,
|
|
584
|
+
options?: SSEOptions<T>
|
|
585
|
+
): SSESubscription<T>;
|
|
586
|
+
static async downloadStream(
|
|
587
|
+
url: string,
|
|
588
|
+
options?: DownloadOptions
|
|
589
|
+
): Promise<Blob>;
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### WebSocketHelper
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
class WebSocketHelper {
|
|
597
|
+
constructor(url: string, options?: WebSocketOptions);
|
|
598
|
+
async connect(): Promise<void>;
|
|
599
|
+
on<T>(event: string, listener: (data: T) => void): void;
|
|
600
|
+
once<T>(event: string, listener: (data: T) => void): void;
|
|
601
|
+
off(event: string, listener: Function): void;
|
|
602
|
+
send<T>(event: string, data: T): void;
|
|
603
|
+
async request<Req, Res>(event: string, data: Req): Promise<Res>;
|
|
604
|
+
onError(listener: (error: Error) => void): void;
|
|
605
|
+
close(): void;
|
|
606
|
+
isConnected(): boolean;
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
#### ArrayUtils
|
|
611
|
+
|
|
612
|
+
```ts
|
|
613
|
+
class ArrayUtils {
|
|
614
|
+
static chunk<T>(array: T[], size: number): T[][];
|
|
615
|
+
static flatten<T>(array: unknown[], depth?: number): T[];
|
|
616
|
+
static unique<T>(array: T[], by?: (item: T) => unknown): T[];
|
|
617
|
+
static shuffle<T>(array: T[]): T[];
|
|
618
|
+
static random<T>(array: T[]): T;
|
|
619
|
+
static randomN<T>(array: T[], n: number): T[];
|
|
620
|
+
static zip<T>(...arrays: T[][]): T[][];
|
|
621
|
+
static unzip<T>(array: T[][]): T[][];
|
|
622
|
+
static difference<T>(array1: T[], array2: T[]): T[];
|
|
623
|
+
static intersection<T>(array1: T[], array2: T[]): T[];
|
|
624
|
+
static union<T>(array1: T[], array2: T[]): T[];
|
|
625
|
+
static partition<T>(
|
|
626
|
+
array: T[],
|
|
627
|
+
predicate: (item: T) => boolean
|
|
628
|
+
): [T[], T[]];
|
|
629
|
+
static sum(array: number[]): number;
|
|
630
|
+
static average(array: number[]): number;
|
|
631
|
+
static min(array: number[]): number;
|
|
632
|
+
static max(array: number[]): number;
|
|
633
|
+
static range(start: number, end: number, step?: number): number[];
|
|
634
|
+
static rotate<T>(array: T[], steps: number): T[];
|
|
635
|
+
static transpose<T>(array: T[][]): T[][];
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### ObjectUtils
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
class ObjectUtils {
|
|
643
|
+
static isEmpty(obj: unknown): boolean;
|
|
644
|
+
static deepClone<T>(obj: T): T;
|
|
645
|
+
static merge<T>(...objects: Partial<T>[]): T;
|
|
646
|
+
static deepMerge<T>(...objects: Partial<T>[]): T;
|
|
647
|
+
static pick<T>(obj: T, keys: (keyof T)[]): Partial<T>;
|
|
648
|
+
static omit<T>(obj: T, keys: (keyof T)[]): Partial<T>;
|
|
649
|
+
static get<T>(obj: unknown, path: string): T | undefined;
|
|
650
|
+
static set<T>(obj: T, path: string, value: unknown): T;
|
|
651
|
+
static flatten<T>(obj: T, prefix?: string): Record<string, unknown>;
|
|
652
|
+
static unflatten(obj: Record<string, unknown>): Record<string, unknown>;
|
|
653
|
+
static filter<T>(
|
|
654
|
+
obj: T,
|
|
655
|
+
predicate: (key: string, value: unknown) => boolean
|
|
656
|
+
): Partial<T>;
|
|
657
|
+
static mapValues<T>(obj: T, mapper: (value: unknown) => unknown): T;
|
|
658
|
+
static hasKeys<T>(obj: T, keys: (keyof T)[]): boolean;
|
|
659
|
+
static invert<T>(obj: Record<string, T>): Record<T, string>;
|
|
660
|
+
static groupBy<T>(array: T[], key: keyof T): Record<string, T[]>;
|
|
661
|
+
static indexBy<T>(array: T[], key: keyof T): Record<string, T>;
|
|
662
|
+
static deepEqual(obj1: unknown, obj2: unknown): boolean;
|
|
663
|
+
static size(obj: Record<string, unknown>): number;
|
|
664
|
+
static entries<T>(obj: T): [string, unknown][];
|
|
665
|
+
static fromEntries(entries: [string, unknown][]): Record<string, unknown>;
|
|
666
|
+
static fromKeys<T>(keys: string[], value: T): Record<string, T>;
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
#### FormUtils
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
class FormUtils {
|
|
674
|
+
constructor(config: FormConfig);
|
|
675
|
+
setValue(field: string, value: unknown): void;
|
|
676
|
+
getValue(field: string): unknown;
|
|
677
|
+
getFieldError(field: string): string;
|
|
678
|
+
touchField(field: string): void;
|
|
679
|
+
isTouched(field: string): boolean;
|
|
680
|
+
isDirty(field: string): boolean;
|
|
681
|
+
async validateField(field: string): Promise<string | null>;
|
|
682
|
+
async validate(): Promise<Record<string, string>>;
|
|
683
|
+
async submit(): Promise<boolean>;
|
|
684
|
+
getState(): FormState;
|
|
685
|
+
createBinding(field: string): FieldBinding;
|
|
686
|
+
reset(): void;
|
|
687
|
+
serialize(): Record<string, unknown>;
|
|
688
|
+
deserialize(data: Record<string, unknown>): void;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function createForm(config: FormConfig): FormUtils;
|
|
692
|
+
|
|
693
|
+
class Validators {
|
|
694
|
+
static required(value: unknown): boolean;
|
|
695
|
+
static email(value: string): boolean;
|
|
696
|
+
static minLength(value: string, min: number): boolean;
|
|
697
|
+
static maxLength(value: string, max: number): boolean;
|
|
698
|
+
static pattern(value: string, pattern: RegExp): boolean;
|
|
699
|
+
static url(value: string): boolean;
|
|
700
|
+
static match(value: string, other: string): boolean;
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
#### TimeUtils
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
class TimeUtils {
|
|
708
|
+
static now(): number;
|
|
709
|
+
static sleep(ms: number): Promise<void>;
|
|
710
|
+
static debounce<T extends (...args: unknown[]) => unknown>(
|
|
711
|
+
fn: T,
|
|
712
|
+
delay: number
|
|
713
|
+
): T;
|
|
714
|
+
static throttle<T extends (...args: unknown[]) => unknown>(
|
|
715
|
+
fn: T,
|
|
716
|
+
delay: number
|
|
717
|
+
): T;
|
|
718
|
+
static timeout<T>(promise: Promise<T>, ms: number): Promise<T>;
|
|
719
|
+
static retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
#### EventEmitter
|
|
724
|
+
|
|
725
|
+
```ts
|
|
726
|
+
class EventEmitter<
|
|
727
|
+
Events extends Record<string, unknown> = Record<string, unknown>
|
|
728
|
+
> {
|
|
729
|
+
on<K extends keyof Events>(
|
|
730
|
+
event: K,
|
|
731
|
+
listener: EventListener<Events[K]>
|
|
732
|
+
): this;
|
|
733
|
+
once<K extends keyof Events>(
|
|
734
|
+
event: K,
|
|
735
|
+
listener: EventListener<Events[K]>
|
|
736
|
+
): this;
|
|
737
|
+
off<K extends keyof Events>(
|
|
738
|
+
event: K,
|
|
739
|
+
listener: EventListener<Events[K]>
|
|
740
|
+
): this;
|
|
741
|
+
removeAllListeners<K extends keyof Events>(event?: K): this;
|
|
742
|
+
async emit<K extends keyof Events>(
|
|
743
|
+
event: K,
|
|
744
|
+
data: Events[K]
|
|
745
|
+
): Promise<boolean>;
|
|
746
|
+
emitSync<K extends keyof Events>(event: K, data: Events[K]): boolean;
|
|
747
|
+
onError(listener: EventListenerWithError): this;
|
|
748
|
+
listenerCount<K extends keyof Events>(event: K): number;
|
|
749
|
+
getListeners<K extends keyof Events>(event: K): EventListener<Events[K]>[];
|
|
750
|
+
eventNames(): (keyof Events)[];
|
|
751
|
+
setMaxListeners(n: number): this;
|
|
752
|
+
getMaxListeners(): number;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function createEventEmitter<
|
|
756
|
+
Events extends Record<string, unknown> = Record<string, unknown>
|
|
757
|
+
>(options?: EventEmitterOptions): EventEmitter<Events>;
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
#### DiffUtils
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
class DiffUtils {
|
|
764
|
+
static diff(
|
|
765
|
+
old: Record<string, unknown>,
|
|
766
|
+
new_: Record<string, unknown>
|
|
767
|
+
): DiffResult;
|
|
768
|
+
static createPatch(
|
|
769
|
+
old: Record<string, unknown>,
|
|
770
|
+
new_: Record<string, unknown>
|
|
771
|
+
): Patch[];
|
|
772
|
+
static applyPatch<T>(obj: T, patches: Patch[]): T;
|
|
773
|
+
static deepEqual(obj1: unknown, obj2: unknown): boolean;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
interface Patch {
|
|
777
|
+
op: "add" | "remove" | "replace";
|
|
778
|
+
path: string;
|
|
779
|
+
value?: unknown;
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
#### PollingHelper
|
|
784
|
+
|
|
785
|
+
```ts
|
|
786
|
+
class PollingHelper {
|
|
787
|
+
constructor(fn: () => Promise<unknown>, options?: PollingOptions);
|
|
788
|
+
async start(): Promise<PollingResult>;
|
|
789
|
+
stop(): void;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function createPoller(
|
|
793
|
+
fn: () => Promise<unknown>,
|
|
794
|
+
options?: PollingOptions
|
|
795
|
+
): PollingHelper;
|
|
796
|
+
|
|
797
|
+
interface PollingResult {
|
|
798
|
+
success: boolean;
|
|
799
|
+
attempts: number;
|
|
800
|
+
lastResult: unknown;
|
|
801
|
+
totalTimeMs: number;
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
#### CryptoUtils
|
|
806
|
+
|
|
807
|
+
```ts
|
|
808
|
+
class CryptoUtils {
|
|
809
|
+
static generateToken(bytes?: number): string;
|
|
810
|
+
static generateUUID(): string;
|
|
811
|
+
static base64Encode(str: string): string;
|
|
812
|
+
static base64Decode(str: string): string;
|
|
813
|
+
static base64UrlEncode(str: string): string;
|
|
814
|
+
static base64UrlDecode(str: string): string;
|
|
815
|
+
static async hash(str: string): Promise<string>;
|
|
816
|
+
static async verifyHash(str: string, hash: string): Promise<boolean>;
|
|
817
|
+
static constantTimeCompare(a: string, b: string): boolean;
|
|
818
|
+
static async hmac(message: string, secret: string): Promise<string>;
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
#### PaginationHelper
|
|
823
|
+
|
|
824
|
+
```ts
|
|
825
|
+
class PaginationHelper {
|
|
826
|
+
constructor(items: unknown[], options?: PaginationOptions);
|
|
827
|
+
getCurrentPage(): unknown[];
|
|
828
|
+
next(): void;
|
|
829
|
+
previous(): void;
|
|
830
|
+
goToPage(page: number): void;
|
|
831
|
+
getState(): PaginationState;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function createPaginator(
|
|
835
|
+
items: unknown[],
|
|
836
|
+
options?: PaginationOptions
|
|
837
|
+
): PaginationHelper;
|
|
838
|
+
|
|
839
|
+
interface PaginationState {
|
|
840
|
+
currentPage: number;
|
|
841
|
+
pageSize: number;
|
|
842
|
+
total: number;
|
|
843
|
+
totalPages: number;
|
|
844
|
+
hasNextPage: boolean;
|
|
845
|
+
hasPreviousPage: boolean;
|
|
846
|
+
offset: number;
|
|
847
|
+
limit: number;
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
#### CacheManager
|
|
852
|
+
|
|
853
|
+
```ts
|
|
854
|
+
class CacheManager {
|
|
855
|
+
constructor(options?: CacheManagerOptions);
|
|
856
|
+
set<T>(key: string, value: T, ttl?: number): void;
|
|
857
|
+
get<T>(key: string): T | null;
|
|
858
|
+
has(key: string): boolean;
|
|
859
|
+
remove(key: string): void;
|
|
860
|
+
clear(): void;
|
|
861
|
+
async getOrCompute<T>(
|
|
862
|
+
key: string,
|
|
863
|
+
fn: () => Promise<T>,
|
|
864
|
+
ttl?: number
|
|
865
|
+
): Promise<T>;
|
|
866
|
+
invalidatePattern(pattern: string): void;
|
|
867
|
+
getStats(): CacheStats;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function createCacheManager(options?: CacheManagerOptions): CacheManager;
|
|
871
|
+
|
|
872
|
+
interface CacheStats {
|
|
873
|
+
hits: number;
|
|
874
|
+
misses: number;
|
|
875
|
+
hitRate: number;
|
|
876
|
+
size: number;
|
|
877
|
+
maxSize: number;
|
|
878
|
+
}
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
#### CompressionUtils
|
|
882
|
+
|
|
883
|
+
```ts
|
|
884
|
+
class CompressionUtils {
|
|
885
|
+
static compress(str: string): string;
|
|
886
|
+
static decompress(compressed: string): string;
|
|
887
|
+
static base64Encode(str: string): string;
|
|
888
|
+
static base64Decode(str: string): string;
|
|
889
|
+
static base64UrlEncode(str: string): string;
|
|
890
|
+
static base64UrlDecode(str: string): string;
|
|
891
|
+
static serializeCompressed(obj: unknown): string;
|
|
892
|
+
static deserializeCompressed(compressed: string): unknown;
|
|
893
|
+
static getCompressionRatio(original: string, compressed: string): number;
|
|
894
|
+
static minifyJSON(json: string): string;
|
|
895
|
+
static prettyJSON(json: string, indent?: number): string;
|
|
896
|
+
static async gzip(str: string): Promise<Buffer | string>;
|
|
897
|
+
static async gunzip(data: Buffer | string): Promise<string>;
|
|
898
|
+
static async deflate(str: string): Promise<Buffer | string>;
|
|
899
|
+
static async inflate(data: Buffer | string): Promise<string>;
|
|
900
|
+
static getSize(str: string): number;
|
|
901
|
+
static formatBytes(bytes: number, decimals?: number): string;
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
## API surface (pnpm info) / Métodos expuestos
|
|
906
|
+
|
|
907
|
+
`pnpm info bytekit readme` ahora lista todos los exports públicos:
|
|
908
|
+
|
|
909
|
+
## ApiClient Details / Detalles del ApiClient
|
|
910
|
+
|
|
911
|
+
- `baseUrl`: **EN** required prefix for relative endpoints. **ES** prefijo requerido para endpoints relativos.
|
|
912
|
+
- `defaultHeaders`: **EN** shared headers merged per request. **ES** cabeceras comunes que se combinan en cada request.
|
|
913
|
+
- `locale` + `errorMessages`: **EN** localized HTTP errors. **ES** mensajes localizados por código HTTP.
|
|
914
|
+
- `fetchImpl`: **EN** inject your own fetch (tests, custom environments). **ES** inyectá tu propio `fetch` (tests o entornos custom).
|
|
915
|
+
- `retryPolicy`: **EN** configure automatic retries with exponential backoff. **ES** configura reintentos automáticos con backoff exponencial.
|
|
916
|
+
- `circuitBreaker`: **EN** configure circuit breaker to prevent cascading failures. **ES** configura circuit breaker para evitar fallos en cascada.
|
|
917
|
+
|
|
918
|
+
Each `request` (and `get`, `post`, `put`, `patch`, `delete`) accepts / Cada request acepta:
|
|
919
|
+
|
|
920
|
+
- `searchParams`: **EN** serializes to URLSearchParams. **ES** se serializa automáticamente.
|
|
921
|
+
- `body`: **EN** strings, serializable objects, or `FormData`. **ES** strings, objetos serializables o `FormData`.
|
|
922
|
+
- `errorLocale`: **EN** override language per request. **ES** forzá un idioma específico.
|
|
923
|
+
- Native `RequestInit` fields (`headers`, `signal`, etc.).
|
|
924
|
+
|
|
925
|
+
```ts
|
|
926
|
+
import { HttpError } from "bytekit";
|
|
927
|
+
|
|
928
|
+
try {
|
|
929
|
+
await http.get("/users");
|
|
930
|
+
} catch (error) {
|
|
931
|
+
if (error instanceof HttpError) {
|
|
932
|
+
console.error("Server error", error.status, error.body);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### Paginated Lists / Listados Paginados
|
|
938
|
+
|
|
939
|
+
- **getList**: **EN** fetch paginated data with built-in support for `pagination`, `sort`, and `filters`. Returns a typed `PaginatedResponse` with metadata. **ES** obtiene datos paginados con soporte para `pagination`, `sort` y `filters`. Devuelve `PaginatedResponse` con metadatos.
|
|
940
|
+
|
|
941
|
+
```ts
|
|
942
|
+
import { ApiClient } from "bytekit";
|
|
943
|
+
|
|
944
|
+
const api = new ApiClient({ baseUrl: "https://api.example.com" });
|
|
945
|
+
|
|
946
|
+
// Fetch first page with 10 items per page
|
|
947
|
+
const response = await api.getList<User>("/users", {
|
|
948
|
+
pagination: { page: 1, limit: 10 },
|
|
949
|
+
sort: { field: "name", order: "asc" },
|
|
950
|
+
filters: { status: "active" },
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
console.log(response.data); // User[]
|
|
954
|
+
console.log(response.pagination);
|
|
955
|
+
// {
|
|
956
|
+
// page: 1,
|
|
957
|
+
// limit: 10,
|
|
958
|
+
// total: 42,
|
|
959
|
+
// totalPages: 5,
|
|
960
|
+
// hasNextPage: true,
|
|
961
|
+
// hasPreviousPage: false,
|
|
962
|
+
// }
|
|
963
|
+
|
|
964
|
+
// Fetch with custom filters
|
|
965
|
+
const filtered = await api.getList<User>("/users", {
|
|
966
|
+
pagination: { page: 2, limit: 20 },
|
|
967
|
+
sort: { field: "createdAt", order: "desc" },
|
|
968
|
+
filters: { role: "admin", department: "engineering" },
|
|
969
|
+
});
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
## Advanced Features / Características Avanzadas
|
|
973
|
+
|
|
974
|
+
### Retry Policy & Circuit Breaker
|
|
975
|
+
|
|
976
|
+
- **RetryPolicy**: **EN** automatic retry with exponential backoff for transient failures. **ES** reintentos automáticos con backoff exponencial para fallos transitorios.
|
|
977
|
+
- **CircuitBreaker**: **EN** prevent cascading failures by stopping requests when service is down. **ES** evita fallos en cascada deteniendo requests cuando el servicio está caído.
|
|
978
|
+
|
|
979
|
+
```ts
|
|
980
|
+
import { ApiClient } from "bytekit";
|
|
981
|
+
|
|
982
|
+
const api = new ApiClient({
|
|
983
|
+
baseUrl: "https://api.example.com",
|
|
984
|
+
retryPolicy: {
|
|
985
|
+
maxAttempts: 3,
|
|
986
|
+
initialDelayMs: 100,
|
|
987
|
+
backoffMultiplier: 2,
|
|
988
|
+
},
|
|
989
|
+
circuitBreaker: {
|
|
990
|
+
failureThreshold: 5,
|
|
991
|
+
successThreshold: 2,
|
|
992
|
+
timeoutMs: 60000,
|
|
993
|
+
},
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
// Requests automatically retry and respect circuit breaker state
|
|
997
|
+
const data = await api.get("/users");
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
### Response Validation
|
|
1001
|
+
|
|
1002
|
+
- **ResponseValidator**: **EN** validate API responses against schemas before using them. **ES** valida respuestas de API contra esquemas antes de usarlas.
|
|
1003
|
+
|
|
1004
|
+
```ts
|
|
1005
|
+
import { ApiClient, ValidationSchema } from "bytekit";
|
|
1006
|
+
|
|
1007
|
+
const userSchema: ValidationSchema = {
|
|
1008
|
+
type: "object",
|
|
1009
|
+
properties: {
|
|
1010
|
+
id: { type: "number", required: true },
|
|
1011
|
+
email: { type: "string", pattern: /.+@.+\..+/ },
|
|
1012
|
+
age: { type: "number", minimum: 0, maximum: 150 },
|
|
1013
|
+
},
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
const users = await api.get<User[]>("/users", {
|
|
1017
|
+
validateResponse: {
|
|
1018
|
+
type: "array",
|
|
1019
|
+
items: userSchema,
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
### File Upload Helper
|
|
1025
|
+
|
|
1026
|
+
- **FileUploadHelper**: **EN** upload files with progress tracking, chunking, and retry support. **ES** sube archivos con seguimiento de progreso, chunking y reintentos.
|
|
1027
|
+
|
|
1028
|
+
```ts
|
|
1029
|
+
import { FileUploadHelper } from "bytekit";
|
|
1030
|
+
|
|
1031
|
+
const file = document.querySelector<HTMLInputElement>("#file")?.files?.[0];
|
|
1032
|
+
if (file) {
|
|
1033
|
+
const validation = FileUploadHelper.validateFile(file, {
|
|
1034
|
+
maxSize: 50 * 1024 * 1024, // 50MB
|
|
1035
|
+
allowedTypes: ["image/jpeg", "image/png"],
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
if (validation.valid) {
|
|
1039
|
+
const response = await FileUploadHelper.uploadFile(
|
|
1040
|
+
file,
|
|
1041
|
+
"/api/upload",
|
|
1042
|
+
{
|
|
1043
|
+
chunkSize: 5 * 1024 * 1024, // 5MB chunks
|
|
1044
|
+
onProgress: (progress) => {
|
|
1045
|
+
console.log(`${progress.percentage}% uploaded`);
|
|
1046
|
+
},
|
|
1047
|
+
}
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### Streaming Helper
|
|
1054
|
+
|
|
1055
|
+
- **StreamingHelper**: **EN** stream JSON lines, Server-Sent Events, or download files with progress. **ES** transmite JSON lines, Server-Sent Events o descarga archivos con progreso.
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
import { StreamingHelper } from "bytekit";
|
|
1059
|
+
|
|
1060
|
+
// Stream JSON lines (NDJSON)
|
|
1061
|
+
const { data, complete } = await StreamingHelper.streamJsonLines<User>(
|
|
1062
|
+
"/api/users/stream",
|
|
1063
|
+
{
|
|
1064
|
+
onChunk: (line) => console.log("Received:", line),
|
|
1065
|
+
onComplete: () => console.log("Stream complete"),
|
|
1066
|
+
}
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1069
|
+
// Stream Server-Sent Events
|
|
1070
|
+
const sse = StreamingHelper.streamSSE<Message>("/api/messages", {
|
|
1071
|
+
eventType: "message",
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
const unsubscribe = sse.subscribe((message) => {
|
|
1075
|
+
console.log("New message:", message);
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// Download with progress
|
|
1079
|
+
const blob = await StreamingHelper.downloadStream("/api/export.csv", {
|
|
1080
|
+
onProgress: (percentage) => console.log(`Downloaded: ${percentage}%`),
|
|
1081
|
+
});
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
### WebSocket Helper
|
|
1085
|
+
|
|
1086
|
+
- **WebSocketHelper**: **EN** manage WebSocket connections with auto-reconnect, heartbeat, and typed messages. **ES** gestiona conexiones WebSocket con reconexión automática, heartbeat y mensajes tipados.
|
|
1087
|
+
|
|
1088
|
+
```ts
|
|
1089
|
+
import { WebSocketHelper } from "bytekit";
|
|
1090
|
+
|
|
1091
|
+
const ws = new WebSocketHelper("wss://api.example.com/ws", {
|
|
1092
|
+
reconnect: true,
|
|
1093
|
+
maxReconnectAttempts: 5,
|
|
1094
|
+
heartbeatIntervalMs: 30000,
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
await ws.connect();
|
|
1098
|
+
|
|
1099
|
+
// Subscribe to messages
|
|
1100
|
+
ws.on<{ userId: string; text: string }>("message", (data) => {
|
|
1101
|
+
console.log(`${data.userId}: ${data.text}`);
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
// Send messages
|
|
1105
|
+
ws.send("message", { text: "Hello!" });
|
|
1106
|
+
|
|
1107
|
+
// Request-response pattern
|
|
1108
|
+
const response = await ws.request<{ query: string }, { result: string }>(
|
|
1109
|
+
"query",
|
|
1110
|
+
{ query: "SELECT * FROM users" }
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
// Handle errors
|
|
1114
|
+
ws.onError((error) => console.error("WebSocket error:", error));
|
|
1115
|
+
|
|
1116
|
+
// Close connection
|
|
1117
|
+
ws.close();
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
### Request Caching
|
|
1121
|
+
|
|
1122
|
+
- **RequestCache**: **EN** cache HTTP responses with TTL and stale-while-revalidate support. **ES** cachea respuestas HTTP con TTL y soporte para stale-while-revalidate.
|
|
1123
|
+
|
|
1124
|
+
```ts
|
|
1125
|
+
import { RequestCache } from "bytekit";
|
|
1126
|
+
|
|
1127
|
+
const cache = new RequestCache({
|
|
1128
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
1129
|
+
staleWhileRevalidate: 60 * 1000, // 1 minute
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// Cache a response
|
|
1133
|
+
cache.set("/users", users);
|
|
1134
|
+
|
|
1135
|
+
// Retrieve from cache
|
|
1136
|
+
const cached = cache.get("/users");
|
|
1137
|
+
|
|
1138
|
+
// Check if stale but still valid
|
|
1139
|
+
if (cache.isStale("/users")) {
|
|
1140
|
+
// Revalidate in background
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Invalidate specific entry
|
|
1144
|
+
cache.invalidate("/users");
|
|
1145
|
+
|
|
1146
|
+
// Invalidate by pattern
|
|
1147
|
+
cache.invalidatePattern("/users/*");
|
|
1148
|
+
|
|
1149
|
+
// Get statistics
|
|
1150
|
+
const stats = cache.getStats();
|
|
1151
|
+
console.log(`Hit rate: ${stats.hitRate * 100}%`);
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
### Rate Limiting
|
|
1155
|
+
|
|
1156
|
+
- **RateLimiter**: **EN** token bucket rate limiter for smooth request throttling. **ES** limitador de tasa con token bucket para throttling suave.
|
|
1157
|
+
- **SlidingWindowRateLimiter**: **EN** sliding window rate limiter for precise rate control. **ES** limitador de ventana deslizante para control preciso de tasa.
|
|
1158
|
+
|
|
1159
|
+
```ts
|
|
1160
|
+
import { RateLimiter, SlidingWindowRateLimiter } from "bytekit";
|
|
1161
|
+
|
|
1162
|
+
// Token bucket limiter (allows bursts)
|
|
1163
|
+
const limiter = new RateLimiter({
|
|
1164
|
+
maxRequests: 100,
|
|
1165
|
+
windowMs: 60 * 1000, // 1 minute
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
if (limiter.isAllowed("https://api.example.com/users")) {
|
|
1169
|
+
// Make request
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Wait for allowance if rate limited
|
|
1173
|
+
await limiter.waitForAllowance("https://api.example.com/users");
|
|
1174
|
+
|
|
1175
|
+
// Get stats
|
|
1176
|
+
const stats = limiter.getStats("https://api.example.com/users");
|
|
1177
|
+
console.log(`Remaining: ${stats.remaining}/${stats.limit}`);
|
|
1178
|
+
|
|
1179
|
+
// Sliding window limiter (more accurate)
|
|
1180
|
+
const slidingLimiter = new SlidingWindowRateLimiter({
|
|
1181
|
+
maxRequests: 100,
|
|
1182
|
+
windowMs: 60 * 1000,
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
if (slidingLimiter.isAllowed("https://api.example.com/users")) {
|
|
1186
|
+
// Make request
|
|
1187
|
+
}
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
### Request Deduplication
|
|
1191
|
+
|
|
1192
|
+
- **RequestDeduplicator**: **EN** deduplicate in-flight requests to avoid redundant API calls. **ES** deduplica requests en vuelo para evitar llamadas redundantes.
|
|
1193
|
+
|
|
1194
|
+
```ts
|
|
1195
|
+
import { RequestDeduplicator } from "bytekit";
|
|
1196
|
+
|
|
1197
|
+
const dedup = new RequestDeduplicator();
|
|
1198
|
+
|
|
1199
|
+
// Multiple consumers of the same request share the same response
|
|
1200
|
+
const [users1, users2] = await Promise.all([
|
|
1201
|
+
dedup.execute("/users", () => api.get("/users")),
|
|
1202
|
+
dedup.execute("/users", () => api.get("/users")), // Deduplicated!
|
|
1203
|
+
]);
|
|
1204
|
+
|
|
1205
|
+
// Different requests execute separately
|
|
1206
|
+
const [users, posts] = await Promise.all([
|
|
1207
|
+
dedup.execute("/users", () => api.get("/users")),
|
|
1208
|
+
dedup.execute("/posts", () => api.get("/posts")),
|
|
1209
|
+
]);
|
|
1210
|
+
|
|
1211
|
+
// Get statistics
|
|
1212
|
+
const stats = dedup.getStats();
|
|
1213
|
+
console.log(`Deduplication rate: ${stats.deduplicationRate * 100}%`);
|
|
1214
|
+
|
|
1215
|
+
// Check in-flight requests
|
|
1216
|
+
console.log(`In-flight: ${dedup.getInFlightCount()}`);
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
### Object Utilities
|
|
1220
|
+
|
|
1221
|
+
- **ObjectUtils**: **EN** everyday object manipulation utilities (isEmpty, deepClone, merge, pick, omit, flatten, groupBy, etc.). **ES** utilidades cotidianas para manipular objetos.
|
|
1222
|
+
|
|
1223
|
+
```ts
|
|
1224
|
+
import { ObjectUtils } from "bytekit";
|
|
1225
|
+
|
|
1226
|
+
// Check if empty
|
|
1227
|
+
ObjectUtils.isEmpty(null); // true
|
|
1228
|
+
ObjectUtils.isEmpty({}); // true
|
|
1229
|
+
ObjectUtils.isEmpty([1, 2]); // false
|
|
1230
|
+
|
|
1231
|
+
// Deep clone
|
|
1232
|
+
const original = { a: { b: 1 } };
|
|
1233
|
+
const cloned = ObjectUtils.deepClone(original);
|
|
1234
|
+
|
|
1235
|
+
// Merge objects
|
|
1236
|
+
const merged = ObjectUtils.merge({ a: 1 }, { b: 2 }, { c: 3 });
|
|
1237
|
+
// { a: 1, b: 2, c: 3 }
|
|
1238
|
+
|
|
1239
|
+
// Pick/omit keys
|
|
1240
|
+
const user = {
|
|
1241
|
+
id: 1,
|
|
1242
|
+
name: "John",
|
|
1243
|
+
email: "john@example.com",
|
|
1244
|
+
password: "secret",
|
|
1245
|
+
};
|
|
1246
|
+
const safe = ObjectUtils.omit(user, ["password"]);
|
|
1247
|
+
// { id: 1, name: "John", email: "john@example.com" }
|
|
1248
|
+
|
|
1249
|
+
// Nested access with dot notation
|
|
1250
|
+
const config = { db: { host: "localhost", port: 5432 } };
|
|
1251
|
+
const host = ObjectUtils.get(config, "db.host"); // "localhost"
|
|
1252
|
+
ObjectUtils.set(config, "db.ssl", true);
|
|
1253
|
+
|
|
1254
|
+
// Flatten/unflatten
|
|
1255
|
+
const flat = ObjectUtils.flatten({ a: { b: { c: 1 } } });
|
|
1256
|
+
// { "a.b.c": 1 }
|
|
1257
|
+
|
|
1258
|
+
// Group and index arrays
|
|
1259
|
+
const users = [
|
|
1260
|
+
{ id: 1, role: "admin" },
|
|
1261
|
+
{ id: 2, role: "user" },
|
|
1262
|
+
{ id: 3, role: "admin" },
|
|
1263
|
+
];
|
|
1264
|
+
const byRole = ObjectUtils.groupBy(users, "role");
|
|
1265
|
+
const byId = ObjectUtils.indexBy(users, "id");
|
|
1266
|
+
|
|
1267
|
+
// Filter and map
|
|
1268
|
+
const filtered = ObjectUtils.filter(
|
|
1269
|
+
user,
|
|
1270
|
+
(_, value) => typeof value === "string"
|
|
1271
|
+
);
|
|
1272
|
+
const doubled = ObjectUtils.mapValues({ a: 1, b: 2 }, (v) => v * 2);
|
|
1273
|
+
|
|
1274
|
+
// Deep equality
|
|
1275
|
+
ObjectUtils.deepEqual({ a: 1 }, { a: 1 }); // true
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
### Array Utilities
|
|
1279
|
+
|
|
1280
|
+
- **ArrayUtils**: **EN** everyday array manipulation utilities (chunk, flatten, unique, shuffle, zip, partition, etc.). **ES** utilidades cotidianas para manipular arrays.
|
|
1281
|
+
|
|
1282
|
+
```ts
|
|
1283
|
+
import { ArrayUtils } from "bytekit";
|
|
1284
|
+
|
|
1285
|
+
// Chunk array into smaller pieces
|
|
1286
|
+
ArrayUtils.chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
|
|
1287
|
+
|
|
1288
|
+
// Flatten nested arrays
|
|
1289
|
+
ArrayUtils.flatten(
|
|
1290
|
+
[
|
|
1291
|
+
[1, 2],
|
|
1292
|
+
[3, [4, 5]],
|
|
1293
|
+
],
|
|
1294
|
+
2
|
|
1295
|
+
); // [1, 2, 3, 4, 5]
|
|
1296
|
+
|
|
1297
|
+
// Get unique values
|
|
1298
|
+
ArrayUtils.unique([1, 2, 2, 3, 3, 3]); // [1, 2, 3]
|
|
1299
|
+
ArrayUtils.unique([{ id: 1 }, { id: 2 }, { id: 1 }], (item) => item.id); // [{ id: 1 }, { id: 2 }]
|
|
1300
|
+
|
|
1301
|
+
// Shuffle and random selection
|
|
1302
|
+
const shuffled = ArrayUtils.shuffle([1, 2, 3, 4, 5]);
|
|
1303
|
+
const random = ArrayUtils.random([1, 2, 3, 4, 5]);
|
|
1304
|
+
const randomN = ArrayUtils.randomN([1, 2, 3, 4, 5], 3);
|
|
1305
|
+
|
|
1306
|
+
// Zip and unzip
|
|
1307
|
+
ArrayUtils.zip([1, 2, 3], ["a", "b", "c"]); // [[1, "a"], [2, "b"], [3, "c"]]
|
|
1308
|
+
|
|
1309
|
+
// Set operations
|
|
1310
|
+
ArrayUtils.difference([1, 2, 3, 4], [2, 4]); // [1, 3]
|
|
1311
|
+
ArrayUtils.intersection([1, 2, 3], [2, 3, 4]); // [2, 3]
|
|
1312
|
+
ArrayUtils.union([1, 2], [2, 3]); // [1, 2, 3]
|
|
1313
|
+
|
|
1314
|
+
// Partition by predicate
|
|
1315
|
+
const [evens, odds] = ArrayUtils.partition([1, 2, 3, 4, 5], (n) => n % 2 === 0);
|
|
1316
|
+
// evens: [2, 4], odds: [1, 3, 5]
|
|
1317
|
+
|
|
1318
|
+
// Math operations
|
|
1319
|
+
ArrayUtils.sum([1, 2, 3, 4]); // 10
|
|
1320
|
+
ArrayUtils.average([1, 2, 3, 4]); // 2.5
|
|
1321
|
+
ArrayUtils.min([3, 1, 4, 1, 5]); // 1
|
|
1322
|
+
ArrayUtils.max([3, 1, 4, 1, 5]); // 5
|
|
1323
|
+
|
|
1324
|
+
// Range generation
|
|
1325
|
+
ArrayUtils.range(1, 5); // [1, 2, 3, 4]
|
|
1326
|
+
ArrayUtils.range(0, 10, 2); // [0, 2, 4, 6, 8]
|
|
1327
|
+
|
|
1328
|
+
// Rotate and transpose
|
|
1329
|
+
ArrayUtils.rotate([1, 2, 3, 4], 2); // [3, 4, 1, 2]
|
|
1330
|
+
ArrayUtils.transpose([
|
|
1331
|
+
[1, 2],
|
|
1332
|
+
[3, 4],
|
|
1333
|
+
]); // [[1, 3], [2, 4]]
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
### Error Boundary
|
|
1337
|
+
|
|
1338
|
+
- **ErrorBoundary**: **EN** comprehensive error handling with automatic retry logic, error history tracking, and global error handlers. **ES** manejo completo de errores con reintentos automáticos, historial de errores y handlers globales.
|
|
1339
|
+
|
|
1340
|
+
```ts
|
|
1341
|
+
import {
|
|
1342
|
+
ErrorBoundary,
|
|
1343
|
+
AppError,
|
|
1344
|
+
AppValidationError,
|
|
1345
|
+
NotFoundError,
|
|
1346
|
+
TimeoutError,
|
|
1347
|
+
RateLimitError,
|
|
1348
|
+
} from "bytekit";
|
|
1349
|
+
|
|
1350
|
+
// Create error boundary with custom config
|
|
1351
|
+
const boundary = new ErrorBoundary({
|
|
1352
|
+
maxRetries: 3,
|
|
1353
|
+
retryDelay: 1000,
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
// Handle errors with custom handlers
|
|
1357
|
+
boundary.addHandler(async (error, context) => {
|
|
1358
|
+
console.error(`Error in ${context.component}:`, error.message);
|
|
1359
|
+
// Send to error tracking service
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
// Execute async function with automatic retry
|
|
1363
|
+
try {
|
|
1364
|
+
const data = await boundary.execute(
|
|
1365
|
+
async () => {
|
|
1366
|
+
return await fetchData();
|
|
1367
|
+
},
|
|
1368
|
+
{ component: "DataFetcher", userId: "user_123" }
|
|
1369
|
+
);
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
if (error instanceof TimeoutError) {
|
|
1372
|
+
console.log("Request timed out after retries");
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// Execute sync function with error handling
|
|
1377
|
+
const result = boundary.executeSync(
|
|
1378
|
+
() => {
|
|
1379
|
+
return JSON.parse(jsonString);
|
|
1380
|
+
},
|
|
1381
|
+
{ context: "json-parsing" }
|
|
1382
|
+
);
|
|
1383
|
+
|
|
1384
|
+
// Wrap functions for automatic error handling
|
|
1385
|
+
const wrappedAsync = boundary.wrap(async (id: string) => {
|
|
1386
|
+
return await api.get(`/users/${id}`);
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
const wrappedSync = boundary.wrapSync((data: string) => {
|
|
1390
|
+
return JSON.parse(data);
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
// Get error history
|
|
1394
|
+
const history = boundary.getErrorHistory(10);
|
|
1395
|
+
history.forEach((entry) => {
|
|
1396
|
+
console.log(`${entry.timestamp}: ${entry.error.message}`);
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
// Create error report
|
|
1400
|
+
const report = boundary.createErrorReport();
|
|
1401
|
+
console.log(report);
|
|
1402
|
+
// {
|
|
1403
|
+
// timestamp: "2024-12-20T10:30:00.000Z",
|
|
1404
|
+
// errors: [
|
|
1405
|
+
// {
|
|
1406
|
+
// code: "TIMEOUT",
|
|
1407
|
+
// message: "Request timeout",
|
|
1408
|
+
// statusCode: 408,
|
|
1409
|
+
// timestamp: 1703068200000
|
|
1410
|
+
// }
|
|
1411
|
+
// ]
|
|
1412
|
+
// }
|
|
1413
|
+
|
|
1414
|
+
// Use global error boundary
|
|
1415
|
+
import { getGlobalErrorBoundary } from "bytekit";
|
|
1416
|
+
|
|
1417
|
+
const globalBoundary = getGlobalErrorBoundary({
|
|
1418
|
+
maxRetries: 2,
|
|
1419
|
+
retryDelay: 500,
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
// Automatically catches unhandled rejections and global errors
|
|
1423
|
+
globalBoundary.addHandler((error) => {
|
|
1424
|
+
console.error("Unhandled error:", error);
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
// Custom error types
|
|
1428
|
+
try {
|
|
1429
|
+
throw new AppValidationError("Invalid email format", {
|
|
1430
|
+
component: "SignupForm",
|
|
1431
|
+
});
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
if (error instanceof AppValidationError) {
|
|
1434
|
+
console.log("Validation failed:", error.message);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
try {
|
|
1439
|
+
throw new RateLimitError("Too many requests", 60, {
|
|
1440
|
+
context: "api-call",
|
|
1441
|
+
});
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
if (error instanceof RateLimitError) {
|
|
1444
|
+
const retryAfter = error.context?.metadata?.retryAfter;
|
|
1445
|
+
console.log(`Retry after ${retryAfter} seconds`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
### Form Utilities
|
|
1451
|
+
|
|
1452
|
+
- **FormUtils**: **EN** form validation and state management with built-in validators, async validation, and framework-agnostic design. **ES** validación de formularios y gestión de estado con validadores integrados, validación async y agnóstico del framework.
|
|
1453
|
+
|
|
1454
|
+
```ts
|
|
1455
|
+
import { FormUtils, createForm, Validators } from "bytekit";
|
|
1456
|
+
|
|
1457
|
+
// Create form with validation rules
|
|
1458
|
+
const form = new FormUtils({
|
|
1459
|
+
initialValues: {
|
|
1460
|
+
email: "",
|
|
1461
|
+
password: "",
|
|
1462
|
+
confirmPassword: "",
|
|
1463
|
+
},
|
|
1464
|
+
rules: {
|
|
1465
|
+
email: {
|
|
1466
|
+
required: "Email is required",
|
|
1467
|
+
email: "Invalid email format",
|
|
1468
|
+
},
|
|
1469
|
+
password: {
|
|
1470
|
+
required: true,
|
|
1471
|
+
minLength: 8,
|
|
1472
|
+
custom: (value) => {
|
|
1473
|
+
return /[A-Z]/.test(value)
|
|
1474
|
+
? true
|
|
1475
|
+
: "Must contain uppercase letter";
|
|
1476
|
+
},
|
|
1477
|
+
},
|
|
1478
|
+
confirmPassword: {
|
|
1479
|
+
required: true,
|
|
1480
|
+
custom: (value) => {
|
|
1481
|
+
return Validators.match(value, form.getValue("password"))
|
|
1482
|
+
? true
|
|
1483
|
+
: "Passwords do not match";
|
|
1484
|
+
},
|
|
1485
|
+
},
|
|
1486
|
+
},
|
|
1487
|
+
validateOnChange: true,
|
|
1488
|
+
validateOnBlur: true,
|
|
1489
|
+
onSubmit: async (values) => {
|
|
1490
|
+
console.log("Form submitted:", values);
|
|
1491
|
+
},
|
|
1492
|
+
onError: (errors) => {
|
|
1493
|
+
console.error("Validation errors:", errors);
|
|
1494
|
+
},
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
// Update field value
|
|
1498
|
+
form.setValue("email", "user@example.com");
|
|
1499
|
+
|
|
1500
|
+
// Get field state
|
|
1501
|
+
const emailError = form.getFieldError("email");
|
|
1502
|
+
const isTouched = form.isTouched("email");
|
|
1503
|
+
const isDirty = form.isDirty("email");
|
|
1504
|
+
|
|
1505
|
+
// Validate single field
|
|
1506
|
+
await form.validateField("email");
|
|
1507
|
+
|
|
1508
|
+
// Validate all fields
|
|
1509
|
+
const errors = await form.validate();
|
|
1510
|
+
|
|
1511
|
+
// Submit form
|
|
1512
|
+
const success = await form.submit();
|
|
1513
|
+
|
|
1514
|
+
// Get form state
|
|
1515
|
+
const state = form.getState();
|
|
1516
|
+
console.log(state);
|
|
1517
|
+
// {
|
|
1518
|
+
// values: { email: "...", password: "...", confirmPassword: "..." },
|
|
1519
|
+
// errors: { email: "", password: "", confirmPassword: "" },
|
|
1520
|
+
// touched: { email: true, password: true, confirmPassword: false },
|
|
1521
|
+
// dirty: { email: true, password: true, confirmPassword: false },
|
|
1522
|
+
// isValidating: false,
|
|
1523
|
+
// isValid: true
|
|
1524
|
+
// }
|
|
1525
|
+
|
|
1526
|
+
// Create bindings for framework integration (React, Vue, etc.)
|
|
1527
|
+
const emailBinding = form.createBinding("email");
|
|
1528
|
+
// {
|
|
1529
|
+
// value: "user@example.com",
|
|
1530
|
+
// onChange: (value) => form.setValue("email", value),
|
|
1531
|
+
// onBlur: () => form.touchField("email"),
|
|
1532
|
+
// error: "",
|
|
1533
|
+
// touched: true,
|
|
1534
|
+
// dirty: true
|
|
1535
|
+
// }
|
|
1536
|
+
|
|
1537
|
+
// Reset form
|
|
1538
|
+
form.reset();
|
|
1539
|
+
|
|
1540
|
+
// Serialize/deserialize
|
|
1541
|
+
const data = form.serialize();
|
|
1542
|
+
form.deserialize({ email: "new@example.com" });
|
|
1543
|
+
|
|
1544
|
+
// Built-in validators
|
|
1545
|
+
Validators.required("value"); // true
|
|
1546
|
+
Validators.email("test@example.com"); // true
|
|
1547
|
+
Validators.minLength("password", 8); // true
|
|
1548
|
+
Validators.maxLength("username", 20); // true
|
|
1549
|
+
Validators.pattern("12345", /^\d+$/); // true
|
|
1550
|
+
Validators.url("https://example.com"); // true
|
|
1551
|
+
Validators.match("password", "password"); // true
|
|
1552
|
+
|
|
1553
|
+
// Factory function
|
|
1554
|
+
const signupForm = createForm({
|
|
1555
|
+
initialValues: { username: "", email: "" },
|
|
1556
|
+
rules: {
|
|
1557
|
+
username: { required: true, minLength: 3 },
|
|
1558
|
+
email: { required: true, email: true },
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
import {
|
|
1566
|
+
createLogger,
|
|
1567
|
+
withTiming,
|
|
1568
|
+
createStopwatch,
|
|
1569
|
+
StorageManager,
|
|
1570
|
+
EnvManager,
|
|
1571
|
+
} from "bytekit";
|
|
1572
|
+
|
|
1573
|
+
const logger = createLogger({ namespace: "payments", level: "debug" });
|
|
1574
|
+
|
|
1575
|
+
await withTiming("settlements", async () => {
|
|
1576
|
+
const stopwatch = createStopwatch({ label: "batch-download", logger });
|
|
1577
|
+
const batch = await downloadPayments();
|
|
1578
|
+
stopwatch.log({ records: batch.length });
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
const storage = new StorageManager();
|
|
1582
|
+
storage.set("token", "abc123", 60_000);
|
|
1583
|
+
const env = new EnvManager();
|
|
1584
|
+
const apiKey = env.require("API_KEY");
|
|
1585
|
+
|
|
1586
|
+
```
|
|
1587
|
+
|
|
1588
|
+
- `DateUtils`: **EN** safe parsing, add/subtract, configurable diffs, `isSameDay`. **ES** parseo seguro, sumas/restas, diferencias configurables e `isSameDay`.
|
|
1589
|
+
- `StringUtils`: **EN** slugify, capitalize, masking, interpolation, query strings. **ES** slugify, capitalización, máscaras, interpolación, query strings.
|
|
1590
|
+
- `Validator`: **EN** lightweight synchronous validators. **ES** validadores sincrónicos livianos.
|
|
1591
|
+
- `StorageManager`: **EN** safe wrapper for `localStorage`/`sessionStorage`. **ES** adaptador seguro para storage del navegador.
|
|
1592
|
+
|
|
1593
|
+
## Toolkit Catalog / Catálogo de herramientas
|
|
1594
|
+
|
|
1595
|
+
### ApiClient
|
|
1596
|
+
|
|
1597
|
+
- **EN**: Typed HTTP client with retries, localized errors, interceptors, and custom fetch support for Node/browsers.
|
|
1598
|
+
**ES**: Cliente HTTP tipado con reintentos, errores localizados, interceptores y `fetch` personalizable para Node/navegadores.
|
|
1599
|
+
|
|
1600
|
+
```ts
|
|
1601
|
+
import { ApiClient } from "bytekit";
|
|
1602
|
+
|
|
1603
|
+
const api = new ApiClient({
|
|
1604
|
+
baseUrl: "https://api.example.com",
|
|
1605
|
+
defaultHeaders: { "X-Team": "development" },
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
const user = await api.get("/users/1", {
|
|
1609
|
+
searchParams: { locale: "es" },
|
|
1610
|
+
});
|
|
1611
|
+
```
|
|
1612
|
+
|
|
1613
|
+
### createLogger
|
|
1614
|
+
|
|
1615
|
+
- **EN**: Structured logger with levels, namespaces, transports for Node/browser, and child loggers.
|
|
1616
|
+
**ES**: Logger estructurado con niveles, namespaces, transports para Node/browser y loggers hijos.
|
|
1617
|
+
|
|
1618
|
+
```ts
|
|
1619
|
+
import { createLogger } from "bytekit";
|
|
1620
|
+
|
|
1621
|
+
const logger = createLogger({ namespace: "payments", level: "info" });
|
|
1622
|
+
logger.warn("payment delayed", { id: "tx_1" });
|
|
1623
|
+
|
|
1624
|
+
const workerLogger = logger.child("worker");
|
|
1625
|
+
workerLogger.debug("processing batch", { size: 20 });
|
|
1626
|
+
```
|
|
1627
|
+
|
|
1628
|
+
### Timing & Debug Utilities
|
|
1629
|
+
|
|
1630
|
+
- **EN**: `createStopwatch`, `withTiming`, `measureAsync`, `captureDebug`, and `Profiler` help you capture execution times and emit logs automatically.
|
|
1631
|
+
**ES**: `createStopwatch`, `withTiming`, `measureAsync`, `captureDebug` y `Profiler` facilitan medir tiempos y loguear automáticamente.
|
|
1632
|
+
|
|
1633
|
+
```ts
|
|
1634
|
+
import {
|
|
1635
|
+
createStopwatch,
|
|
1636
|
+
withTiming,
|
|
1637
|
+
measureAsync,
|
|
1638
|
+
Profiler,
|
|
1639
|
+
} from "bytekit";
|
|
1640
|
+
|
|
1641
|
+
const stopwatch = createStopwatch({ label: "sync-users" });
|
|
1642
|
+
// ... run task
|
|
1643
|
+
stopwatch.log({ records: 42 });
|
|
1644
|
+
|
|
1645
|
+
await withTiming("refresh-cache", async () => fetchCache());
|
|
1646
|
+
const { result, durationMs } = await measureAsync("bill-run", () =>
|
|
1647
|
+
processBills()
|
|
1648
|
+
);
|
|
1649
|
+
|
|
1650
|
+
const profiler = new Profiler();
|
|
1651
|
+
profiler.start("db");
|
|
1652
|
+
await queryDb();
|
|
1653
|
+
profiler.end("db");
|
|
1654
|
+
console.table(profiler.summary());
|
|
1655
|
+
```
|
|
1656
|
+
|
|
1657
|
+
### DateUtils
|
|
1658
|
+
|
|
1659
|
+
- **parse / isValid**: **EN** accept `Date`, ISO strings, timestamps; return normalized Date or boolean. **ES** aceptan `Date`, string ISO o timestamp y devuelven Date normalizada o booleano.
|
|
1660
|
+
- **toISODate**: **EN** format to `YYYY-MM-DD` without timezone surprises. **ES** formatea como `YYYY-MM-DD` evitando problemas de zona horaria.
|
|
1661
|
+
- **startOfDay / endOfDay**: **EN** clamp hours to `00:00:00.000` or `23:59:59.999`. **ES** ajusta horas al inicio o final del día.
|
|
1662
|
+
- **add**: **EN** add duration (`days`, `hours`, `minutes`, `seconds`, `milliseconds`). **ES** suma duraciones con granularidad configurable.
|
|
1663
|
+
- **diff / diffInDays**: **EN** difference between two dates with unit + rounding + absolute options. **ES** diferencia entre fechas con unidad, redondeo y valor absoluto configurable.
|
|
1664
|
+
- **isSameDay / isBefore / isAfter**: **EN** compare normalized dates. **ES** compara fechas normalizadas.
|
|
1665
|
+
- **format**: **EN** locale-aware short date (`es-AR` default). **ES** formatea con `toLocaleDateString`.
|
|
1666
|
+
|
|
1667
|
+
```ts
|
|
1668
|
+
DateUtils.isSameDay("2024-10-10", new Date());
|
|
1669
|
+
DateUtils.diff(new Date("2024-01-01"), Date.now(), {
|
|
1670
|
+
unit: "days",
|
|
1671
|
+
rounding: "round",
|
|
1672
|
+
absolute: true,
|
|
1673
|
+
});
|
|
1674
|
+
```
|
|
1675
|
+
|
|
1676
|
+
### StringUtils
|
|
1677
|
+
|
|
1678
|
+
- **removeDiacritics / compactWhitespace**: **EN** normalize text for comparisons or rendering. **ES** normalizan texto para comparaciones o UI.
|
|
1679
|
+
- **slugify**: **EN** create URL-friendly IDs with configurable separator/lowercase. **ES** genera slugs configurables.
|
|
1680
|
+
- **capitalize / capitalizeWords**: **EN/ES** capitaliza respetando locale.
|
|
1681
|
+
- **truncate**: **EN** trims long strings with optional ellipsis + word boundaries. **ES** recorta textos largos respetando palabras.
|
|
1682
|
+
- **mask**: **EN** hide sensitive parts with custom `maskChar`, `visibleStart`, `visibleEnd`. **ES** oculta secciones sensibles con máscara configurable.
|
|
1683
|
+
- **interpolate**: **EN** replace `{{placeholders}}` with nested object values (strict/fallback/transform). **ES** interpolación con soporte para rutas y validación.
|
|
1684
|
+
- **initials**: **EN** generate up to `limit` initials. **ES** genera iniciales rápido.
|
|
1685
|
+
- **toQueryString**: **EN** serialize nested objects/arrays with formats (`repeat`, `bracket`, `comma`). **ES** serializa objetos y arrays a query strings.
|
|
1686
|
+
|
|
1687
|
+
```ts
|
|
1688
|
+
StringUtils.mask("4242424242424242", { visibleStart: 4, visibleEnd: 2 });
|
|
1689
|
+
StringUtils.toQueryString({
|
|
1690
|
+
page: 1,
|
|
1691
|
+
tags: ["lab", "team"],
|
|
1692
|
+
filters: { status: "active" },
|
|
1693
|
+
});
|
|
1694
|
+
```
|
|
1695
|
+
|
|
1696
|
+
### StorageManager
|
|
1697
|
+
|
|
1698
|
+
- **StorageManager**: **EN** wraps any `Storage` (default `localStorage`) with safe JSON parsing and TTL support. **ES** envuelve cualquier `Storage` con parseo seguro y expiración opcional.
|
|
1699
|
+
- **set/get/remove/clear**: **EN** persist typed values, remove expired entries automatically. **ES** guarda valores tipados y limpia expirados.
|
|
1700
|
+
|
|
1701
|
+
```ts
|
|
1702
|
+
const storage = new StorageManager(sessionStorage);
|
|
1703
|
+
storage.set("session", { token: "abc" }, 60_000);
|
|
1704
|
+
const session = storage.get<{ token: string }>("session");
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
### EnvManager
|
|
1708
|
+
|
|
1709
|
+
- **get / require**: **EN** read ENV vars from Node (via `process.env`) or Vite-style browser builds (`import.meta.env`). **ES** lee env vars en Node o navegador y marca obligatorias con `require`.
|
|
1710
|
+
- **isProd**: **EN** check `NODE_ENV`/`MODE`. **ES** detecta modo producción.
|
|
1711
|
+
|
|
1712
|
+
```ts
|
|
1713
|
+
const env = new EnvManager();
|
|
1714
|
+
const apiBase = env.require("API_BASE_URL");
|
|
1715
|
+
if (env.isProd()) {
|
|
1716
|
+
// toggle prod-only behavior
|
|
1717
|
+
}
|
|
1718
|
+
```
|
|
1719
|
+
|
|
1720
|
+
### Validator
|
|
1721
|
+
|
|
1722
|
+
- **Identity**: **EN** `isEmail`, `isUUIDv4`, `isDni`, `isCuit`, `isCbu`. **ES** validaciones de identidad y banking locales.
|
|
1723
|
+
- **Phones**: **EN** `isInternationalPhone`, `isPhoneE164`, `isLocalPhone(locale)`. **ES** valida teléfonos internacionales y locales con patrones por país.
|
|
1724
|
+
- **Security**: **EN** `isStrongPassword`, `isOneTimeCode`. **ES** contraseñas fuertes y códigos OTP.
|
|
1725
|
+
- **General**: **EN** `isUrl`, `isEmpty`, length guards, regex matcher, `isDateRange`. **ES** helpers generales para formularios.
|
|
1726
|
+
|
|
1727
|
+
```ts
|
|
1728
|
+
Validator.isStrongPassword("SecurePass!2024", { minLength: 10 });
|
|
1729
|
+
Validator.isLocalPhone("11 5555-7777", "es-AR");
|
|
1730
|
+
```
|
|
1731
|
+
|
|
1732
|
+
### EventEmitter
|
|
1733
|
+
|
|
1734
|
+
- **EN**: Pub/sub event system with sync/async listeners, once listeners, error handling, and listener tracking.
|
|
1735
|
+
**ES**: Sistema de eventos pub/sub con listeners síncronos/asíncronos, listeners únicos, manejo de errores y seguimiento.
|
|
1736
|
+
|
|
1737
|
+
```ts
|
|
1738
|
+
import { EventEmitter, createEventEmitter } from "bytekit";
|
|
1739
|
+
|
|
1740
|
+
// Create event emitter with typed events
|
|
1741
|
+
const emitter = new EventEmitter<{
|
|
1742
|
+
"user:login": { userId: string; timestamp: number };
|
|
1743
|
+
"user:logout": { userId: string };
|
|
1744
|
+
error: Error;
|
|
1745
|
+
}>();
|
|
1746
|
+
|
|
1747
|
+
// Register listeners
|
|
1748
|
+
emitter.on("user:login", async (data) => {
|
|
1749
|
+
console.log(`User ${data.userId} logged in`);
|
|
1750
|
+
await trackEvent(data);
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
// One-time listener
|
|
1754
|
+
emitter.once("user:logout", (data) => {
|
|
1755
|
+
console.log(`User ${data.userId} logged out`);
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
// Emit events
|
|
1759
|
+
await emitter.emit("user:login", {
|
|
1760
|
+
userId: "user_123",
|
|
1761
|
+
timestamp: Date.now(),
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
// Sync emit
|
|
1765
|
+
emitter.emitSync("user:logout", { userId: "user_123" });
|
|
1766
|
+
|
|
1767
|
+
// Error handling
|
|
1768
|
+
emitter.onError((data, error) => {
|
|
1769
|
+
console.error("Event error:", error);
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
// Listener management
|
|
1773
|
+
const count = emitter.listenerCount("user:login");
|
|
1774
|
+
emitter.removeAllListeners("user:login");
|
|
1775
|
+
|
|
1776
|
+
// Factory function
|
|
1777
|
+
const events = createEventEmitter<{ message: string }>();
|
|
1778
|
+
```
|
|
1779
|
+
|
|
1780
|
+
### DiffUtils
|
|
1781
|
+
|
|
1782
|
+
- **EN**: Deep object comparison, patch generation/application, and merge strategies for tracking changes.
|
|
1783
|
+
**ES**: Comparación profunda de objetos, generación/aplicación de parches y estrategias de merge para rastrear cambios.
|
|
1784
|
+
|
|
1785
|
+
```ts
|
|
1786
|
+
import { DiffUtils } from "bytekit";
|
|
1787
|
+
|
|
1788
|
+
// Detect changes
|
|
1789
|
+
const old = { name: "John", age: 30, email: "john@example.com" };
|
|
1790
|
+
const new_ = { name: "Jane", age: 31, phone: "555-1234" };
|
|
1791
|
+
|
|
1792
|
+
const diff = DiffUtils.diff(old, new_);
|
|
1793
|
+
console.log(diff);
|
|
1794
|
+
// {
|
|
1795
|
+
// changed: ["name", "age"],
|
|
1796
|
+
// added: ["phone"],
|
|
1797
|
+
// removed: ["email"]
|
|
1798
|
+
// }
|
|
1799
|
+
|
|
1800
|
+
// Create patches (JSON Patch format)
|
|
1801
|
+
const patches = DiffUtils.createPatch(old, new_);
|
|
1802
|
+
console.log(patches);
|
|
1803
|
+
// [
|
|
1804
|
+
// { op: "replace", path: "name", value: "Jane" },
|
|
1805
|
+
// { op: "replace", path: "age", value: 31 },
|
|
1806
|
+
// { op: "add", path: "phone", value: "555-1234" },
|
|
1807
|
+
// { op: "remove", path: "email" }
|
|
1808
|
+
// ]
|
|
1809
|
+
|
|
1810
|
+
// Apply patches
|
|
1811
|
+
const result = DiffUtils.applyPatch(old, patches);
|
|
1812
|
+
console.log(result); // { name: "Jane", age: 31, phone: "555-1234" }
|
|
1813
|
+
|
|
1814
|
+
// Deep equality check
|
|
1815
|
+
DiffUtils.deepEqual({ a: { b: 1 } }, { a: { b: 1 } }); // true
|
|
1816
|
+
DiffUtils.deepEqual({ a: { b: 1 } }, { a: { b: 2 } }); // false
|
|
1817
|
+
```
|
|
1818
|
+
|
|
1819
|
+
### PollingHelper
|
|
1820
|
+
|
|
1821
|
+
- **EN**: Intelligent polling with exponential backoff, stop conditions, and max attempts for async operations.
|
|
1822
|
+
**ES**: Polling inteligente con backoff exponencial, condiciones de parada e intentos máximos para operaciones async.
|
|
1823
|
+
|
|
1824
|
+
```ts
|
|
1825
|
+
import { PollingHelper, createPoller } from "bytekit";
|
|
1826
|
+
|
|
1827
|
+
// Poll until condition is met
|
|
1828
|
+
const poller = new PollingHelper(
|
|
1829
|
+
async () => {
|
|
1830
|
+
const status = await checkJobStatus();
|
|
1831
|
+
return status === "completed";
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
interval: 1000, // Start with 1 second
|
|
1835
|
+
maxAttempts: 30,
|
|
1836
|
+
backoffMultiplier: 1.5, // Exponential backoff
|
|
1837
|
+
stopCondition: (result) => result === true,
|
|
1838
|
+
}
|
|
1839
|
+
);
|
|
1840
|
+
|
|
1841
|
+
const result = await poller.start();
|
|
1842
|
+
console.log(result);
|
|
1843
|
+
// {
|
|
1844
|
+
// success: true,
|
|
1845
|
+
// attempts: 5,
|
|
1846
|
+
// lastResult: true,
|
|
1847
|
+
// totalTimeMs: 3500
|
|
1848
|
+
// }
|
|
1849
|
+
|
|
1850
|
+
// Handle failure
|
|
1851
|
+
if (!result.success) {
|
|
1852
|
+
console.log(`Failed after ${result.attempts} attempts`);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// Factory function
|
|
1856
|
+
const filePoller = createPoller(
|
|
1857
|
+
async () => {
|
|
1858
|
+
return await fileExists("output.txt");
|
|
1859
|
+
},
|
|
1860
|
+
{ interval: 500, maxAttempts: 60 }
|
|
1861
|
+
);
|
|
1862
|
+
```
|
|
1863
|
+
|
|
1864
|
+
### CryptoUtils
|
|
1865
|
+
|
|
1866
|
+
- **EN**: Token/UUID generation, base64 encoding, hashing, HMAC, and constant-time comparison for security.
|
|
1867
|
+
**ES**: Generación de tokens/UUIDs, codificación base64, hashing, HMAC y comparación en tiempo constante para seguridad.
|
|
1868
|
+
|
|
1869
|
+
```ts
|
|
1870
|
+
import { CryptoUtils } from "bytekit";
|
|
1871
|
+
|
|
1872
|
+
// Generate secure tokens
|
|
1873
|
+
const token = CryptoUtils.generateToken(32); // 64 hex chars
|
|
1874
|
+
const uuid = CryptoUtils.generateUUID(); // v4 UUID
|
|
1875
|
+
|
|
1876
|
+
// Base64 encoding
|
|
1877
|
+
const encoded = CryptoUtils.base64Encode("Hello, World!");
|
|
1878
|
+
const decoded = CryptoUtils.base64Decode(encoded);
|
|
1879
|
+
|
|
1880
|
+
// URL-safe base64 (no +, /, =)
|
|
1881
|
+
const urlSafe = CryptoUtils.base64UrlEncode("data+with/special=chars");
|
|
1882
|
+
const restored = CryptoUtils.base64UrlDecode(urlSafe);
|
|
1883
|
+
|
|
1884
|
+
// Hashing
|
|
1885
|
+
const hash = await CryptoUtils.hash("password");
|
|
1886
|
+
const isValid = await CryptoUtils.verifyHash("password", hash);
|
|
1887
|
+
|
|
1888
|
+
// Constant-time comparison (prevents timing attacks)
|
|
1889
|
+
const match = CryptoUtils.constantTimeCompare(token1, token2);
|
|
1890
|
+
|
|
1891
|
+
// HMAC signing
|
|
1892
|
+
const signature = await CryptoUtils.hmac("message", "secret");
|
|
1893
|
+
```
|
|
1894
|
+
|
|
1895
|
+
### PaginationHelper
|
|
1896
|
+
|
|
1897
|
+
- **EN**: Offset-limit and cursor-based pagination with state tracking and navigation.
|
|
1898
|
+
**ES**: Paginación offset-limit y basada en cursores con seguimiento de estado y navegación.
|
|
1899
|
+
|
|
1900
|
+
```ts
|
|
1901
|
+
import { PaginationHelper, createPaginator } from "bytekit";
|
|
1902
|
+
|
|
1903
|
+
const items = Array.from({ length: 100 }, (_, i) => ({ id: i + 1 }));
|
|
1904
|
+
|
|
1905
|
+
// Offset-limit pagination
|
|
1906
|
+
const paginator = new PaginationHelper(items, {
|
|
1907
|
+
pageSize: 10,
|
|
1908
|
+
mode: "offset",
|
|
1909
|
+
});
|
|
1910
|
+
|
|
1911
|
+
// Get current page
|
|
1912
|
+
const page1 = paginator.getCurrentPage(); // First 10 items
|
|
1913
|
+
|
|
1914
|
+
// Navigate
|
|
1915
|
+
paginator.next();
|
|
1916
|
+
const page2 = paginator.getCurrentPage(); // Items 11-20
|
|
1917
|
+
|
|
1918
|
+
paginator.previous();
|
|
1919
|
+
const backToPage1 = paginator.getCurrentPage();
|
|
1920
|
+
|
|
1921
|
+
// Jump to specific page
|
|
1922
|
+
paginator.goToPage(5);
|
|
1923
|
+
|
|
1924
|
+
// Get state
|
|
1925
|
+
const state = paginator.getState();
|
|
1926
|
+
console.log(state);
|
|
1927
|
+
// {
|
|
1928
|
+
// currentPage: 5,
|
|
1929
|
+
// pageSize: 10,
|
|
1930
|
+
// total: 100,
|
|
1931
|
+
// totalPages: 10,
|
|
1932
|
+
// hasNextPage: true,
|
|
1933
|
+
// hasPreviousPage: true,
|
|
1934
|
+
// offset: 40,
|
|
1935
|
+
// limit: 10
|
|
1936
|
+
// }
|
|
1937
|
+
|
|
1938
|
+
// Cursor-based pagination
|
|
1939
|
+
const cursorPaginator = new PaginationHelper(items, {
|
|
1940
|
+
pageSize: 10,
|
|
1941
|
+
mode: "cursor",
|
|
1942
|
+
cursorField: "id",
|
|
1943
|
+
});
|
|
1944
|
+
|
|
1945
|
+
// Factory function
|
|
1946
|
+
const userPaginator = createPaginator(users, { pageSize: 20 });
|
|
1947
|
+
```
|
|
1948
|
+
|
|
1949
|
+
### CacheManager
|
|
1950
|
+
|
|
1951
|
+
- **EN**: Multi-tier cache (memory + localStorage) with TTL, LRU eviction, and statistics for performance optimization.
|
|
1952
|
+
**ES**: Cache multi-nivel (memoria + localStorage) con TTL, evicción LRU y estadísticas para optimización de rendimiento.
|
|
1953
|
+
|
|
1954
|
+
```ts
|
|
1955
|
+
import { CacheManager, createCacheManager } from "bytekit";
|
|
1956
|
+
|
|
1957
|
+
const cache = new CacheManager({
|
|
1958
|
+
maxSize: 100, // Max entries
|
|
1959
|
+
ttl: 5 * 60 * 1000, // 5 minutes default
|
|
1960
|
+
useLocalStorage: true, // Enable persistent cache
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1963
|
+
// Set and get
|
|
1964
|
+
cache.set("user:123", { id: 123, name: "John" }, 60_000); // 1 minute TTL
|
|
1965
|
+
const user = cache.get("user:123");
|
|
1966
|
+
|
|
1967
|
+
// Get or compute
|
|
1968
|
+
const data = await cache.getOrCompute(
|
|
1969
|
+
"expensive:key",
|
|
1970
|
+
async () => {
|
|
1971
|
+
return await fetchExpensiveData();
|
|
1972
|
+
},
|
|
1973
|
+
10 * 60 * 1000
|
|
1974
|
+
); // Cache for 10 minutes
|
|
1975
|
+
|
|
1976
|
+
// Check if exists
|
|
1977
|
+
if (cache.has("user:123")) {
|
|
1978
|
+
console.log("User cached");
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
// Remove and clear
|
|
1982
|
+
cache.remove("user:123");
|
|
1983
|
+
cache.clear();
|
|
1984
|
+
|
|
1985
|
+
// Statistics
|
|
1986
|
+
const stats = cache.getStats();
|
|
1987
|
+
console.log(stats);
|
|
1988
|
+
// {
|
|
1989
|
+
// hits: 42,
|
|
1990
|
+
// misses: 8,
|
|
1991
|
+
// hitRate: 0.84,
|
|
1992
|
+
// size: 15,
|
|
1993
|
+
// maxSize: 100
|
|
1994
|
+
// }
|
|
1995
|
+
|
|
1996
|
+
// Invalidate by pattern
|
|
1997
|
+
cache.invalidatePattern("user:*");
|
|
1998
|
+
|
|
1999
|
+
// Factory function
|
|
2000
|
+
const apiCache = createCacheManager({ maxSize: 50 });
|
|
2001
|
+
```
|
|
2002
|
+
|
|
2003
|
+
### CompressionUtils
|
|
2004
|
+
|
|
2005
|
+
- **EN**: String compression, base64 encoding, JSON minification, and gzip/deflate support for data optimization.
|
|
2006
|
+
**ES**: Compresión de strings, codificación base64, minificación JSON y soporte gzip/deflate para optimización de datos.
|
|
2007
|
+
|
|
2008
|
+
```ts
|
|
2009
|
+
import { CompressionUtils } from "bytekit";
|
|
2010
|
+
|
|
2011
|
+
// Compress and decompress
|
|
2012
|
+
const original = "Hello World Hello World Hello World";
|
|
2013
|
+
const compressed = CompressionUtils.compress(original);
|
|
2014
|
+
const decompressed = CompressionUtils.decompress(compressed);
|
|
2015
|
+
|
|
2016
|
+
// Base64 encoding
|
|
2017
|
+
const encoded = CompressionUtils.base64Encode("data");
|
|
2018
|
+
const decoded = CompressionUtils.base64Decode(encoded);
|
|
2019
|
+
|
|
2020
|
+
// URL-safe base64
|
|
2021
|
+
const urlSafe = CompressionUtils.base64UrlEncode("data+special/chars=");
|
|
2022
|
+
|
|
2023
|
+
// JSON utilities
|
|
2024
|
+
const json = '{ "name": "John", "age": 30 }';
|
|
2025
|
+
const minified = CompressionUtils.minifyJSON(json);
|
|
2026
|
+
const pretty = CompressionUtils.prettyJSON(minified, 2);
|
|
2027
|
+
|
|
2028
|
+
// Compression ratio
|
|
2029
|
+
const ratio = CompressionUtils.getCompressionRatio(original, compressed);
|
|
2030
|
+
console.log(`Compression: ${ratio.toFixed(2)}%`);
|
|
2031
|
+
|
|
2032
|
+
// Format bytes
|
|
2033
|
+
CompressionUtils.formatBytes(1024); // "1 KB"
|
|
2034
|
+
CompressionUtils.formatBytes(1024 * 1024); // "1 MB"
|
|
2035
|
+
|
|
2036
|
+
// Serialize/deserialize with compression
|
|
2037
|
+
const obj = { users: [{ id: 1, name: "John" }] };
|
|
2038
|
+
const serialized = CompressionUtils.serializeCompressed(obj);
|
|
2039
|
+
const restored = CompressionUtils.deserializeCompressed(serialized);
|
|
2040
|
+
|
|
2041
|
+
// Gzip (Node.js)
|
|
2042
|
+
const gzipped = await CompressionUtils.gzip(original);
|
|
2043
|
+
const gunzipped = await CompressionUtils.gunzip(gzipped);
|
|
2044
|
+
|
|
2045
|
+
// Deflate (Node.js)
|
|
2046
|
+
const deflated = await CompressionUtils.deflate(original);
|
|
2047
|
+
const inflated = await CompressionUtils.inflate(deflated);
|
|
2048
|
+
```
|
|
2049
|
+
|
|
2050
|
+
## Compatibility / Compatibilidad
|
|
2051
|
+
|
|
2052
|
+
- Node.js >= 18 (ESM, `fetch`, `AbortController`, `URL`).
|
|
2053
|
+
- Modern browsers (ships optional `cross-fetch` polyfill).
|
|
2054
|
+
|
|
2055
|
+
## CLI scaffolding / Generador CLI
|
|
2056
|
+
|
|
2057
|
+
**EN:** Install the package globally or invoke it with `npx`. The `sutils` binary provides two powerful commands: scaffolding resource folders with CRUD helpers and generating TypeScript types from API responses.
|
|
2058
|
+
**ES:** Instalá el paquete globalmente o usalo con `npx`. El binario `sutils` proporciona dos comandos poderosos: crear carpetas de recursos con helpers CRUD y generar tipos TypeScript desde respuestas de API.
|
|
2059
|
+
|
|
2060
|
+
### Create Command / Comando Create
|
|
2061
|
+
|
|
2062
|
+
```bash
|
|
2063
|
+
npx sutils create users
|
|
2064
|
+
```
|
|
2065
|
+
|
|
2066
|
+
**What is generated / Qué se genera:**
|
|
2067
|
+
|
|
2068
|
+
- `api/<resource>/index.ts`: typed CRUD helpers built on `bytekit`' `ApiClient`, including shape placeholders, filter helpers, and `list/get/create/update/delete` functions.
|
|
2069
|
+
- `hooks/<resource>/use<ResourcePlural>.ts`: React Query hooks (`use<ResourcePlural>`, `use<Resource>`, `useCreate<Resource>`, `useUpdate<Resource>`, `useDelete<Resource>`) that invalidate the corresponding queries and wire mutations to `@tanstack/react-query`.
|
|
2070
|
+
- `hooks/<resource>/index.ts`: re-exports the generated hooks.
|
|
2071
|
+
|
|
2072
|
+
The generator accepts `--apiDir`, `--hooksDir`, `--route`, and `--force`; directories default to `api`/`hooks`, the route defaults to the resource name, and `--force` overwrites existing files. It also respects nested resource paths like `admin/users`.
|
|
2073
|
+
|
|
2074
|
+
React Query must be available in the consuming project (`npm install @tanstack/react-query`), and the hooks expect an `ApiClient` instance that you pass as the first argument.
|
|
2075
|
+
|
|
2076
|
+
```bash
|
|
2077
|
+
npx sutils create payments --apiDir=services --hooksDir=app/hooks --route=/billing/payments --force
|
|
2078
|
+
```
|
|
2079
|
+
|
|
2080
|
+
### Types Command / Comando Types
|
|
2081
|
+
|
|
2082
|
+
**EN:** Generate TypeScript type definitions automatically from API responses. Perfect for quickly creating types without manual work.
|
|
2083
|
+
**ES:** Genera definiciones de tipos TypeScript automáticamente desde respuestas de API. Perfecto para crear tipos rápidamente sin trabajo manual.
|
|
2084
|
+
|
|
2085
|
+
```bash
|
|
2086
|
+
# Generate types from GET endpoint
|
|
2087
|
+
npx sutils types https://api.example.com/users
|
|
2088
|
+
|
|
2089
|
+
# Generate types with custom output and name
|
|
2090
|
+
npx sutils types https://api.example.com/users --output=user-types.ts --name=User
|
|
2091
|
+
|
|
2092
|
+
# Generate types from POST endpoint with custom headers
|
|
2093
|
+
npx sutils types https://api.example.com/users --method=POST --header=Authorization:Bearer\ token
|
|
2094
|
+
```
|
|
2095
|
+
|
|
2096
|
+
**Options / Opciones:**
|
|
2097
|
+
|
|
2098
|
+
- `--method=<METHOD>`: HTTP method (default: GET). Supports GET, POST, PUT, PATCH, DELETE.
|
|
2099
|
+
- `--output=<file>`: Output file path (default: types.ts).
|
|
2100
|
+
- `--name=<name>`: Type name (default: ApiResponse).
|
|
2101
|
+
- `--header=<key:value>`: Custom header (can be used multiple times).
|
|
2102
|
+
|
|
2103
|
+
**Example output / Ejemplo de salida:**
|
|
2104
|
+
|
|
2105
|
+
```ts
|
|
2106
|
+
// Auto-generated types from API response
|
|
2107
|
+
// Generated at 2024-12-20T10:30:00.000Z
|
|
2108
|
+
|
|
2109
|
+
export interface ApiResponse {
|
|
2110
|
+
id: string;
|
|
2111
|
+
name: string;
|
|
2112
|
+
email: string;
|
|
2113
|
+
createdAt: string;
|
|
2114
|
+
updatedAt: string;
|
|
2115
|
+
}
|
|
2116
|
+
```
|
|
2117
|
+
|
|
2118
|
+
## TypeScript Compatibility / Compatibilidad con TypeScript
|
|
2119
|
+
|
|
2120
|
+
**EN:** The package is tested and verified to work with multiple TypeScript versions.
|
|
2121
|
+
**ES:** El paquete está testeado y verificado para funcionar con múltiples versiones de TypeScript.
|
|
2122
|
+
|
|
2123
|
+
### Supported Versions / Versiones Soportadas
|
|
2124
|
+
|
|
2125
|
+
| Version | Status | Notes |
|
|
2126
|
+
| ------- | ------------------- | --------------------------------------------- |
|
|
2127
|
+
| 5.9.3+ | ✅ **Recommended** | Full ES2023 support, modern module resolution |
|
|
2128
|
+
| 6.0.0+ | ✅ **Future-proof** | Compatible with upcoming TypeScript versions |
|
|
2129
|
+
| 5.6.3 | ⚠️ Partial | Requires ES2022 target instead of ES2023 |
|
|
2130
|
+
| < 5.6 | ❌ Not supported | Missing ES2023 features and modern tooling |
|
|
2131
|
+
|
|
2132
|
+
### Recommended Configuration / Configuración Recomendada
|
|
2133
|
+
|
|
2134
|
+
**EN:** Use TypeScript 5.9.3 or later for optimal compatibility and zero deprecation warnings.
|
|
2135
|
+
**ES:** Usá TypeScript 5.9.3 o posterior para compatibilidad óptima y sin warnings de deprecación.
|
|
2136
|
+
|
|
2137
|
+
```bash
|
|
2138
|
+
npm install --save-dev typescript@^5.9.3
|
|
2139
|
+
```
|
|
2140
|
+
|
|
2141
|
+
### ES2023 Features Used / Características ES2023 Utilizadas
|
|
2142
|
+
|
|
2143
|
+
The package leverages these ES2023 features:
|
|
2144
|
+
|
|
2145
|
+
- `Array.prototype.findLast()` - Find last element matching predicate
|
|
2146
|
+
- `Array.prototype.findLastIndex()` - Find last index matching predicate
|
|
2147
|
+
- `Array.prototype.at()` - Access array elements with negative indices
|
|
2148
|
+
- `String.prototype.at()` - Access string characters with negative indices
|
|
2149
|
+
- `Object.hasOwn()` - Check object property ownership
|
|
2150
|
+
- Experimental Decorators
|
|
2151
|
+
|
|
2152
|
+
### Migration from Older Versions / Migración desde Versiones Antiguas
|
|
2153
|
+
|
|
2154
|
+
**From TypeScript 5.6 to 5.9:**
|
|
2155
|
+
|
|
2156
|
+
```bash
|
|
2157
|
+
# 1. Update TypeScript
|
|
2158
|
+
npm install --save-dev typescript@^5.9.3
|
|
2159
|
+
|
|
2160
|
+
# 2. Update tsconfig.json
|
|
2161
|
+
# Change target from ES2022 to ES2023
|
|
2162
|
+
# Change moduleResolution from node to bundler
|
|
2163
|
+
|
|
2164
|
+
# 3. No code changes needed
|
|
2165
|
+
npm run build
|
|
2166
|
+
```
|
|
2167
|
+
|
|
2168
|
+
**From TypeScript 5.9 to 6.0+:**
|
|
2169
|
+
|
|
2170
|
+
```bash
|
|
2171
|
+
# 1. Update TypeScript
|
|
2172
|
+
npm install --save-dev typescript@^6.0.0
|
|
2173
|
+
|
|
2174
|
+
# 2. No tsconfig changes needed (already future-proof)
|
|
2175
|
+
|
|
2176
|
+
# 3. No code changes needed
|
|
2177
|
+
npm run build
|
|
2178
|
+
```
|
|
2179
|
+
|
|
2180
|
+
### Testing TypeScript Compatibility / Testeando Compatibilidad
|
|
2181
|
+
|
|
2182
|
+
**EN:** Run the compatibility test script to verify your TypeScript version:
|
|
2183
|
+
**ES:** Ejecutá el script de compatibilidad para verificar tu versión de TypeScript:
|
|
2184
|
+
|
|
2185
|
+
```bash
|
|
2186
|
+
# Run compatibility tests
|
|
2187
|
+
node .kiro/test-ts-compat.js
|
|
2188
|
+
|
|
2189
|
+
# Or run full test suite
|
|
2190
|
+
npm run test
|
|
2191
|
+
```
|
|
2192
|
+
|
|
2193
|
+
### Detailed Compatibility Guide / Guía Detallada de Compatibilidad
|
|
2194
|
+
|
|
2195
|
+
**EN:** For detailed information about TypeScript compatibility, see:
|
|
2196
|
+
**ES:** Para información detallada sobre compatibilidad con TypeScript, ver:
|
|
2197
|
+
|
|
2198
|
+
- `.kiro/TYPESCRIPT_COMPATIBILITY.md` - Full compatibility matrix and migration paths
|
|
2199
|
+
- `.kiro/MULTI_VERSION_TEST_REPORT.md` - Detailed test results and performance metrics
|
|
2200
|
+
- `.kiro/TYPESCRIPT_TESTING_SUMMARY.md` - Summary of testing methodology
|
|
2201
|
+
|
|
2202
|
+
## Migration from @sebamar88/utils / Migración desde @sebamar88/utils
|
|
2203
|
+
|
|
2204
|
+
**EN:** If you were using `@sebamar88/utils`, simply update your package name to `bytekit`. No code changes are required—all APIs remain the same.
|
|
2205
|
+
|
|
2206
|
+
**ES:** Si estabas usando `@sebamar88/utils`, simplemente actualiza el nombre del paquete a `bytekit`. No se requieren cambios de código—todas las APIs permanecen igual.
|
|
2207
|
+
|
|
2208
|
+
### Before / Antes
|
|
2209
|
+
|
|
2210
|
+
```bash
|
|
2211
|
+
npm install @sebamar88/utils
|
|
2212
|
+
```
|
|
2213
|
+
|
|
2214
|
+
```typescript
|
|
2215
|
+
import { ApiClient, DateUtils } from "@sebamar88/utils";
|
|
2216
|
+
```
|
|
2217
|
+
|
|
2218
|
+
### After / Después
|
|
2219
|
+
|
|
2220
|
+
```bash
|
|
2221
|
+
npm install bytekit
|
|
2222
|
+
```
|
|
2223
|
+
|
|
2224
|
+
```typescript
|
|
2225
|
+
import { ApiClient, DateUtils } from "bytekit";
|
|
2226
|
+
```
|
|
2227
|
+
|
|
2228
|
+
### Version History / Historial de Versiones
|
|
2229
|
+
|
|
2230
|
+
- **v0.1.10+** - `bytekit` (current)
|
|
2231
|
+
- **v0.1.9 and earlier** - `@sebamar88/utils` (deprecated)
|
|
2232
|
+
|
|
2233
|
+
**EN:** The package was renamed from `@sebamar88/utils` to `bytekit` starting with v0.1.10. All functionality remains the same.
|
|
2234
|
+
|
|
2235
|
+
**ES:** El paquete fue renombrado de `@sebamar88/utils` a `bytekit` a partir de v0.1.10. Toda la funcionalidad permanece igual.
|
|
2236
|
+
|
|
2237
|
+
## License / Licencia
|
|
2238
|
+
|
|
2239
|
+
MIT © 2024 Sebastián Martinez
|
|
2240
|
+
|
|
2241
|
+
|