hanzio 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +510 -0
- package/dist/api-wrapper/index.d.ts +84 -0
- package/dist/api-wrapper/index.js +250 -0
- package/dist/array/index.d.ts +22 -0
- package/dist/cache/index.d.ts +19 -0
- package/dist/date/index.d.ts +11 -0
- package/dist/gzip/index.d.ts +2 -0
- package/dist/gzip/index.js +16 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1565 -0
- package/dist/jwt/index.d.ts +3 -0
- package/dist/jwt/index.js +19 -0
- package/dist/math/index.d.ts +2 -0
- package/dist/math/linearRegressionTrend.d.ts +5 -0
- package/dist/math/metricsSeriesColors.d.ts +42 -0
- package/dist/p-queue/index.d.ts +95 -0
- package/dist/p-queue/index.js +439 -0
- package/dist/promise/index.d.ts +21 -0
- package/dist/sitemap/index.d.ts +14 -0
- package/dist/sitemap/index.js +552 -0
- package/dist/state/index.d.ts +101 -0
- package/dist/string/index.d.ts +8 -0
- package/dist/typedswitch/index.d.ts +124 -0
- package/dist/url/index.d.ts +1 -0
- package/dist/zod/index.d.ts +8 -0
- package/dist/zod/index.js +210 -0
- package/dist/zod/jsonToZodSchemaMapper.d.ts +6 -0
- package/dist/zod/zodMinMaxFilter.d.ts +14 -0
- package/dist/zod/zodToTypeString.d.ts +9 -0
- package/package.json +102 -0
- package/src/api-wrapper/index.ts +492 -0
- package/src/array/index.ts +187 -0
- package/src/cache/index.ts +154 -0
- package/src/date/index.ts +54 -0
- package/src/gzip/index.ts +29 -0
- package/src/index.ts +12 -0
- package/src/jwt/index.ts +25 -0
- package/src/math/index.ts +11 -0
- package/src/math/linearRegressionTrend.ts +36 -0
- package/src/math/metricsSeriesColors.ts +208 -0
- package/src/p-queue/index.ts +690 -0
- package/src/promise/index.ts +73 -0
- package/src/sitemap/index.ts +195 -0
- package/src/state/index.ts +331 -0
- package/src/string/index.ts +86 -0
- package/src/typedswitch/index.ts +309 -0
- package/src/url/index.ts +5 -0
- package/src/zod/index.ts +29 -0
- package/src/zod/jsonToZodSchemaMapper.ts +44 -0
- package/src/zod/zodMinMaxFilter.ts +28 -0
- package/src/zod/zodToTypeString.ts +222 -0
package/README.md
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# hanzio
|
|
2
|
+
|
|
3
|
+
A small TypeScript-first utility library for common application code: arrays,
|
|
4
|
+
strings, promises, caching, state machines, dates, URLs, Zod helpers, API clients,
|
|
5
|
+
JWTs, gzip, sitemap crawling, and queues.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add hanzio
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package is ESM and Bun-friendly. Bun consumers resolve the `bun` export to
|
|
14
|
+
the source TypeScript files, while Node/bundler consumers resolve built files
|
|
15
|
+
from `dist`.
|
|
16
|
+
|
|
17
|
+
## Imports
|
|
18
|
+
|
|
19
|
+
Most lightweight utilities are available from the root export:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { groupBy, typedSwitch, tryCatch } from 'hanzio'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Heavier or dependency-specific modules are also available as subpath exports:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createZId } from 'hanzio/zod'
|
|
29
|
+
import { jwtSign } from 'hanzio/jwt'
|
|
30
|
+
import { compressString } from 'hanzio/gzip'
|
|
31
|
+
import { getDomainSitemap } from 'hanzio/sitemap'
|
|
32
|
+
import PQueue from 'hanzio/p-queue'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The package currently publishes `src/**/*.ts` intentionally so Bun can consume
|
|
36
|
+
the source entrypoints directly. Tests are excluded from the published package.
|
|
37
|
+
|
|
38
|
+
## Utilities
|
|
39
|
+
|
|
40
|
+
### Array
|
|
41
|
+
|
|
42
|
+
`chunkArray` splits a readonly array into fixed-size chunks.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { chunkArray } from 'hanzio'
|
|
46
|
+
|
|
47
|
+
const pages = chunkArray([1, 2, 3, 4, 5], 2)
|
|
48
|
+
// [[1, 2], [3, 4], [5]]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`findArrayDifferenceByKey` compares two arrays by a shared key and returns
|
|
52
|
+
`new`, `upsert`, and `delete` groups.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { findArrayDifferenceByKey } from 'hanzio'
|
|
56
|
+
|
|
57
|
+
const diff = findArrayDifferenceByKey(
|
|
58
|
+
[{ id: 1, name: 'old' }],
|
|
59
|
+
[{ id: 1, name: 'new' }, { id: 2, name: 'added' }],
|
|
60
|
+
'id'
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`getUniqueValues` keeps the first unique serialized value.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { getUniqueValues } from 'hanzio'
|
|
68
|
+
|
|
69
|
+
const values = getUniqueValues([{ id: 1 }, { id: 1 }, { id: 2 }])
|
|
70
|
+
// [{ id: 1 }, { id: 2 }]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`compact` removes `null` and `undefined` while narrowing the returned type.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { compact } from 'hanzio'
|
|
77
|
+
|
|
78
|
+
const names = compact(['Ada', null, 'Grace', undefined])
|
|
79
|
+
// string[]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`getUniqueValuesByKey` keeps the first item for each key value.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { getUniqueValuesByKey } from 'hanzio'
|
|
86
|
+
|
|
87
|
+
const users = getUniqueValuesByKey(
|
|
88
|
+
[{ id: 'a', role: 'admin' }, { id: 'a', role: 'user' }],
|
|
89
|
+
'id'
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`groupBy` groups items by a property or callback and preserves literal key unions
|
|
94
|
+
when possible.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { groupBy } from 'hanzio'
|
|
98
|
+
|
|
99
|
+
const byType = groupBy(
|
|
100
|
+
[
|
|
101
|
+
{ type: 'fruit', name: 'apple' },
|
|
102
|
+
{ type: 'veg', name: 'carrot' }
|
|
103
|
+
] as const,
|
|
104
|
+
'type'
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`keyBy` indexes items by a property or callback.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { keyBy } from 'hanzio'
|
|
112
|
+
|
|
113
|
+
const byId = keyBy([{ id: 'user_1', name: 'Ada' }], 'id')
|
|
114
|
+
// { user_1: { id: 'user_1', name: 'Ada' } }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`partition` splits an array into matched and unmatched items.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { partition } from 'hanzio'
|
|
121
|
+
|
|
122
|
+
const [even, odd] = partition([1, 2, 3, 4], (value) => value % 2 === 0)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`pickItemsInArray` filters by a key and narrows discriminated unions for const
|
|
126
|
+
value lists.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { pickItemsInArray } from 'hanzio'
|
|
130
|
+
|
|
131
|
+
type Item = { type: 'user'; name: string } | { type: 'team'; members: number }
|
|
132
|
+
const users = pickItemsInArray([] as Item[], 'type', ['user'] as const)
|
|
133
|
+
// Array<{ type: 'user'; name: string }>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### String
|
|
137
|
+
|
|
138
|
+
`extractNumber` parses the first number-like value from a string.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { extractNumber } from 'hanzio'
|
|
142
|
+
|
|
143
|
+
const amount = extractNumber('Total: 123.45 kr')
|
|
144
|
+
// 123.45
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`generateId` creates a stable short ID from JSON-like input.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { generateId } from 'hanzio'
|
|
151
|
+
|
|
152
|
+
const id = generateId({ page: 1, filters: ['active'] })
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`slugify` normalizes text for URL slugs.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { slugify } from 'hanzio'
|
|
159
|
+
|
|
160
|
+
const slug = slugify('Hello, København!')
|
|
161
|
+
// "hello-kobenhavn"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Promise
|
|
165
|
+
|
|
166
|
+
`tryCatch` converts a promise into a `Result<T, E>` object.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { tryCatch } from 'hanzio'
|
|
170
|
+
|
|
171
|
+
const result = await tryCatch(fetch('/api/users'))
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`isSuccess`, `isFailure`, and `unwrapResult` help narrow and consume
|
|
175
|
+
`Result<T, E>`.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { isSuccess, unwrapResult } from 'hanzio'
|
|
179
|
+
|
|
180
|
+
if (isSuccess(result)) {
|
|
181
|
+
result.data
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const response = unwrapResult(result)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`backgroundPromise` runs async work and reports success or failure through hooks.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { backgroundPromise } from 'hanzio'
|
|
191
|
+
|
|
192
|
+
backgroundPromise(() => syncAnalytics(), {
|
|
193
|
+
onError: (error) => console.error(error)
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`backgroundPromiseSync` schedules background work in a microtask by default.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { backgroundPromiseSync } from 'hanzio'
|
|
201
|
+
|
|
202
|
+
backgroundPromiseSync(() => refreshCache())
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`promiseTimeout` resolves after a delay.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { promiseTimeout } from 'hanzio'
|
|
209
|
+
|
|
210
|
+
await promiseTimeout(250)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Cache
|
|
214
|
+
|
|
215
|
+
`cacheFunction` wraps sync or async functions with TTL caching and request
|
|
216
|
+
deduplication.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { cacheFunction } from 'hanzio'
|
|
220
|
+
|
|
221
|
+
const getUser = cacheFunction({
|
|
222
|
+
name: 'getUser',
|
|
223
|
+
fn: async (id: string) => ({ id, name: 'Ada' }),
|
|
224
|
+
cacheTimeMs: 60_000
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const user = await getUser('user_1')
|
|
228
|
+
getUser.clearCache()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### State Machine
|
|
232
|
+
|
|
233
|
+
`SimpleStateMachine` models typed states, actions, validation, and action
|
|
234
|
+
payloads.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { SimpleStateMachine } from 'hanzio'
|
|
238
|
+
|
|
239
|
+
type State = 'draft' | 'published'
|
|
240
|
+
type Action = 'publish'
|
|
241
|
+
|
|
242
|
+
const machine = new SimpleStateMachine<
|
|
243
|
+
State,
|
|
244
|
+
Action,
|
|
245
|
+
{ status: State },
|
|
246
|
+
{ publish: { notify: boolean } }
|
|
247
|
+
>(
|
|
248
|
+
{
|
|
249
|
+
stateActionMap: { draft: { publish: 'published' }, published: {} },
|
|
250
|
+
stateTransitions: { draft: ['published'], published: [] }
|
|
251
|
+
},
|
|
252
|
+
{ status: 'draft' },
|
|
253
|
+
'status'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
const result = await machine.executeAction('publish', { notify: true })
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### typedSwitch
|
|
260
|
+
|
|
261
|
+
`typedSwitch` is an exhaustive switch helper for string unions and discriminated
|
|
262
|
+
unions.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { typedSwitch } from 'hanzio'
|
|
266
|
+
|
|
267
|
+
type Event =
|
|
268
|
+
| { type: 'click'; x: number; y: number }
|
|
269
|
+
| { type: 'scroll'; offset: number }
|
|
270
|
+
|
|
271
|
+
const label = typedSwitch({ type: 'click', x: 10, y: 20 } as Event, 'type', {
|
|
272
|
+
click: (event) => `Clicked ${event.x},${event.y}`,
|
|
273
|
+
scroll: (event) => `Scrolled ${event.offset}`
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Date
|
|
278
|
+
|
|
279
|
+
`getPreviousReportPeriod` returns the previous monthly, quarterly, or yearly
|
|
280
|
+
date range.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { getPreviousReportPeriod } from 'hanzio'
|
|
284
|
+
|
|
285
|
+
const previousMonth = getPreviousReportPeriod('MONTHLY', new Date('2026-04-15'))
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`formatPeriodLabel` formats a date range for display.
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { formatPeriodLabel } from 'hanzio'
|
|
292
|
+
|
|
293
|
+
const label = formatPeriodLabel('2026-01-01', '2026-03-31')
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### URL
|
|
297
|
+
|
|
298
|
+
`getDomainFaviconUrl` builds a Google favicon URL for a domain.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { getDomainFaviconUrl } from 'hanzio'
|
|
302
|
+
|
|
303
|
+
const favicon = getDomainFaviconUrl('example.com', 64)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Math
|
|
307
|
+
|
|
308
|
+
`linearRegressionTrend` returns fitted trend values for a numeric series.
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { linearRegressionTrend } from 'hanzio'
|
|
312
|
+
|
|
313
|
+
const trend = linearRegressionTrend([1, 3, 5])
|
|
314
|
+
// [1, 3, 5]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`hashStringToColorIndex`, `seriesColorKey`, `resolveSeriesColorMap`,
|
|
318
|
+
`createResolveColorsByKeys`, `withAlpha`, and `getHeatmapCellStyle` help assign
|
|
319
|
+
stable chart colors and heatmap styles.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { createResolveColorsByKeys, withAlpha } from 'hanzio'
|
|
323
|
+
|
|
324
|
+
const colors = createResolveColorsByKeys(
|
|
325
|
+
['revenue', 'profit'],
|
|
326
|
+
(index) => ['#2563eb', '#16a34a'][index] ?? '#64748b',
|
|
327
|
+
(index) => ['#1d4ed8', '#15803d'][index] ?? '#475569'
|
|
328
|
+
)
|
|
329
|
+
const faded = withAlpha('#2563eb', 0.4)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Zod
|
|
333
|
+
|
|
334
|
+
`createZId` and `GenericId` create branded string ID schemas.
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import { createZId, type GenericId } from 'hanzio/zod'
|
|
338
|
+
|
|
339
|
+
const UserId = createZId('User')
|
|
340
|
+
type UserId = GenericId<'User'>
|
|
341
|
+
|
|
342
|
+
const userId: UserId = UserId.parse('user_123')
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`zSingleOrArray` accepts either one schema value or an array of schema values.
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { zSingleOrArray } from 'hanzio/zod'
|
|
349
|
+
import { z } from 'zod'
|
|
350
|
+
|
|
351
|
+
const Tags = zSingleOrArray(z.string())
|
|
352
|
+
Tags.parse('news')
|
|
353
|
+
Tags.parse(['news', 'product'])
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`normalizeSingleOrArray` normalizes nullable single-or-array inputs to an array.
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
import { normalizeSingleOrArray } from 'hanzio/zod'
|
|
360
|
+
|
|
361
|
+
const values = normalizeSingleOrArray('tag')
|
|
362
|
+
// ['tag']
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
`zodMinMaxFilter` and `toGteLteFilter` model common min/max filters.
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { toGteLteFilter, zodMinMaxFilter } from 'hanzio/zod'
|
|
369
|
+
|
|
370
|
+
const filter = zodMinMaxFilter.parse({ min: 10, max: 20 })
|
|
371
|
+
const query = toGteLteFilter(filter)
|
|
372
|
+
// { gte: 10, lte: 20 }
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`jsonToZodSchemaMapper` maps raw JSON into a Zod schema shape with optional field
|
|
376
|
+
mapping.
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import { jsonToZodSchemaMapper } from 'hanzio/zod'
|
|
380
|
+
import { z } from 'zod'
|
|
381
|
+
|
|
382
|
+
const User = z.object({ id: z.string(), name: z.string() })
|
|
383
|
+
const user = jsonToZodSchemaMapper({ user_id: '1', name: 'Ada' }, User, {
|
|
384
|
+
id: (json) => json.user_id
|
|
385
|
+
})
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`zodToTypeString` and `zodToExample` render simple documentation artifacts from
|
|
389
|
+
Zod schemas.
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import { zodToExample, zodToTypeString } from 'hanzio/zod'
|
|
393
|
+
import { z } from 'zod'
|
|
394
|
+
|
|
395
|
+
const schema = z.object({ id: z.string() })
|
|
396
|
+
const typeString = zodToTypeString(schema)
|
|
397
|
+
const example = zodToExample(schema)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### API Wrapper
|
|
401
|
+
|
|
402
|
+
`createApiClient` and `createApiWrapper` create a Zod-backed API client with
|
|
403
|
+
typed request inputs and response data.
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import { createApiClient } from 'hanzio/api-wrapper'
|
|
407
|
+
import { z } from 'zod'
|
|
408
|
+
|
|
409
|
+
const api = createApiClient({
|
|
410
|
+
baseApiUrls: { default: 'https://api.example.com' },
|
|
411
|
+
endpoints: {
|
|
412
|
+
getUser: {
|
|
413
|
+
method: 'GET',
|
|
414
|
+
path: '/users/:id',
|
|
415
|
+
reqParamsSchema: z.object({ id: z.number() }),
|
|
416
|
+
resSchema: z.object({ id: z.number(), name: z.string() })
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
const result = await api.request('getUser', { reqParams: { id: 1 } })
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`ResponseValidationError` and `HttpResponseError` are thrown for invalid
|
|
425
|
+
responses and non-OK HTTP statuses.
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import { HttpResponseError, ResponseValidationError } from 'hanzio/api-wrapper'
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
await api.request('getUser', { reqParams: { id: 1 } })
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (error instanceof ResponseValidationError) {
|
|
434
|
+
console.error(error.validationIssues)
|
|
435
|
+
}
|
|
436
|
+
if (error instanceof HttpResponseError) console.error(error.status)
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### JWT
|
|
441
|
+
|
|
442
|
+
`jwtSign` signs a payload with HS256, and `jwtVerify` verifies a token or returns
|
|
443
|
+
`null`.
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
import { jwtSign, jwtVerify } from 'hanzio/jwt'
|
|
447
|
+
|
|
448
|
+
const token = await jwtSign({ sub: 'user_1' }, 'secret')
|
|
449
|
+
const payload = await jwtVerify<{ sub: string }>(token, 'secret')
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Gzip
|
|
453
|
+
|
|
454
|
+
`compressString` and `decompressString` gzip roundtrip strings as base64.
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
import { compressString, decompressString } from 'hanzio/gzip'
|
|
458
|
+
|
|
459
|
+
const compressed = compressString('hello')
|
|
460
|
+
const text = decompressString(compressed)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Sitemap
|
|
464
|
+
|
|
465
|
+
`getDomainSitemap` discovers and crawls sitemap URLs for a domain.
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { getDomainSitemap } from 'hanzio/sitemap'
|
|
469
|
+
|
|
470
|
+
const urls = await getDomainSitemap('example.com', {
|
|
471
|
+
concurrency: 5,
|
|
472
|
+
maxDepth: 3
|
|
473
|
+
})
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### PQueue
|
|
477
|
+
|
|
478
|
+
`PQueue` runs promise-returning tasks with concurrency, priority, timeout, abort,
|
|
479
|
+
and rate-limit controls.
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
import PQueue from 'hanzio/p-queue'
|
|
483
|
+
|
|
484
|
+
const queue = new PQueue({ concurrency: 2 })
|
|
485
|
+
|
|
486
|
+
const result = await queue.add(async () => 'done')
|
|
487
|
+
await queue.onIdle()
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
`PriorityQueue` and `TimeoutError` are exported for custom queue behavior and
|
|
491
|
+
timeout handling.
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
import PQueue, { TimeoutError } from 'hanzio/p-queue'
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
await new PQueue({ timeout: 100 }).add(() => new Promise(() => {}))
|
|
498
|
+
} catch (error) {
|
|
499
|
+
if (error instanceof TimeoutError) console.error('Task timed out')
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Development
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
bun run typecheck
|
|
507
|
+
bun test
|
|
508
|
+
bun run lint
|
|
509
|
+
bun run build
|
|
510
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type z, ZodError } from 'zod';
|
|
2
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
3
|
+
export type RequestBodyFormat = 'json' | 'form-data';
|
|
4
|
+
export type BaseApiUrl = string | (() => string);
|
|
5
|
+
export type PathParams = Record<string, string | number>;
|
|
6
|
+
export type QueryParams = Record<string, string | number | boolean | undefined>;
|
|
7
|
+
export declare class ResponseValidationError extends Error {
|
|
8
|
+
readonly zodError: ZodError;
|
|
9
|
+
readonly rawResponse: unknown;
|
|
10
|
+
readonly validationIssues: string;
|
|
11
|
+
constructor(zodError: ZodError, rawResponse: unknown);
|
|
12
|
+
}
|
|
13
|
+
export declare class HttpResponseError extends Error {
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly body: string;
|
|
16
|
+
constructor(status: number, body: string);
|
|
17
|
+
}
|
|
18
|
+
export interface ApiEndpoint<TReqBody extends z.ZodType | undefined = z.ZodType | undefined, TReqParams extends z.ZodType | undefined = z.ZodType | undefined, TReqQuery extends z.ZodType | undefined = z.ZodType | undefined, TReqHeaders extends z.ZodType | undefined = z.ZodType | undefined, TResponse extends z.ZodType = z.ZodType> {
|
|
19
|
+
method: HttpMethod;
|
|
20
|
+
path: string | ((baseUrl: string) => string);
|
|
21
|
+
reqBodySchema?: TReqBody;
|
|
22
|
+
reqParamsSchema?: TReqParams;
|
|
23
|
+
reqQuerySchema?: TReqQuery;
|
|
24
|
+
reqHeadersSchema?: TReqHeaders;
|
|
25
|
+
resSchema: TResponse;
|
|
26
|
+
baseApiUrl?: string;
|
|
27
|
+
reqBodyFormat?: RequestBodyFormat;
|
|
28
|
+
defaultHeaders?: Record<string, string>;
|
|
29
|
+
resFormatter?: (data: unknown, headers: Record<string, string>) => z.infer<TResponse>;
|
|
30
|
+
reqDefaultQueryParams?: QueryParams;
|
|
31
|
+
doNotEncodeQueryParams?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface ApiWrapperResponse<T> {
|
|
34
|
+
data: T;
|
|
35
|
+
requestSizeMb: number;
|
|
36
|
+
responseSizeMb: number;
|
|
37
|
+
requestSizeTotalMb: number;
|
|
38
|
+
responseTimeMs: number;
|
|
39
|
+
httpStatus: number;
|
|
40
|
+
retryCount: number;
|
|
41
|
+
}
|
|
42
|
+
export type RetryContext = {
|
|
43
|
+
error?: unknown;
|
|
44
|
+
response?: Response;
|
|
45
|
+
retryCount: number;
|
|
46
|
+
maxRetries: number;
|
|
47
|
+
};
|
|
48
|
+
export interface ApiClientConfig<T extends Record<string, ApiEndpoint>> {
|
|
49
|
+
name?: string;
|
|
50
|
+
baseApiUrls: Record<string, BaseApiUrl>;
|
|
51
|
+
defaultBaseApiUrl?: string;
|
|
52
|
+
endpoints: T;
|
|
53
|
+
defaultHeaders?: Record<string, string>;
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
retries?: number;
|
|
56
|
+
retryDelayMs?: number;
|
|
57
|
+
shouldRetry?: (context: RetryContext) => boolean;
|
|
58
|
+
logger?: Pick<Console, 'debug' | 'error'>;
|
|
59
|
+
fetch?: typeof fetch;
|
|
60
|
+
onRequestStart?: (context: {
|
|
61
|
+
endpoint: string;
|
|
62
|
+
url: string;
|
|
63
|
+
}) => void;
|
|
64
|
+
onRequestEnd?: (context: {
|
|
65
|
+
endpoint: string;
|
|
66
|
+
url: string;
|
|
67
|
+
status: number;
|
|
68
|
+
retryCount: number;
|
|
69
|
+
responseTimeMs: number;
|
|
70
|
+
}) => void;
|
|
71
|
+
}
|
|
72
|
+
type RequestInput<TEndpoint extends ApiEndpoint> = {
|
|
73
|
+
reqBody?: InferOptionalSchema<TEndpoint['reqBodySchema']>;
|
|
74
|
+
reqParams?: InferOptionalSchema<TEndpoint['reqParamsSchema']>;
|
|
75
|
+
reqQuery?: InferOptionalSchema<TEndpoint['reqQuerySchema']>;
|
|
76
|
+
reqHeaders?: InferOptionalSchema<TEndpoint['reqHeadersSchema']>;
|
|
77
|
+
url?: string;
|
|
78
|
+
};
|
|
79
|
+
type InferOptionalSchema<TSchema> = TSchema extends z.ZodType ? z.infer<TSchema> : undefined;
|
|
80
|
+
export declare function createApiClient<T extends Record<string, ApiEndpoint>>(apiConfig: ApiClientConfig<T>): {
|
|
81
|
+
request: <K extends keyof T>(endpointKey: K, input?: RequestInput<T[K]>) => Promise<ApiWrapperResponse<z.TypeOf<T[K]["resSchema"]>>>;
|
|
82
|
+
};
|
|
83
|
+
export declare const createApiWrapper: typeof createApiClient;
|
|
84
|
+
export {};
|