hanzio 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +24 -10
- package/dist/api-wrapper/endpoint.d.ts +8 -5
- package/dist/api-wrapper/transport.d.ts +1 -0
- package/dist/api-wrapper/types.d.ts +7 -0
- package/dist/{index-j1ybktbr.js → index-zbj9s3s3.js} +6 -4
- package/dist/src/api-wrapper/index.js +1 -1
- package/dist/src/index.js +8 -2
- package/dist/src/secrets/index.js +1 -1
- package/dist/typedswitch/index.d.ts +55 -34
- package/package.json +11 -3
- package/src/api-wrapper/README.md +741 -0
- package/src/api-wrapper/SKILL.md +256 -0
- package/src/api-wrapper/create-client.ts +9 -3
- package/src/api-wrapper/endpoint.ts +19 -5
- package/src/api-wrapper/transport.ts +6 -2
- package/src/api-wrapper/types.ts +13 -0
- package/src/crypto/README.md +144 -0
- package/src/format/README.md +120 -0
- package/src/i18n/README.md +65 -0
- package/src/jwt/README.md +70 -0
- package/src/p-queue/README.md +237 -0
- package/src/secrets/README.md +413 -0
- package/src/sitemap/README.md +61 -0
- package/src/storage/README.md +71 -0
- package/src/typedswitch/README.md +335 -0
- package/src/typedswitch/index.ts +97 -83
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Typed `{name}` placeholders, interpolation and a small typed translator.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# hanzio/i18n
|
|
6
|
+
|
|
7
|
+
A typed helper for `{name}` message templates, not an i18n framework: no
|
|
8
|
+
plurals, ICU, locale negotiation or escaping.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createTranslator, extractPlaceholders, interpolate } from 'hanzio/i18n'
|
|
12
|
+
|
|
13
|
+
interpolate('Hi {name}, you have {count} messages', { name: 'Ada', count: 3 })
|
|
14
|
+
// 'Hi Ada, you have 3 messages'
|
|
15
|
+
|
|
16
|
+
interpolate('Hi {name}') // type error: params required
|
|
17
|
+
interpolate('Hi {name}', { nam: 'Ada' }) // type error: misspelled
|
|
18
|
+
|
|
19
|
+
extractPlaceholders('{name} has {count} items, {name}') // ['name', 'count']
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Placeholders
|
|
23
|
+
|
|
24
|
+
A placeholder is `{` + word characters (`[A-Za-z0-9_]`) + `}`. Anything else is
|
|
25
|
+
plain text: `{ name }`, `{}`, `{count, plural, ...}`. The types and the runtime
|
|
26
|
+
use the same rule.
|
|
27
|
+
|
|
28
|
+
- A placeholder without a param (or with `null`/`undefined`) stays as `{name}`.
|
|
29
|
+
- Only own properties of `params` are used, so `{toString}` is never filled from
|
|
30
|
+
`Object.prototype`.
|
|
31
|
+
- Values are `string | number`, converted with `String()`.
|
|
32
|
+
|
|
33
|
+
Types:
|
|
34
|
+
|
|
35
|
+
| Type | `'Hi {name}, {count}'` |
|
|
36
|
+
| ------------------------ | -------------------------------------------- |
|
|
37
|
+
| `PlaceholderNames<S>` | `'name' \| 'count'` |
|
|
38
|
+
| `PlaceholderParams<S>` | `{ name: string \| number; count: ... }` |
|
|
39
|
+
| `PlaceholderArgs<S>` | `[params: PlaceholderParams<S>]`, `[]` if none |
|
|
40
|
+
|
|
41
|
+
For a non-literal `string` template, names are `string` and params are optional.
|
|
42
|
+
|
|
43
|
+
## Translator
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const messages = {
|
|
47
|
+
common: { save: 'Save' },
|
|
48
|
+
greeting: 'Hi {name}'
|
|
49
|
+
} as const
|
|
50
|
+
|
|
51
|
+
const t = createTranslator(messages)
|
|
52
|
+
|
|
53
|
+
t('common.save') // 'Save'
|
|
54
|
+
t('greeting', { name: 'Ada' }) // 'Hi Ada'
|
|
55
|
+
t('greeting') // type error: params required
|
|
56
|
+
t('common.save', { name: 'Ada' }) // type error: no placeholders
|
|
57
|
+
t('common.missing') // type error: unknown key
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Keys are the dot paths of the string leaves (`MessageKeys<T>`). Pass the
|
|
61
|
+
messages inline or `as const` so the templates stay literal; with plain
|
|
62
|
+
`Record<string, string>` messages (for example JSON imports) keys are `string`
|
|
63
|
+
and params are optional. A key that is missing at runtime returns the key.
|
|
64
|
+
|
|
65
|
+
`flattenMessages(messages)` gives the flat map: `{ 'common.save': 'Save', greeting: 'Hi {name}' }`.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: JWT sign/verify/decode using built-in Web Crypto HMAC (HS256/HS384/HS512).
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# hanzio/jwt
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { jwtDecode, jwtSign, jwtVerify, jwtVerifyResult } from 'hanzio/jwt'
|
|
9
|
+
|
|
10
|
+
const token = await jwtSign({ sub: 'user-1' }, secret, {
|
|
11
|
+
expiresIn: 60 * 60, // seconds
|
|
12
|
+
issuer: 'api',
|
|
13
|
+
audience: 'web'
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// Payload or null
|
|
17
|
+
const payload = await jwtVerify<{ sub: string }>(token, secret)
|
|
18
|
+
|
|
19
|
+
// Result with a reason
|
|
20
|
+
const result = await jwtVerifyResult(token, secret, {
|
|
21
|
+
issuer: 'api',
|
|
22
|
+
audience: 'web',
|
|
23
|
+
leewaySec: 30
|
|
24
|
+
})
|
|
25
|
+
if (!result.ok) console.log(result.error.code) // 'expired', 'invalid_signature', …
|
|
26
|
+
|
|
27
|
+
// Unverified: for reading claims client-side, never for auth
|
|
28
|
+
const decoded = jwtDecode(token) // { header, payload } | null
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Signing
|
|
32
|
+
|
|
33
|
+
`jwtSign(payload, secret, options?)`
|
|
34
|
+
|
|
35
|
+
| Option | Claim | Notes |
|
|
36
|
+
| ------------ | ----- | ----------------------------------- |
|
|
37
|
+
| `expiresIn` | `exp` | seconds from now |
|
|
38
|
+
| `notBefore` | `nbf` | seconds from now |
|
|
39
|
+
| `issuer` | `iss` | |
|
|
40
|
+
| `audience` | `aud` | `string \| string[]` |
|
|
41
|
+
| `algorithm` | — | `'HS256'` (default), `'HS384'`, `'HS512'` |
|
|
42
|
+
|
|
43
|
+
- `iat` is always set to the current time.
|
|
44
|
+
- Options win: a claim set through an option replaces the same claim in `payload`.
|
|
45
|
+
Payload claims are kept when the option is omitted.
|
|
46
|
+
- Non-finite `expiresIn`/`notBefore` throw a `TypeError`.
|
|
47
|
+
- The header is `{ alg, typ: 'JWT' }`.
|
|
48
|
+
|
|
49
|
+
## Verifying
|
|
50
|
+
|
|
51
|
+
`jwtVerifyResult(token, secret, options?)` returns
|
|
52
|
+
`Result<Payload, JwtVerifyError>` (see `hanzio` promise helpers). `error.code` is one of
|
|
53
|
+
`'malformed' | 'unsupported_alg' | 'invalid_signature' | 'expired' | 'not_yet_valid' | 'invalid_issuer' | 'invalid_audience'`.
|
|
54
|
+
|
|
55
|
+
| Option | Default | Notes |
|
|
56
|
+
| ------------ | ----------------------- | ------------------------------------------ |
|
|
57
|
+
| `leewaySec` | `0` | clock skew tolerance for `exp` / `nbf` |
|
|
58
|
+
| `issuer` | — | required `iss` |
|
|
59
|
+
| `audience` | — | must equal `aud` or be one of its entries |
|
|
60
|
+
| `algorithms` | `['HS256','HS384','HS512']` | the header `alg` must be in this list |
|
|
61
|
+
|
|
62
|
+
- The signature is checked before any claim, so unsigned claims never decide the error code.
|
|
63
|
+
- `alg: 'none'` and anything outside `algorithms` is rejected as `unsupported_alg`.
|
|
64
|
+
- Configuration errors (no Web Crypto, empty secret) throw. `jwtVerify` swallows them
|
|
65
|
+
and returns `null`, like any other failure.
|
|
66
|
+
|
|
67
|
+
Imported `CryptoKey`s are cached per secret, algorithm and usage (up to 100 entries).
|
|
68
|
+
|
|
69
|
+
The base64url and UTF-8 helpers this module uses are public in
|
|
70
|
+
[`hanzio/crypto`](../crypto/README.md) (`encodeBase64Url`, `decodeBase64Url`, …).
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Promise queue with concurrency, priority, timeout, cancellation, and rate-limit controls.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# hanzio/p-queue
|
|
6
|
+
|
|
7
|
+
`hanzio/p-queue` is a small dependency-free promise queue inspired by
|
|
8
|
+
`p-queue`. Use it when you need to run async work with bounded concurrency,
|
|
9
|
+
priority ordering, basic rate limiting, or queue lifecycle hooks.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import PQueue from 'hanzio/p-queue'
|
|
13
|
+
|
|
14
|
+
const queue = new PQueue({ concurrency: 2 })
|
|
15
|
+
|
|
16
|
+
const results = await Promise.all([
|
|
17
|
+
queue.add(async () => fetchUser('1')),
|
|
18
|
+
queue.add(async () => fetchUser('2')),
|
|
19
|
+
queue.add(async () => fetchUser('3'))
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
await queue.onIdle()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Common Usage
|
|
26
|
+
|
|
27
|
+
### Limit Concurrency
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const queue = new PQueue({ concurrency: 3 })
|
|
31
|
+
|
|
32
|
+
for (const id of userIds) {
|
|
33
|
+
queue.add(async () => syncUser(id))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await queue.onIdle()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Only three tasks run at the same time. Queued tasks start as running tasks
|
|
40
|
+
finish.
|
|
41
|
+
|
|
42
|
+
### Add Multiple Tasks
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const results = await queue.addAll([
|
|
46
|
+
async () => 'first',
|
|
47
|
+
async () => 'second'
|
|
48
|
+
])
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`addAll` resolves results in the same order as the input functions.
|
|
52
|
+
|
|
53
|
+
### Pause And Resume
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const queue = new PQueue({ autoStart: false })
|
|
57
|
+
|
|
58
|
+
queue.add(async () => syncUser('1'))
|
|
59
|
+
queue.add(async () => syncUser('2'))
|
|
60
|
+
|
|
61
|
+
queue.start()
|
|
62
|
+
await queue.onIdle()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use `pause()` to stop starting new queued tasks. Running tasks continue.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
queue.pause()
|
|
69
|
+
queue.start()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Priority
|
|
73
|
+
|
|
74
|
+
Higher priority tasks run first. Tasks with the same priority keep FIFO order.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const queue = new PQueue({ concurrency: 1, autoStart: false })
|
|
78
|
+
|
|
79
|
+
queue.add(async () => 'low', { priority: 0, id: 'low' })
|
|
80
|
+
queue.add(async () => 'high', { priority: 10, id: 'high' })
|
|
81
|
+
|
|
82
|
+
queue.start()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
You can update queued task priority by id before it starts:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
queue.setPriority('low', 20)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Timeout
|
|
92
|
+
|
|
93
|
+
Timeouts begin when the task starts running, not while it is waiting in the
|
|
94
|
+
queue.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import PQueue, { TimeoutError } from 'hanzio/p-queue'
|
|
98
|
+
|
|
99
|
+
const queue = new PQueue({ timeout: 1000 })
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await queue.add(async () => slowTask())
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof TimeoutError) {
|
|
105
|
+
console.log('Task timed out')
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Override the timeout per task:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
await queue.add(async () => slowTask(), { timeout: 5000 })
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Cancellation
|
|
117
|
+
|
|
118
|
+
Pass an `AbortSignal` to remove a queued task or reject a running task. The task
|
|
119
|
+
also receives the same signal.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const controller = new AbortController()
|
|
123
|
+
|
|
124
|
+
const promise = queue.add(
|
|
125
|
+
async ({ signal }) => {
|
|
126
|
+
const response = await fetch(url, { signal })
|
|
127
|
+
return response.json()
|
|
128
|
+
},
|
|
129
|
+
{ signal: controller.signal }
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
controller.abort(new Error('Cancelled'))
|
|
133
|
+
await promise
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Rate Limiting
|
|
137
|
+
|
|
138
|
+
Use `intervalCap` and `interval` to limit how many tasks start during a time
|
|
139
|
+
window.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const queue = new PQueue({
|
|
143
|
+
concurrency: 5,
|
|
144
|
+
intervalCap: 10,
|
|
145
|
+
interval: 1000
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Enable `strict` to use a sliding window instead of a fixed window.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const queue = new PQueue({
|
|
153
|
+
intervalCap: 10,
|
|
154
|
+
interval: 1000,
|
|
155
|
+
strict: true
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Waiting For Queue State
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
await queue.onEmpty()
|
|
163
|
+
await queue.onIdle()
|
|
164
|
+
await queue.onPendingZero()
|
|
165
|
+
await queue.onSizeLessThan(10)
|
|
166
|
+
await queue.onRateLimit()
|
|
167
|
+
await queue.onRateLimitCleared()
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- `onEmpty()` resolves when there are no queued tasks waiting to start.
|
|
171
|
+
- `onIdle()` resolves when there are no queued or running tasks.
|
|
172
|
+
- `onPendingZero()` resolves when all currently running tasks finish.
|
|
173
|
+
- `onSizeLessThan(limit)` resolves when `queue.size < limit`.
|
|
174
|
+
- `onRateLimit()` resolves when the queue becomes rate-limited.
|
|
175
|
+
- `onRateLimitCleared()` resolves when rate limiting clears.
|
|
176
|
+
|
|
177
|
+
## Events
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
queue.on('active', () => {
|
|
181
|
+
console.log('A task started')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
queue.on('completed', (result) => {
|
|
185
|
+
console.log('Task completed', result)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
queue.on('error', (error) => {
|
|
189
|
+
console.error('Task failed', error)
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Supported events:
|
|
194
|
+
|
|
195
|
+
- `add`
|
|
196
|
+
- `active`
|
|
197
|
+
- `completed`
|
|
198
|
+
- `error`
|
|
199
|
+
- `empty`
|
|
200
|
+
- `idle`
|
|
201
|
+
- `pendingZero`
|
|
202
|
+
- `next`
|
|
203
|
+
- `rateLimit`
|
|
204
|
+
- `rateLimitCleared`
|
|
205
|
+
|
|
206
|
+
Use `off(event, listener)` to remove a listener.
|
|
207
|
+
|
|
208
|
+
## State
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
queue.size
|
|
212
|
+
queue.pending
|
|
213
|
+
queue.isPaused
|
|
214
|
+
queue.isRateLimited
|
|
215
|
+
queue.isSaturated
|
|
216
|
+
queue.runningTasks
|
|
217
|
+
queue.concurrency = 5
|
|
218
|
+
queue.timeout = 2000
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `size` is the number of queued tasks waiting to start.
|
|
222
|
+
- `pending` is the number of running tasks.
|
|
223
|
+
- `runningTasks` returns task metadata for currently running tasks.
|
|
224
|
+
- `clear()` removes queued tasks but does not cancel tasks already running.
|
|
225
|
+
|
|
226
|
+
## Exports
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import PQueue, {
|
|
230
|
+
PriorityQueue,
|
|
231
|
+
TimeoutError,
|
|
232
|
+
type Options,
|
|
233
|
+
type Queue,
|
|
234
|
+
type QueueAddOptions,
|
|
235
|
+
type TaskOptions
|
|
236
|
+
} from 'hanzio/p-queue'
|
|
237
|
+
```
|