@alxia/redis 0.4.0 → 0.5.1
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 +11 -11
- package/dist/index.js +23 -28
- package/dist/index.js.map +4 -4
- package/dist/store.d.ts +17 -22
- package/dist/store.d.ts.map +1 -1
- package/docs/guide/connecting.md +9 -9
- package/docs/guide/rate-limits.md +12 -7
- package/docs/roadmap.md +15 -1
- package/docs/troubleshooting.md +15 -20
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -18,7 +18,8 @@ bun add -d typescript
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
They are peers, with `@alxia/rate-limit` for `redisStore` and `@alxia/cache`
|
|
21
|
-
for `redisCacheStore`.
|
|
21
|
+
for `redisCacheStore`. `@nxgt/redis` is `^0.7.0`: `redisStore` reads the
|
|
22
|
+
rate from the bound limit's `definition`, which 0.7 added. **Bun 1.4 or
|
|
22
23
|
later**: Bun's `RedisClient` is what the nxgt packages run on.
|
|
23
24
|
|
|
24
25
|
## A shared rate limit
|
|
@@ -182,7 +183,7 @@ The bare `RedisClient` forms are unchanged and add no prefix.
|
|
|
182
183
|
|
|
183
184
|
### Defined once, in `defineRedis`
|
|
184
185
|
|
|
185
|
-
With `@nxgt/redis` 0.
|
|
186
|
+
With `@nxgt/redis` 0.7 the rate limit and the idempotency are wired by
|
|
186
187
|
`defineRedis` too (`limits`, `idempotency`), and `redisStore` and `idempotency`
|
|
187
188
|
take the wired entry, so the definition lives in one place and its types flow
|
|
188
189
|
through:
|
|
@@ -196,19 +197,18 @@ const orders = defineIdempotency({ name: 'orders', key: (id: string) => id, ttl:
|
|
|
196
197
|
const wired = await openRedis(defineRedis({ uri: Bun.env['REDIS_URL']!, prefix: 'shop', limits: { api }, idempotency: { orders } }));
|
|
197
198
|
|
|
198
199
|
alxia()
|
|
199
|
-
.use(rateLimit({ store: redisStore(wired.limits.api
|
|
200
|
+
.use(rateLimit({ store: redisStore(wired.limits.api) })) // shop:api:<address>, 100 per 60 s from the definition
|
|
200
201
|
.use(idempotency(wired.idempotency.orders, { required: true })) // shop:orders:<route>:<scope>:<key>
|
|
201
202
|
.post('/orders', ({ reply }) => reply(201, { id: crypto.randomUUID() }));
|
|
202
203
|
```
|
|
203
204
|
|
|
204
205
|
The keys are those `@nxgt/redis` writes, `<prefix>:<name>:<key>`, so another
|
|
205
206
|
consumer calling `wired.limits.api.consume(address)` shares the count. The
|
|
206
|
-
rate, `ttl` and `lease` are the definition's.
|
|
207
|
-
|
|
208
|
-
needs no `limit` nor `windowMs` and writes
|
|
209
|
-
that differs throws at declaration)
|
|
210
|
-
|
|
211
|
-
headers. The wired `idempotency` takes no `name`, `ttl` or `lease`. The limit's key takes a
|
|
207
|
+
rate, `ttl` and `lease` are the definition's. The bound limit carries its
|
|
208
|
+
`definition`, so `redisStore(wired.limits.api)` declares its `limit` and `per`
|
|
209
|
+
as the store's `policy`: `rateLimit` needs no `limit` nor `windowMs` and writes
|
|
210
|
+
its headers from it (one it is given that differs throws at declaration).
|
|
211
|
+
`redisStore(wired.limits.api, api)` still works and is deprecated. The wired `idempotency` takes no `name`, `ttl` or `lease`. The limit's key takes a
|
|
212
212
|
string, and the idempotency's `schema` is `idempotencyResult`: another is a
|
|
213
213
|
compile error. A rate limit moved from `redisStore(handle, { name })` starts
|
|
214
214
|
its counts again, the layouts differing; an idempotency keeps its keys. Both
|
|
@@ -224,8 +224,8 @@ The package's specs run against `$REDIS_URL`, or a `redis-server` on
|
|
|
224
224
|
| export | |
|
|
225
225
|
| --- | --- |
|
|
226
226
|
| `redisStore(client \| handle, { name })`, `RedisStoreOptions` | an `@alxia/rate-limit` store |
|
|
227
|
-
| `redisStore(handle.limits.api)` | the same for a rate limit wired by `defineRedis` (`@nxgt/redis` 0.
|
|
228
|
-
| `redisStore(handle.limits.api, api)` | the same, given the definition
|
|
227
|
+
| `redisStore(handle.limits.api)` | the same for a rate limit wired by `defineRedis` (`@nxgt/redis` 0.7) or bound by hand, returning a `PolicyStore` (from `@alxia/rate-limit`): the definition holds the name and the rate, the store declares its `limit` and `per` (read from `definition`) as its `policy`, and `rateLimit({ store })` needs no `limit` nor `windowMs`; the keys are `<prefix>:<name>:<key>` |
|
|
228
|
+
| `redisStore(handle.limits.api, api)` | deprecated: the same, given the definition beside the limit; its `limit` and `per` must equal the limit's, or it throws |
|
|
229
229
|
| `redisCacheStore(client \| handle, { name })`, `RedisCacheStoreOptions` | an `@alxia/cache` store |
|
|
230
230
|
| `idempotency(client \| handle, options)` | the middleware, given to `app.use` |
|
|
231
231
|
| `idempotency(handle.idempotency.orders, options?)`, `WiredIdempotency`, `WiredIdempotencyOptions` | the same for an idempotency wired by `defineRedis`: its definition holds the `name`, `ttl` and `lease`, so the options take none of them |
|
package/dist/index.js
CHANGED
|
@@ -253,21 +253,24 @@ function idempotency(target, options = {}) {
|
|
|
253
253
|
});
|
|
254
254
|
return restore(value, replayed);
|
|
255
255
|
} catch (error) {
|
|
256
|
-
|
|
257
|
-
return error.response;
|
|
258
|
-
if (error instanceof GuardError && error.code === "IN_PROGRESS") {
|
|
259
|
-
const retryAfter = Math.max(1, Math.ceil((error.retryAfter ?? 0) / 1000));
|
|
260
|
-
return reply(409, refuse("idempotency_in_progress", retryAfter), {
|
|
261
|
-
headers: { "retry-after": String(retryAfter) }
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
if (error instanceof GuardError && error.code === "MISMATCH") {
|
|
265
|
-
return reply(422, refuse("idempotency_key_reused"));
|
|
266
|
-
}
|
|
267
|
-
throw error;
|
|
256
|
+
return answer(error, reply);
|
|
268
257
|
}
|
|
269
258
|
});
|
|
270
259
|
}
|
|
260
|
+
function answer(error, reply) {
|
|
261
|
+
if (error instanceof Unstored)
|
|
262
|
+
return error.response;
|
|
263
|
+
if (error instanceof GuardError && error.code === "IN_PROGRESS") {
|
|
264
|
+
const retryAfter = Math.max(1, Math.ceil((error.retryAfter ?? 0) / 1000));
|
|
265
|
+
return reply(409, refuse("idempotency_in_progress", retryAfter), {
|
|
266
|
+
headers: { "retry-after": String(retryAfter) }
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (error instanceof GuardError && error.code === "MISMATCH") {
|
|
270
|
+
return reply(422, refuse("idempotency_key_reused"));
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
271
274
|
function refuse(error, retryAfter) {
|
|
272
275
|
return retryAfter === undefined ? { error } : { error, retryAfter };
|
|
273
276
|
}
|
|
@@ -288,14 +291,7 @@ function wiredOf(wired, options) {
|
|
|
288
291
|
throw new TypeError(`idempotency: "${own}" is the wired definition's, set in defineIdempotency: the middleware takes none with a wired idempotency`);
|
|
289
292
|
}
|
|
290
293
|
}
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
return wired.keyFor("").replace(/:$/, "");
|
|
294
|
-
} catch {
|
|
295
|
-
return "wired";
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
return { bound: wired, label };
|
|
294
|
+
return { bound: wired, label: () => wired.definition.name };
|
|
299
295
|
}
|
|
300
296
|
function unscopedWarning(name) {
|
|
301
297
|
return `idempotency "${name}": no client scope could be derived (ctx.ip is undefined and no scope option returned one), so these requests run unguarded, nothing stored or replayed. Pass a scope option, (ctx) => a user id, or an ip option to alxia().`;
|
|
@@ -308,7 +304,7 @@ import {
|
|
|
308
304
|
} from "@nxgt/redis";
|
|
309
305
|
function redisStore(target, options) {
|
|
310
306
|
if (isWiredLimit(target)) {
|
|
311
|
-
return
|
|
307
|
+
return policyStore(target, options);
|
|
312
308
|
}
|
|
313
309
|
if (options === undefined || !("name" in options)) {
|
|
314
310
|
throw new TypeError("redisStore: a client or a handle needs { name }; a wired rate limit, as handle.limits.api, is the only form without");
|
|
@@ -378,13 +374,12 @@ function wiredStore(limit) {
|
|
|
378
374
|
}
|
|
379
375
|
};
|
|
380
376
|
}
|
|
381
|
-
function policyStore(limit,
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
throw new TypeError(`redisStore: the definition "${
|
|
377
|
+
function policyStore(limit, given) {
|
|
378
|
+
const { limit: count, per } = limit.definition;
|
|
379
|
+
if (given !== undefined && (given.limit !== count || given.per !== per)) {
|
|
380
|
+
throw new TypeError(`redisStore: the definition "${given.name}" (${given.limit} per ${given.per}ms) is not the rate this limit counts by (${count} per ${per}ms)`);
|
|
385
381
|
}
|
|
386
|
-
|
|
387
|
-
return { ...wiredStore(limit), policy };
|
|
382
|
+
return { ...wiredStore(limit), policy: { limit: count, windowMs: per } };
|
|
388
383
|
}
|
|
389
384
|
export {
|
|
390
385
|
idempotency,
|
|
@@ -395,5 +390,5 @@ export {
|
|
|
395
390
|
redisStore
|
|
396
391
|
};
|
|
397
392
|
|
|
398
|
-
//# debugId=
|
|
393
|
+
//# debugId=8102DF4343F3D91E64756E2164756E21
|
|
399
394
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
"import type { BoundIdempotency, BoundRateLimit, Redis } from '@nxgt/redis';\nimport type { RedisClient } from 'bun';\n\n/**\n * What the factories take: Bun's own client, or the handle `@nxgt/redis`'s\n * `openRedis(defineRedis({ … }))` gives back.\n */\nexport type RedisTarget = RedisClient | Redis<any>;\n\n/** The one instance of a handle, resolved: its client and the prefix in front of its keys. */\nexport interface ResolvedTarget {\n\treadonly client: RedisClient;\n\t/** `undefined` when nothing goes in front of the keys, and always for a bare client. */\n\treadonly prefix: string | undefined;\n}\n\n/** A handle has `instances` and `close`; Bun's `RedisClient` has neither. */\nexport function isHandle(target: RedisTarget): target is Redis<any> {\n\treturn (\n\t\t'instances' in target &&\n\t\t'close' in target &&\n\t\ttypeof target.close === 'function'\n\t);\n}\n\n/**\n * The client and prefix of a target. A handle must wire exactly one Redis\n * instance: with several, which one an app's keys live on is for the app to\n * say, with the bare client of `handle.clients.<name>`.\n */\nexport function resolve(target: RedisTarget): ResolvedTarget {\n\tif (!isHandle(target)) return { client: target, prefix: undefined };\n\tconst names = Object.keys(target.instances);\n\tconst [only] = names;\n\tif (names.length !== 1 || only === undefined) {\n\t\tthrow new TypeError(\n\t\t\t`@alxia/redis: this @nxgt/redis handle wires ${names.length} Redis instances (${names.map((name) => `\"${name}\"`).join(', ')}), and one is needed. Pass the client of one, as handle.clients.${names[0] ?? 'name'}, with its prefix in the name you give.`,\n\t\t);\n\t}\n\tconst instance = target.instances[only as never] as {\n\t\tclient: RedisClient;\n\t\tprefix: string | undefined;\n\t};\n\treturn { client: instance.client, prefix: instance.prefix };\n}\n\n/** `name`, with the handle's prefix in front when there is one: the one name a deployment's keys share. */\nexport function nameUnder(\n\ttarget: RedisTarget,\n\tname: string,\n): {\n\treadonly client: RedisClient;\n\treadonly name: string;\n} {\n\tconst { client, prefix } = resolve(target);\n\treturn { client, name: prefix === undefined ? name : `${prefix}:${name}` };\n}\n\n/** A rate limit bound by `@nxgt/redis`, wired or by hand: `consume`, `peek`, `reset` and `keyFor`. */\nexport function isWiredLimit(value: object): value is BoundRateLimit<string> {\n\tconst limit = value as Partial<BoundRateLimit<string>>;\n\treturn (\n\t\ttypeof limit.consume === 'function' &&\n\t\ttypeof limit.peek === 'function' &&\n\t\ttypeof limit.reset === 'function' &&\n\t\ttypeof limit.keyFor === 'function'\n\t);\n}\n\n/** An idempotency bound by `@nxgt/redis`, wired or by hand: `run`, `forget` and `keyFor`. */\nexport function isWiredIdempotency(\n\tvalue: object,\n): value is BoundIdempotency<string, any, any> {\n\tconst bound = value as Partial<BoundIdempotency<string, unknown>>;\n\treturn (\n\t\ttypeof bound.run === 'function' &&\n\t\ttypeof bound.forget === 'function' &&\n\t\ttypeof bound.keyFor === 'function'\n\t);\n}\n",
|
|
7
7
|
"import type { PingResult } from '@nxgt/redis';\nimport type { RedisTarget } from './handle';\nimport { isHandle } from './handle';\n\nexport interface RedisCheckOptions {\n\t/**\n\t * Milliseconds a handle's ping waits for each instance, as\n\t * `health({ timeout })` names its own: `@nxgt/redis`'s 2 s by default.\n\t */\n\treadonly timeout?: number;\n}\n\n/**\n * A readiness check for core's `health({ checks })`: it passes when every\n * Redis instance of the handle answers a `PING`, and is down when one does\n * not, or when the call fails. Given a client, it sends the `PING` itself.\n *\n * ```ts\n * app.plugin(health({ checks: { redis: redisCheck(handle) } }));\n * ```\n */\nexport function redisCheck(\n\ttarget: RedisTarget,\n\toptions: RedisCheckOptions = {},\n): () => Promise<boolean> {\n\treturn async () => {\n\t\tif (!isHandle(target)) {\n\t\t\tawait target.send('PING', []);\n\t\t\treturn true;\n\t\t}\n\t\tconst results: Record<string, PingResult> = await target.ping(\n\t\t\toptions.timeout === undefined\n\t\t\t\t? undefined\n\t\t\t\t: { timeoutMs: options.timeout },\n\t\t);\n\t\treturn Object.values(results).every((result) => result.ok);\n\t};\n}\n",
|
|
8
8
|
"import { alxia, markFactory } from '@alxia/core';\nimport {\n\ttype BoundCache,\n\tbindCache,\n\ttype CacheDefinition,\n\ttype LockOptions,\n\ttype Redis,\n\ttype SoleInstance,\n\twithLock,\n} from '@nxgt/redis';\nimport type { RedisClient } from 'bun';\nimport type { z } from 'zod';\nimport { isHandle, resolve } from './handle';\n\n/**\n * Any `@nxgt/redis` cache definition, whatever its key parameters and\n * schema: what `redis()`'s `caches` hold. The constraint a function generic\n * over the caches it hands to `redis()` takes:\n *\n * ```ts\n * function withCaches<const Caches extends Record<string, AnyCache>>(caches: Caches) {\n * return alxia().plugin(redis(client, { caches }));\n * }\n * ```\n */\nexport type AnyCache = CacheDefinition<any, z.ZodType>;\n\n/** The caches of `Caches`, each bound to the client. */\nexport type BoundCaches<Caches extends Record<string, AnyCache>> = {\n\treadonly [Name in keyof Caches]: Caches[Name] extends CacheDefinition<\n\t\tinfer Params,\n\t\tinfer Schema\n\t>\n\t\t? BoundCache<Params, z.output<Schema>, z.input<Schema>>\n\t\t: never;\n};\n\nexport interface RedisContextOptions<Caches extends Record<string, AnyCache>> {\n\t/** `@nxgt/redis` cache definitions, by the name routes read them under. */\n\treadonly caches?: Caches;\n}\n\n/** What routes after `redis()` read. */\nexport interface RedisContext<Caches extends Record<string, AnyCache>> {\n\t/** Bun's own client, untouched. */\n\treadonly redis: RedisClient;\n\t/** Each cache, bound and typed by its schema: `caches.users.remember(…)`. */\n\treadonly caches: BoundCaches<Caches>;\n\t/** `work` under a lock every process sharing the Redis respects: `@nxgt/redis`'s `withLock`. */\n\tlock<T>(\n\t\tkey: string,\n\t\twork: () => Promise<T> | T,\n\t\toptions?: LockOptions,\n\t): Promise<T>;\n}\n\nexport interface RedisHandleOptions {\n\t/**\n\t * Whether the handle is closed when the app stops: once, after the drain,\n\t * in `onStop`. On by default; `false` when something else closes it.\n\t */\n\treadonly close?: boolean;\n}\n\n/** What routes after `redis(handle)` read. */\nexport interface RedisHandleContext<C> {\n\t/** Bun's own client, untouched: the handle's one instance. */\n\treadonly redis: RedisClient;\n\t/** The handle's caches, typed by their schemas, the prefix already in front of every key. */\n\treadonly caches: SoleInstance<C>['cache'];\n\t/** `work` under a lock every process sharing the Redis respects, under the handle's prefix. */\n\treadonly lock: SoleInstance<C>['lock'];\n\t/** What goes in front of every key, or `undefined`. */\n\treadonly prefix: string | undefined;\n}\n\nfunction fromClient<\n\tconst Caches extends Record<string, AnyCache> = Record<never, never>,\n>(client: RedisClient, options: RedisContextOptions<Caches> = {}) {\n\tconst caches = Object.fromEntries(\n\t\tObject.entries(options.caches ?? {}).map(([name, definition]) => [\n\t\t\tname,\n\t\t\tbindCache(client, definition),\n\t\t]),\n\t) as BoundCaches<Caches>;\n\tconst context: RedisContext<Caches> = {\n\t\tredis: client,\n\t\tcaches,\n\t\tlock: (key, work, lockOptions) => withLock(client, key, work, lockOptions),\n\t};\n\treturn alxia().decorate(context);\n}\n\nfunction fromHandle<C>(handle: Redis<C>, options: RedisHandleOptions = {}) {\n\tconst { client, prefix } = resolve(handle);\n\tconst [name] = Object.keys(handle.instances);\n\tconst instance = handle.instances[\n\t\tname as never\n\t] as unknown as SoleInstance<C>;\n\tconst context: RedisHandleContext<C> = {\n\t\tredis: client,\n\t\tcaches: instance.cache,\n\t\tlock: instance.lock,\n\t\tprefix,\n\t};\n\tlet closing: Promise<void> | undefined;\n\tconst app = alxia().decorate(context);\n\treturn options.close === false\n\t\t? app\n\t\t: app.onStop(() => {\n\t\t\t\tclosing ??= handle.close();\n\t\t\t\treturn closing;\n\t\t\t});\n}\n\n/**\n * Redis in the context, as a plugin: the client, the caches bound once, and\n * a lock — typed, for every route declared after it.\n *\n * ```ts\n * const users = defineCache({ name: 'user', key: (id: string) => id, ttl: 300, schema: User });\n * app.plugin(redis(connection.client, { caches: { users } }))\n * .get('/users/:id', async ({ caches, params, reply }) => reply.ok(await caches.users.remember(params.id, load)));\n * ```\n *\n * Given an `@nxgt/redis` handle, the caches are the handle's own, the\n * prefix is in front of every key and lock, and the handle is closed when\n * the app stops, after the drain (`{ close: false }` to close it yourself):\n *\n * ```ts\n * const handle = await openRedis(defineRedis({ uri, prefix: 'shop', caches }));\n * app.plugin(redis(handle)).get('/u/:id', ({ caches, params }) => caches.users.get(params.id));\n * ```\n */\nexport function redis<\n\tconst Caches extends Record<string, AnyCache> = Record<never, never>,\n>(\n\tclient: RedisClient,\n\toptions?: RedisContextOptions<Caches>,\n): ReturnType<typeof fromClient<Caches>>;\nexport function redis<C>(\n\thandle: Redis<C>,\n\toptions?: RedisHandleOptions,\n): ReturnType<typeof fromHandle<C>>;\nexport function redis(\n\ttarget: RedisClient | Redis<never>,\n\toptions?: RedisContextOptions<Record<string, AnyCache>> & RedisHandleOptions,\n): unknown {\n\treturn isHandle(target)\n\t\t? fromHandle(target, options)\n\t\t: fromClient(target, options);\n}\n\nmarkFactory(redis, 'plugin');\n",
|
|
9
|
-
"import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\ttype Empty,\n\ttype Middleware,\n\tmarkFactory,\n\ttype Reply,\n\tsettle,\n} from '@alxia/core';\nimport {\n\ttype BoundIdempotency,\n\tbindIdempotency,\n\tdefineIdempotency,\n\tGuardError,\n} from '@nxgt/redis';\nimport { isWiredIdempotency, nameUnder, type RedisTarget } from './handle';\nimport {\n\tfingerprintOf,\n\ttype IdempotencyResult,\n\tidempotencyResult,\n\trestore,\n\tstore,\n\tUnstored,\n} from './idempotency-response';\n\nexport interface IdempotencyOptions {\n\t/** Names the keys it stores, under a handle's prefix when it is given one. */\n\treadonly name: string;\n\t/** Seconds a finished response is kept and replayed. A day by default. */\n\treadonly ttl?: number;\n\t/** Milliseconds a running request holds its key unless renewed. `@nxgt/redis`'s 10 s by default. */\n\treadonly lease?: number;\n\t/** Milliseconds a repeat waits for the first to finish before a 409. None by default. */\n\treadonly wait?: number;\n\t/** The methods it guards. `POST` and `PATCH` by default: the others are idempotent already. */\n\treadonly methods?: readonly string[];\n\t/** The header the key is read from. `Idempotency-Key` by default. */\n\treadonly header?: string;\n\t/** Whether a guarded request without a key is refused, with a 400. Off by default. */\n\treadonly required?: boolean;\n\t/**\n\t * Whose key it is: keys are scoped by the route and by this, so two\n\t * clients choosing the same key never see each other's response. The\n\t * client's address by default; a user id when there is one. A request\n\t * it gives no scope for runs unguarded — nothing stored, nothing\n\t * replayed — with a warning, once.\n\t */\n\treadonly scope?: (ctx: BaseContext) => string | undefined;\n}\n\n/** The body of a refusal. */\nexport interface IdempotencyErrorBody {\n\treadonly error:\n\t\t| 'idempotency_key_missing'\n\t\t| 'idempotency_key_invalid'\n\t\t| 'idempotency_in_progress'\n\t\t| 'idempotency_key_reused';\n\t/** Seconds until a running request should be over: with `idempotency_in_progress`. */\n\treadonly retryAfter?: number;\n}\n\n/**\n * What `idempotency()` makes: a middleware that adds nothing to the\n * context, and may answer a 400, a 409 or a 422.\n */\nexport type IdempotencyMiddleware = Middleware<\n\tEmpty,\n\tPromise<\n\t\t| Response\n\t\t| Reply<400, IdempotencyErrorBody>\n\t\t| Reply<409, IdempotencyErrorBody>\n\t\t| Reply<422, IdempotencyErrorBody>\n\t>\n>;\n\n/**\n * An idempotency wired by `defineRedis` — `handle.idempotency.orders` — whose\n * definition holds `idempotencyResult` as its schema and a key taken as a\n * string.\n */\nexport type WiredIdempotency = BoundIdempotency<\n\tstring,\n\tIdempotencyResult,\n\tIdempotencyResult\n>;\n\n/** What a wired idempotency leaves to the middleware: its name, `ttl` and `lease` are the definition's. */\nexport type WiredIdempotencyOptions = Omit<\n\tIdempotencyOptions,\n\t'name' | 'ttl' | 'lease'\n>;\n\nconst KEY = /^[\\x21-\\x7e]{1,255}$/;\n\n/**\n * Idempotent routes, as a middleware, with `@nxgt/redis`: a `POST` or\n * `PATCH` carrying an `Idempotency-Key` runs once per key, and every repeat\n * gets the first response back, marked `Idempotent-Replayed: true` — across\n * every process sharing the Redis. Routes declared after it are guarded; a\n * request no route matches is not: there is no route to scope its key by.\n * What is kept is the response the route answers, an error's answer\n * included.\n *\n * A repeat while the first still runs is a 409, and the same key with\n * another request — method, path or body — a 422: both are part of every\n * guarded route's type. A 5xx, or a stream, is answered and not kept: the\n * key is free again.\n *\n * ```ts\n * app.use(idempotency(redis.client, { name: 'payments' })).post('/payments', ...);\n * ```\n *\n * Given an `@nxgt/redis` handle instead of a client, the keys are under the\n * handle's `prefix`: `idempotency(handle, { name: 'payments' })`.\n *\n * Given an idempotency wired by `defineRedis`, the definition — name, `ttl`,\n * `lease` — is the one place, and the keys are those `@nxgt/redis` writes,\n * `<prefix>:<name>:<route>:<scope>:<key>`:\n *\n * ```ts\n * app.use(idempotency(handle.idempotency.orders, { required: true })).post('/orders', ...);\n * ```\n */\nexport function idempotency(\n\ttarget: RedisTarget,\n\toptions: IdempotencyOptions,\n): IdempotencyMiddleware;\nexport function idempotency(\n\twired: WiredIdempotency,\n\toptions?: WiredIdempotencyOptions,\n): IdempotencyMiddleware;\nexport function idempotency(\n\ttarget: RedisTarget | WiredIdempotency,\n\toptions: IdempotencyOptions | WiredIdempotencyOptions = {},\n): IdempotencyMiddleware {\n\tconst { bound, label } = isWiredIdempotency(target)\n\t\t? wiredOf(target, options)\n\t\t: boundOf(target, options as IdempotencyOptions);\n\tconst methods = new Set(options.methods ?? ['POST', 'PATCH']);\n\tconst header = options.header ?? 'idempotency-key';\n\tconst scope = options.scope ?? ((ctx: BaseContext) => ctx.ip);\n\tlet warned = false;\n\tconst warnUnscoped = () => {\n\t\tif (warned) return;\n\t\twarned = true;\n\t\tconsole.warn(unscopedWarning(label()));\n\t};\n\n\treturn defineMiddleware(async function idempotency(ctx, next) {\n\t\tconst { request, reply, route } = ctx;\n\t\tif (route === undefined || !methods.has(request.method)) return next();\n\t\tconst key = request.headers.get(header);\n\t\tif (key === null) {\n\t\t\treturn options.required\n\t\t\t\t? reply(400, refuse('idempotency_key_missing'))\n\t\t\t\t: next();\n\t\t}\n\t\tif (!KEY.test(key)) return reply(400, refuse('idempotency_key_invalid'));\n\n\t\tconst scoped = scope(ctx);\n\t\tif (scoped === undefined) {\n\t\t\twarnUnscoped();\n\t\t\treturn next();\n\t\t}\n\t\tconst id = `${route}:${scoped}:${key}`;\n\t\tconst fingerprint = await fingerprintOf(request, ctx.url);\n\n\t\ttry {\n\t\t\tconst { value, replayed } = await bound.run(\n\t\t\t\tid,\n\t\t\t\tasync () => store(await settle(ctx, next())),\n\t\t\t\t{\n\t\t\t\t\tfingerprint,\n\t\t\t\t\t...(options.wait === undefined ? {} : { wait: options.wait }),\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn restore(value, replayed);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Unstored) return error.response;\n\
|
|
9
|
+
"import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\ttype Empty,\n\ttype Middleware,\n\tmarkFactory,\n\ttype Reply,\n\tsettle,\n} from '@alxia/core';\nimport {\n\ttype BoundIdempotency,\n\tbindIdempotency,\n\tdefineIdempotency,\n\tGuardError,\n} from '@nxgt/redis';\nimport { isWiredIdempotency, nameUnder, type RedisTarget } from './handle';\nimport {\n\tfingerprintOf,\n\ttype IdempotencyResult,\n\tidempotencyResult,\n\trestore,\n\tstore,\n\tUnstored,\n} from './idempotency-response';\n\nexport interface IdempotencyOptions {\n\t/** Names the keys it stores, under a handle's prefix when it is given one. */\n\treadonly name: string;\n\t/** Seconds a finished response is kept and replayed. A day by default. */\n\treadonly ttl?: number;\n\t/** Milliseconds a running request holds its key unless renewed. `@nxgt/redis`'s 10 s by default. */\n\treadonly lease?: number;\n\t/** Milliseconds a repeat waits for the first to finish before a 409. None by default. */\n\treadonly wait?: number;\n\t/** The methods it guards. `POST` and `PATCH` by default: the others are idempotent already. */\n\treadonly methods?: readonly string[];\n\t/** The header the key is read from. `Idempotency-Key` by default. */\n\treadonly header?: string;\n\t/** Whether a guarded request without a key is refused, with a 400. Off by default. */\n\treadonly required?: boolean;\n\t/**\n\t * Whose key it is: keys are scoped by the route and by this, so two\n\t * clients choosing the same key never see each other's response. The\n\t * client's address by default; a user id when there is one. A request\n\t * it gives no scope for runs unguarded — nothing stored, nothing\n\t * replayed — with a warning, once.\n\t */\n\treadonly scope?: (ctx: BaseContext) => string | undefined;\n}\n\n/** The body of a refusal. */\nexport interface IdempotencyErrorBody {\n\treadonly error:\n\t\t| 'idempotency_key_missing'\n\t\t| 'idempotency_key_invalid'\n\t\t| 'idempotency_in_progress'\n\t\t| 'idempotency_key_reused';\n\t/** Seconds until a running request should be over: with `idempotency_in_progress`. */\n\treadonly retryAfter?: number;\n}\n\n/**\n * What `idempotency()` makes: a middleware that adds nothing to the\n * context, and may answer a 400, a 409 or a 422.\n */\nexport type IdempotencyMiddleware = Middleware<\n\tEmpty,\n\tPromise<\n\t\t| Response\n\t\t| Reply<400, IdempotencyErrorBody>\n\t\t| Reply<409, IdempotencyErrorBody>\n\t\t| Reply<422, IdempotencyErrorBody>\n\t>\n>;\n\n/**\n * An idempotency wired by `defineRedis` — `handle.idempotency.orders` — whose\n * definition holds `idempotencyResult` as its schema and a key taken as a\n * string.\n */\nexport type WiredIdempotency = BoundIdempotency<\n\tstring,\n\tIdempotencyResult,\n\tIdempotencyResult\n>;\n\n/** What a wired idempotency leaves to the middleware: its name, `ttl` and `lease` are the definition's. */\nexport type WiredIdempotencyOptions = Omit<\n\tIdempotencyOptions,\n\t'name' | 'ttl' | 'lease'\n>;\n\nconst KEY = /^[\\x21-\\x7e]{1,255}$/;\n\n/**\n * Idempotent routes, as a middleware, with `@nxgt/redis`: a `POST` or\n * `PATCH` carrying an `Idempotency-Key` runs once per key, and every repeat\n * gets the first response back, marked `Idempotent-Replayed: true` — across\n * every process sharing the Redis. Routes declared after it are guarded; a\n * request no route matches is not: there is no route to scope its key by.\n * What is kept is the response the route answers, an error's answer\n * included.\n *\n * A repeat while the first still runs is a 409, and the same key with\n * another request — method, path or body — a 422: both are part of every\n * guarded route's type. A 5xx, or a stream, is answered and not kept: the\n * key is free again.\n *\n * ```ts\n * app.use(idempotency(redis.client, { name: 'payments' })).post('/payments', ...);\n * ```\n *\n * Given an `@nxgt/redis` handle instead of a client, the keys are under the\n * handle's `prefix`: `idempotency(handle, { name: 'payments' })`.\n *\n * Given an idempotency wired by `defineRedis`, the definition — name, `ttl`,\n * `lease` — is the one place, and the keys are those `@nxgt/redis` writes,\n * `<prefix>:<name>:<route>:<scope>:<key>`:\n *\n * ```ts\n * app.use(idempotency(handle.idempotency.orders, { required: true })).post('/orders', ...);\n * ```\n */\nexport function idempotency(\n\ttarget: RedisTarget,\n\toptions: IdempotencyOptions,\n): IdempotencyMiddleware;\nexport function idempotency(\n\twired: WiredIdempotency,\n\toptions?: WiredIdempotencyOptions,\n): IdempotencyMiddleware;\nexport function idempotency(\n\ttarget: RedisTarget | WiredIdempotency,\n\toptions: IdempotencyOptions | WiredIdempotencyOptions = {},\n): IdempotencyMiddleware {\n\tconst { bound, label } = isWiredIdempotency(target)\n\t\t? wiredOf(target, options)\n\t\t: boundOf(target, options as IdempotencyOptions);\n\tconst methods = new Set(options.methods ?? ['POST', 'PATCH']);\n\tconst header = options.header ?? 'idempotency-key';\n\tconst scope = options.scope ?? ((ctx: BaseContext) => ctx.ip);\n\tlet warned = false;\n\tconst warnUnscoped = () => {\n\t\tif (warned) return;\n\t\twarned = true;\n\t\tconsole.warn(unscopedWarning(label()));\n\t};\n\n\treturn defineMiddleware(async function idempotency(ctx, next) {\n\t\tconst { request, reply, route } = ctx;\n\t\tif (route === undefined || !methods.has(request.method)) return next();\n\t\tconst key = request.headers.get(header);\n\t\tif (key === null) {\n\t\t\treturn options.required\n\t\t\t\t? reply(400, refuse('idempotency_key_missing'))\n\t\t\t\t: next();\n\t\t}\n\t\tif (!KEY.test(key)) return reply(400, refuse('idempotency_key_invalid'));\n\n\t\tconst scoped = scope(ctx);\n\t\tif (scoped === undefined) {\n\t\t\twarnUnscoped();\n\t\t\treturn next();\n\t\t}\n\t\tconst id = `${route}:${scoped}:${key}`;\n\t\tconst fingerprint = await fingerprintOf(request, ctx.url);\n\n\t\ttry {\n\t\t\tconst { value, replayed } = await bound.run(\n\t\t\t\tid,\n\t\t\t\tasync () => store(await settle(ctx, next())),\n\t\t\t\t{\n\t\t\t\t\tfingerprint,\n\t\t\t\t\t...(options.wait === undefined ? {} : { wait: options.wait }),\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn restore(value, replayed);\n\t\t} catch (error) {\n\t\t\treturn answer(error, reply);\n\t\t}\n\t});\n}\n\n/** The answer to what a run threw: the response kept apart, a 409 or a 422, or the error itself. */\nfunction answer(error: unknown, reply: BaseContext['reply']) {\n\tif (error instanceof Unstored) return error.response;\n\tif (error instanceof GuardError && error.code === 'IN_PROGRESS') {\n\t\tconst retryAfter = Math.max(1, Math.ceil((error.retryAfter ?? 0) / 1000));\n\t\treturn reply(409, refuse('idempotency_in_progress', retryAfter), {\n\t\t\theaders: { 'retry-after': String(retryAfter) },\n\t\t});\n\t}\n\tif (error instanceof GuardError && error.code === 'MISMATCH') {\n\t\treturn reply(422, refuse('idempotency_key_reused'));\n\t}\n\tthrow error;\n}\n\nfunction refuse(\n\terror: IdempotencyErrorBody['error'],\n\tretryAfter?: number,\n): IdempotencyErrorBody {\n\treturn retryAfter === undefined ? { error } : { error, retryAfter };\n}\n\n/** The idempotency bound from a client or a handle: the keys are `<prefix>:<name>:<id>`. */\nfunction boundOf(target: RedisTarget, options: IdempotencyOptions) {\n\tconst { client, name } = nameUnder(target, options.name);\n\tconst bound = bindIdempotency(\n\t\tclient,\n\t\tdefineIdempotency({\n\t\t\tname,\n\t\t\tkey: (key: string) => key,\n\t\t\tttl: options.ttl ?? 86_400,\n\t\t\t...(options.lease === undefined ? {} : { lease: options.lease }),\n\t\t\tschema: idempotencyResult,\n\t\t}),\n\t);\n\treturn { bound: bound as WiredIdempotency, label: () => options.name };\n}\n\n/** The idempotency wired by `defineRedis`, which carries its own name, `ttl` and `lease`. */\nfunction wiredOf(wired: WiredIdempotency, options: object) {\n\tfor (const own of ['name', 'ttl', 'lease']) {\n\t\tif (own in options) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`idempotency: \"${own}\" is the wired definition's, set in defineIdempotency: the middleware takes none with a wired idempotency`,\n\t\t\t);\n\t\t}\n\t}\n\treturn { bound: wired, label: () => wired.definition.name };\n}\n\n/** The warning a request with no scope prints, once per middleware. */\nfunction unscopedWarning(name: string): string {\n\treturn `idempotency \"${name}\": no client scope could be derived (ctx.ip is undefined and no scope option returned one), so these requests run unguarded, nothing stored or replayed. Pass a scope option, (ctx) => a user id, or an ip option to alxia().`;\n}\n\nmarkFactory(idempotency);\n",
|
|
10
10
|
"import { z } from 'zod';\n\n/**\n * What `idempotency()` keeps under a key: the response, as the route\n * answered it. The `schema` of a wired `defineIdempotency` that\n * `idempotency(handle.idempotency.orders)` is given:\n *\n * ```ts\n * export const orders = defineIdempotency({ name: 'orders', key: (id: string) => id, ttl: 86_400, schema: idempotencyResult });\n * ```\n */\nexport const idempotencyResult = z.object({\n\tstatus: z.number().int(),\n\theaders: z.array(z.tuple([z.string(), z.string()])),\n\tbody: z.string(),\n});\nexport type IdempotencyResult = z.infer<typeof idempotencyResult>;\n\n/** Headers a replay never repeats: a session cookie belongs to one response. */\nconst UNSTORED_HEADERS = new Set(['set-cookie', 'date', 'content-length']);\n\n/** A response that is answered and not kept: a 5xx, a stream. */\nexport class Unstored extends Error {\n\treadonly response: Response;\n\tconstructor(response: Response) {\n\t\tsuper('unstored');\n\t\tthis.response = response;\n\t}\n}\n\n/** What a key is bound to: the method, the path and query, the body. */\nexport async function fingerprintOf(\n\trequest: Request,\n\turl: URL,\n): Promise<Uint8Array> {\n\tconst body = new Uint8Array(await request.clone().arrayBuffer());\n\tconst head = new TextEncoder().encode(\n\t\t`${request.method} ${url.pathname}${url.search}\\n`,\n\t);\n\tconst fingerprint = new Uint8Array(head.length + body.length);\n\tfingerprint.set(head);\n\tfingerprint.set(body, head.length);\n\treturn fingerprint;\n}\n\nexport async function store(response: Response): Promise<IdempotencyResult> {\n\tif (\n\t\tresponse.status >= 500 ||\n\t\tresponse.headers.get('content-type')?.startsWith('text/event-stream')\n\t) {\n\t\tthrow new Unstored(response);\n\t}\n\tconst headers: [string, string][] = [];\n\tfor (const [name, value] of response.headers) {\n\t\tif (!UNSTORED_HEADERS.has(name)) headers.push([name, value]);\n\t}\n\tconst bytes = new Uint8Array(await response.arrayBuffer());\n\treturn {\n\t\tstatus: response.status,\n\t\theaders,\n\t\tbody: Buffer.from(bytes).toString('base64'),\n\t};\n}\n\nexport function restore(\n\tstored: IdempotencyResult,\n\treplayed: boolean,\n): Response {\n\tconst headers = new Headers(stored.headers);\n\tif (replayed) headers.set('idempotent-replayed', 'true');\n\tconst body = Buffer.from(stored.body, 'base64');\n\treturn new Response(\n\t\tstored.status === 204 || stored.status === 304 ? null : body,\n\t\t{ status: stored.status, headers },\n\t);\n}\n",
|
|
11
|
-
"import type {\n\tDecision,\n\tPolicy,\n\tPolicyStore,\n\tRateLimitStore,\n} from '@alxia/rate-limit';\nimport {\n\ttype BoundRateLimit,\n\tbindRateLimit,\n\tdefineRateLimit,\n\ttype RateLimitDefinition,\n} from '@nxgt/redis';\nimport { isWiredLimit, nameUnder, type RedisTarget } from './handle';\n\nexport interface RedisStoreOptions {\n\t/**\n\t * Prepended to every key it counts: one name per limit, so two never share\n\t * a count. Under a handle's prefix, when it is given one.\n\t */\n\treadonly name: string;\n}\n\n/**\n * An `@alxia/rate-limit` store in Redis, with `@nxgt/redis`'s GCRA:\n * every process sharing the Redis counts together, timed by the Redis\n * server's clock, and a refused request counts nothing.\n *\n * ```ts\n * app.use(rateLimit({ limit: 100, windowMs: 60_000, store: redisStore(redis.client, { name: 'api' }) }));\n * ```\n *\n * Given an `@nxgt/redis` handle instead of a client, every key is under the\n * handle's `prefix`: `redisStore(handle, { name: 'api' })` counts under\n * `<prefix>:api:…`.\n */\nexport function redisStore(\n\ttarget: RedisTarget,\n\toptions: RedisStoreOptions,\n): RateLimitStore;\n/**\n * The store of a rate limit wired by `defineRedis
|
|
11
|
+
"import type {\n\tDecision,\n\tPolicy,\n\tPolicyStore,\n\tRateLimitStore,\n} from '@alxia/rate-limit';\nimport {\n\ttype BoundRateLimit,\n\tbindRateLimit,\n\tdefineRateLimit,\n\ttype RateLimitDefinition,\n} from '@nxgt/redis';\nimport { isWiredLimit, nameUnder, type RedisTarget } from './handle';\n\nexport interface RedisStoreOptions {\n\t/**\n\t * Prepended to every key it counts: one name per limit, so two never share\n\t * a count. Under a handle's prefix, when it is given one.\n\t */\n\treadonly name: string;\n}\n\n/**\n * An `@alxia/rate-limit` store in Redis, with `@nxgt/redis`'s GCRA:\n * every process sharing the Redis counts together, timed by the Redis\n * server's clock, and a refused request counts nothing.\n *\n * ```ts\n * app.use(rateLimit({ limit: 100, windowMs: 60_000, store: redisStore(redis.client, { name: 'api' }) }));\n * ```\n *\n * Given an `@nxgt/redis` handle instead of a client, every key is under the\n * handle's `prefix`: `redisStore(handle, { name: 'api' })` counts under\n * `<prefix>:api:…`.\n */\nexport function redisStore(\n\ttarget: RedisTarget,\n\toptions: RedisStoreOptions,\n): RateLimitStore;\n/**\n * The store of a rate limit bound by `@nxgt/redis` (0.7 and later), wired by\n * `defineRedis` or by hand: the definition — its `limit`, `per` and `burst` —\n * is the one place. The store declares its `limit` and `per` as its `policy`,\n * so `rateLimit({ store })` takes `limit` and `windowMs` from it and the\n * numbers are written once. The keys are those `@nxgt/redis` writes,\n * `<prefix>:<name>:<key>`, so every consumer of the handle counts together,\n * alxia's routes included.\n *\n * ```ts\n * const api = defineRateLimit({ name: 'api', key: (ip: string) => ip, limit: 100, per: 60_000 });\n * const handle = await openRedis(defineRedis({ uri, prefix: 'shop', limits: { api } }));\n * app.use(rateLimit({ store: redisStore(handle.limits.api) }));\n * ```\n *\n * A `limit` or `windowMs` given to `rateLimit` too must equal the\n * definition's, or `rateLimit()` throws at declaration. The limit's key must\n * take a string, the one `rateLimit` counts by.\n */\nexport function redisStore(limit: BoundRateLimit<string>): PolicyStore;\n/**\n * A bound rate limit with its definition beside it.\n *\n * @deprecated The bound limit carries its definition since `@nxgt/redis`\n * 0.7: `redisStore(handle.limits.api)` alone is the same store. Given both,\n * the definition's `limit` and `per` must equal the bound limit's, or this\n * throws.\n */\nexport function redisStore(\n\tlimit: BoundRateLimit<string>,\n\tdefinition: RateLimitDefinition<string>,\n): PolicyStore;\nexport function redisStore(\n\ttarget: RedisTarget | BoundRateLimit<string>,\n\toptions?: RedisStoreOptions | RateLimitDefinition<string>,\n): RateLimitStore {\n\tif (isWiredLimit(target)) {\n\t\treturn policyStore(\n\t\t\ttarget,\n\t\t\toptions as RateLimitDefinition<string> | undefined,\n\t\t);\n\t}\n\tif (options === undefined || !('name' in options)) {\n\t\tthrow new TypeError(\n\t\t\t'redisStore: a client or a handle needs { name }; a wired rate limit, as handle.limits.api, is the only form without',\n\t\t);\n\t}\n\tconst { client, name } = nameUnder(\n\t\ttarget,\n\t\t(options as RedisStoreOptions).name,\n\t);\n\tconst limits = new Map<string, Promise<BoundRateLimit<string>>>();\n\t// Every policy counted under this name, by any process: what `reset` forgets.\n\tconst policies = `${name}:policies`;\n\tconst bind = (limit: number, windowMs: number) =>\n\t\tbindRateLimit(\n\t\t\tclient,\n\t\t\tdefineRateLimit({\n\t\t\t\tname: `${name}:${limit}/${windowMs}`,\n\t\t\t\tkey: (key: string) => key,\n\t\t\t\tlimit,\n\t\t\t\tper: windowMs,\n\t\t\t}),\n\t\t);\n\t/** The bound limit of a policy, recorded in `policies` the first time this process counts under it. */\n\tconst limitFor = (policy: Policy) => {\n\t\tconst id = `${policy.limit}/${policy.windowMs}`;\n\t\tlet bound = limits.get(id);\n\t\tif (bound === undefined) {\n\t\t\tconst limit = bind(policy.limit, policy.windowMs);\n\t\t\tconst recorded = client.send('SADD', [policies, id]).then(() => limit);\n\t\t\trecorded.catch(() => {\n\t\t\t\tif (limits.get(id) === recorded) limits.delete(id);\n\t\t\t});\n\t\t\tlimits.set(id, recorded);\n\t\t\tbound = recorded;\n\t\t}\n\t\treturn bound;\n\t};\n\treturn {\n\t\tasync consume(key, policy): Promise<Decision> {\n\t\t\tconst result = await (await limitFor(policy)).consume(key);\n\t\t\treturn {\n\t\t\t\tallowed: result.allowed,\n\t\t\t\tremaining: result.remaining,\n\t\t\t\tresetAfter: result.resetAfter,\n\t\t\t\tretryAfter: result.retryAfter,\n\t\t\t};\n\t\t},\n\t\tasync reset(key) {\n\t\t\tconst ids = (await client.send('SMEMBERS', [policies])) as string[];\n\t\t\tawait Promise.all(\n\t\t\t\tids.map(async (id) => {\n\t\t\t\t\t// Only what `limitFor` writes: anything else in the set is not ours.\n\t\t\t\t\tconst policy = /^(\\d+)\\/(\\d+)$/.exec(id);\n\t\t\t\t\tif (policy === null) return;\n\t\t\t\t\tconst limit = Number(policy[1]);\n\t\t\t\t\tconst windowMs = Number(policy[2]);\n\t\t\t\t\tif (limit < 1 || windowMs < 1) return;\n\t\t\t\t\tawait bind(limit, windowMs).reset(key);\n\t\t\t\t}),\n\t\t\t);\n\t\t},\n\t};\n}\n\n/** A wired limit as a store: it counts by its own rate, the policy the middleware holds is left to its headers. */\nfunction wiredStore(limit: BoundRateLimit<string>): RateLimitStore {\n\treturn {\n\t\tasync consume(key): Promise<Decision> {\n\t\t\tconst result = await limit.consume(key);\n\t\t\treturn {\n\t\t\t\tallowed: result.allowed,\n\t\t\t\tremaining: result.remaining,\n\t\t\t\tresetAfter: result.resetAfter,\n\t\t\t\tretryAfter: result.retryAfter,\n\t\t\t};\n\t\t},\n\t\tasync reset(key) {\n\t\t\tawait limit.reset(key);\n\t\t},\n\t};\n}\n\n/**\n * A bound limit as a store, declaring the rate of its `definition` as its\n * policy. A definition given beside it must hold the same rate.\n */\nfunction policyStore(\n\tlimit: BoundRateLimit<string>,\n\tgiven?: RateLimitDefinition<string>,\n): PolicyStore {\n\tconst { limit: count, per } = limit.definition;\n\tif (given !== undefined && (given.limit !== count || given.per !== per)) {\n\t\tthrow new TypeError(\n\t\t\t`redisStore: the definition \"${given.name}\" (${given.limit} per ${given.per}ms) is not the rate this limit counts by (${count} per ${per}ms)`,\n\t\t);\n\t}\n\treturn { ...wiredStore(limit), policy: { limit: count, windowMs: per } };\n}\n"
|
|
12
12
|
],
|
|
13
|
-
"mappings": "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",
|
|
14
|
-
"debugId": "
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "8102DF4343F3D91E64756E2164756E21",
|
|
15
15
|
"names": []
|
|
16
16
|
}
|
package/dist/store.d.ts
CHANGED
|
@@ -23,37 +23,32 @@ export interface RedisStoreOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function redisStore(target: RedisTarget, options: RedisStoreOptions): RateLimitStore;
|
|
25
25
|
/**
|
|
26
|
-
* The store of a rate limit
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* The store of a rate limit bound by `@nxgt/redis` (0.7 and later), wired by
|
|
27
|
+
* `defineRedis` or by hand: the definition — its `limit`, `per` and `burst` —
|
|
28
|
+
* is the one place. The store declares its `limit` and `per` as its `policy`,
|
|
29
|
+
* so `rateLimit({ store })` takes `limit` and `windowMs` from it and the
|
|
30
|
+
* numbers are written once. The keys are those `@nxgt/redis` writes,
|
|
31
|
+
* `<prefix>:<name>:<key>`, so every consumer of the handle counts together,
|
|
32
|
+
* alxia's routes included.
|
|
30
33
|
*
|
|
31
34
|
* ```ts
|
|
32
35
|
* const api = defineRateLimit({ name: 'api', key: (ip: string) => ip, limit: 100, per: 60_000 });
|
|
33
36
|
* const handle = await openRedis(defineRedis({ uri, prefix: 'shop', limits: { api } }));
|
|
34
|
-
* app.use(rateLimit({
|
|
37
|
+
* app.use(rateLimit({ store: redisStore(handle.limits.api) }));
|
|
35
38
|
* ```
|
|
36
39
|
*
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
39
|
-
*
|
|
40
|
-
* `rateLimit` then reads them from (below). The limit's key must take a
|
|
41
|
-
* string, the one `rateLimit` counts by.
|
|
40
|
+
* A `limit` or `windowMs` given to `rateLimit` too must equal the
|
|
41
|
+
* definition's, or `rateLimit()` throws at declaration. The limit's key must
|
|
42
|
+
* take a string, the one `rateLimit` counts by.
|
|
42
43
|
*/
|
|
43
|
-
export declare function redisStore(limit: BoundRateLimit<string>):
|
|
44
|
+
export declare function redisStore(limit: BoundRateLimit<string>): PolicyStore;
|
|
44
45
|
/**
|
|
45
|
-
* A
|
|
46
|
-
* `limit` and `per` as its `policy`, so `rateLimit({ store })` takes `limit`
|
|
47
|
-
* and `windowMs` from it and the numbers are written once. (`@nxgt/redis`
|
|
48
|
-
* 0.6 does not expose a bound limit's rate, so the definition that wired it
|
|
49
|
-
* is given beside it; a definition of another name than the bound limit's is refused.)
|
|
46
|
+
* A bound rate limit with its definition beside it.
|
|
50
47
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* A `limit` or `windowMs` given to `rateLimit` too must equal the
|
|
56
|
-
* definition's, or `rateLimit()` throws at declaration.
|
|
48
|
+
* @deprecated The bound limit carries its definition since `@nxgt/redis`
|
|
49
|
+
* 0.7: `redisStore(handle.limits.api)` alone is the same store. Given both,
|
|
50
|
+
* the definition's `limit` and `per` must equal the bound limit's, or this
|
|
51
|
+
* throws.
|
|
57
52
|
*/
|
|
58
53
|
export declare function redisStore(limit: BoundRateLimit<string>, definition: RateLimitDefinition<string>): PolicyStore;
|
|
59
54
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGX,WAAW,EACX,cAAc,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,KAAK,cAAc,EAGnB,KAAK,mBAAmB,EACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAErE,MAAM,WAAW,iBAAiB;IACjC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACzB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,iBAAiB,GACxB,cAAc,CAAC;AAClB
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGX,WAAW,EACX,cAAc,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,KAAK,cAAc,EAGnB,KAAK,mBAAmB,EACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAErE,MAAM,WAAW,iBAAiB;IACjC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACzB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,iBAAiB,GACxB,cAAc,CAAC;AAClB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;AACvE;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,EAC7B,UAAU,EAAE,mBAAmB,CAAC,MAAM,CAAC,GACrC,WAAW,CAAC"}
|
package/docs/guide/connecting.md
CHANGED
|
@@ -199,7 +199,7 @@ several is refused with a `TypeError` naming them. Give a bare client,
|
|
|
199
199
|
`handle.clients.<name>`, to the factory that lives on one of them, with the
|
|
200
200
|
prefix in its `name`.
|
|
201
201
|
|
|
202
|
-
**Defined once.** With `@nxgt/redis` 0.
|
|
202
|
+
**Defined once.** With `@nxgt/redis` 0.7 the rate limit and the idempotent
|
|
203
203
|
operation can be wired by `defineRedis` too, and `redisStore` and `idempotency`
|
|
204
204
|
take what it wired: [Defined once, in `defineRedis`](#defined-once-in-defineredis).
|
|
205
205
|
|
|
@@ -229,7 +229,7 @@ const handle = await openRedis(
|
|
|
229
229
|
);
|
|
230
230
|
|
|
231
231
|
const app = alxia()
|
|
232
|
-
.use(rateLimit({ store: redisStore(handle.limits.api
|
|
232
|
+
.use(rateLimit({ store: redisStore(handle.limits.api) })) // shop:api:<address>, 100 per 60 s from `api`
|
|
233
233
|
.use(idempotency(handle.idempotency.orders)) // shop:orders:<route>:<scope>:<key>
|
|
234
234
|
.post('/orders', ({ reply }) => reply(201, { id: crypto.randomUUID() }));
|
|
235
235
|
```
|
|
@@ -247,10 +247,10 @@ What to know:
|
|
|
247
247
|
options (`required`, `scope`, `wait`, `methods`, `header`) but not `name`,
|
|
248
248
|
`ttl` or `lease`: the types refuse them, and a script that passes them gets a
|
|
249
249
|
`TypeError`.
|
|
250
|
-
- **The rate is the definition's, and `rateLimit`
|
|
251
|
-
|
|
252
|
-
`
|
|
253
|
-
|
|
250
|
+
- **The rate is the definition's, and `rateLimit` reads it.** The bound limit
|
|
251
|
+
carries its `definition`, so the store declares `limit` and `per` as its
|
|
252
|
+
`policy`: `rateLimit({ store })` needs no numbers, and one it is given that
|
|
253
|
+
differs throws at declaration. ([The rate, written once](rate-limits.md#the-rate-written-once).)
|
|
254
254
|
- **The limit counts by a string.** `rateLimit` counts by the string its `key`
|
|
255
255
|
returns, so the wired limit's key must take one, as `key: (ip: string) => ip`.
|
|
256
256
|
`redisStore(handle.limits.byIp)` for a limit keyed by `{ ip }` does not
|
|
@@ -268,9 +268,9 @@ What to know:
|
|
|
268
268
|
does not: the by-name store counts under `shop:api:<limit>/<windowMs>:<key>`,
|
|
269
269
|
the wired one under `shop:api:<key>`, so counts restart:
|
|
270
270
|
[troubleshooting](../troubleshooting.md#counts-restart-after-moving-a-rate-limit-to-the-wired-form).
|
|
271
|
-
- **Both forms stay**,
|
|
272
|
-
|
|
273
|
-
|
|
271
|
+
- **Both forms stay**, on `@nxgt/redis` 0.7: the peer is `^0.7.0`, because the
|
|
272
|
+
bound limit's `definition`, which the single-argument `redisStore` reads its
|
|
273
|
+
rate from, is 0.7's.
|
|
274
274
|
|
|
275
275
|
## Naming keys
|
|
276
276
|
|
|
@@ -55,27 +55,32 @@ A limit wired by `defineRedis`, `redisStore(handle.limits.api)`, takes no
|
|
|
55
55
|
|
|
56
56
|
### The rate, written once
|
|
57
57
|
|
|
58
|
-
`@nxgt/redis` 0.
|
|
59
|
-
bound limit alone
|
|
60
|
-
`windowMs` repeat the definition for the headers. Give the definition as the
|
|
61
|
-
second argument and the store declares it as its `policy`:
|
|
58
|
+
A bound limit carries its `definition` (`@nxgt/redis` 0.7), so the store of
|
|
59
|
+
the bound limit alone declares it as its `policy`:
|
|
62
60
|
|
|
63
61
|
```ts
|
|
64
62
|
const api = defineRateLimit({ name: 'api', key: (ip: string) => ip, limit: 100, per: 60_000 });
|
|
65
63
|
const handle = await openRedis(defineRedis({ uri, prefix: 'shop', limits: { api } }));
|
|
66
64
|
|
|
67
|
-
app.use(rateLimit({ store: redisStore(handle.limits.api
|
|
65
|
+
app.use(rateLimit({ store: redisStore(handle.limits.api) })); // 100 per 60 s
|
|
68
66
|
```
|
|
69
67
|
|
|
70
68
|
`rateLimit` reads `limit` (the definition's `limit`) and `windowMs` (its
|
|
71
69
|
`per`) from the policy: the 429 and the `RateLimit-Limit`, `-Remaining`,
|
|
72
70
|
`-Reset` and `-Policy` headers come from the definition, and one place holds
|
|
73
71
|
the numbers. A `limit` or `windowMs` given too must equal them, or
|
|
74
|
-
`rateLimit()` throws at declaration. A
|
|
75
|
-
|
|
72
|
+
`rateLimit()` throws at declaration. A limit bound by hand,
|
|
73
|
+
`redisStore(bindRateLimit(client, api))`, does the same. (A wired
|
|
74
|
+
limit's `definition.name` is prefixed: only `limit` and `per` are read.)
|
|
76
75
|
The definition's `burst`, when it sets one, still governs how many requests
|
|
77
76
|
pass at once; the policy states `limit` per `per`.
|
|
78
77
|
|
|
78
|
+
**The two-argument form is deprecated.** `redisStore(handle.limits.api, api)`
|
|
79
|
+
was the way before 0.7 exposed the rate, and still works: given both, it
|
|
80
|
+
compares the definition's `limit` and `per` with the bound limit's, and a
|
|
81
|
+
mismatch is a `TypeError` (the name is not compared: a wired one is prefixed).
|
|
82
|
+
Drop the second argument.
|
|
83
|
+
|
|
79
84
|
## What changes with Redis
|
|
80
85
|
|
|
81
86
|
- **Every process counts together.** Behind a load balancer, a client gets
|
package/docs/roadmap.md
CHANGED
|
@@ -28,6 +28,20 @@ Nothing scheduled yet.
|
|
|
28
28
|
|
|
29
29
|
## Shipped
|
|
30
30
|
|
|
31
|
+
### 0.5.0: the definition read from the bound limit
|
|
32
|
+
|
|
33
|
+
- **`redisStore(handle.limits.api)` alone carries the policy.** On
|
|
34
|
+
`@nxgt/redis` 0.7 a bound limit exposes its `definition`, so the store reads
|
|
35
|
+
`limit` and `per` from it: `rateLimit({ store: redisStore(handle.limits.api) })`
|
|
36
|
+
needs no numbers, wired or bound by hand. The two-argument form,
|
|
37
|
+
`redisStore(handle.limits.api, api)`, is deprecated; it compares `limit` and
|
|
38
|
+
`per` (not the prefixed name) and throws on a mismatch.
|
|
39
|
+
- **`idempotency(handle.idempotency.orders)` reads its definition.** It names
|
|
40
|
+
itself from `definition.name` in the unscoped-request warning, where it
|
|
41
|
+
rebuilt the name from a key.
|
|
42
|
+
- **The peer is `@nxgt/redis` `^0.7.0`.** The single-argument form has no rate
|
|
43
|
+
to read from an older bound limit, so it cannot degrade gracefully.
|
|
44
|
+
|
|
31
45
|
### 0.4.0: the rate, written once
|
|
32
46
|
|
|
33
47
|
- **A rate limit that reads its policy from the store.**
|
|
@@ -42,7 +56,7 @@ Nothing scheduled yet.
|
|
|
42
56
|
|
|
43
57
|
### 0.3.0, continued: wired guards
|
|
44
58
|
|
|
45
|
-
- **On `@nxgt/redis` 0.6.** The peer range is `^0.5.0 || ^0.6.0`: the new forms
|
|
59
|
+
- **On `@nxgt/redis` 0.6 (until 0.5.0).** The peer range is `^0.5.0 || ^0.6.0`: the new forms
|
|
46
60
|
need no more than 0.5's types, and 0.6 only adds `handle.limits` and
|
|
47
61
|
`handle.idempotency` to wire them.
|
|
48
62
|
- **A rate limit and an idempotency defined once.** `redisStore(handle.limits.api)`
|
package/docs/troubleshooting.md
CHANGED
|
@@ -21,7 +21,7 @@ each through; its own refusals, a handle of several instances, a wired guard giv
|
|
|
21
21
|
- [`TypeError: defineIdempotency: "…" has a lease of …; it is a whole number of milliseconds, and must be at least 1`](#typeerror-defineidempotency--has-a-lease-of--it-is-a-whole-number-of-milliseconds-and-must-be-at-least-1)
|
|
22
22
|
- [`TypeError: defineIdempotency: an idempotent operation needs a name, for its keys`](#typeerror-defineidempotency-an-idempotent-operation-needs-a-name-for-its-keys)
|
|
23
23
|
- [`RedisError: Connection closed`](#rediserror-connection-closed)
|
|
24
|
-
- [`TypeError: redisStore: the definition "…" is not the
|
|
24
|
+
- [`TypeError: redisStore: the definition "…" is not the rate this limit counts by`](#typeerror-redisstore-the-definition--is-not-the-rate-this-limit-counts-by)
|
|
25
25
|
- [`TypeError: connectRedis: this URI is already connected with other options. Pass the same options everywhere, or close the first connection.`](#typeerror-connectredis-this-uri-is-already-connected-with-other-options-pass-the-same-options-everywhere-or-close-the-first-connection)
|
|
26
26
|
|
|
27
27
|
- [`TypeError: @alxia/redis: this @nxgt/redis handle wires N Redis instances (…), and one is needed.`](#typeerror-alxiaredis-this-nxgtredis-handle-wires-n-redis-instances--and-one-is-needed)
|
|
@@ -263,25 +263,24 @@ defineRedis({ uri, prefix: 'shop', caches: { users } });
|
|
|
263
263
|
defineRedis({ uri, prefix: 'shop', limits: { api } });
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
### `TypeError: redisStore: the definition "…" is not the
|
|
266
|
+
### `TypeError: redisStore: the definition "…" is not the rate this limit counts by`
|
|
267
267
|
|
|
268
|
-
**When:** `redisStore(handle.limits.api, other)` is given a
|
|
269
|
-
`
|
|
270
|
-
key the limit counts under:
|
|
268
|
+
**When:** the deprecated `redisStore(handle.limits.api, other)` is given a
|
|
269
|
+
definition whose `limit` or `per` is not the bound limit's:
|
|
271
270
|
|
|
272
271
|
```text
|
|
273
|
-
TypeError: redisStore: the definition "
|
|
272
|
+
TypeError: redisStore: the definition "api" (3 per 60000ms) is not the rate this limit counts by (3 per 30000ms)
|
|
274
273
|
```
|
|
275
274
|
|
|
276
|
-
**Why:** the definition
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
**Why:** the definition would give `rateLimit` its `limit` and `windowMs`, and
|
|
276
|
+
one of another rate would write headers for a rate Redis does not count by.
|
|
277
|
+
The comparison is on the rate, not on the name: a wired limit's
|
|
278
|
+
`definition.name` is prefixed.
|
|
280
279
|
|
|
281
|
-
**Fix:**
|
|
280
|
+
**Fix:** drop the second argument, the bound limit carries its definition:
|
|
282
281
|
|
|
283
282
|
```ts
|
|
284
|
-
app.use(rateLimit({ store: redisStore(handle.limits.api
|
|
283
|
+
app.use(rateLimit({ store: redisStore(handle.limits.api) }));
|
|
285
284
|
```
|
|
286
285
|
|
|
287
286
|
### `TypeError: defineRedis: instance "default" wires the cache "users" and the rate limit "login" under one name, "user". They would share every key in Redis. Give one of them a name of its own.`
|
|
@@ -762,15 +761,11 @@ themselves.
|
|
|
762
761
|
|
|
763
762
|
**Fix:** none is needed but the wait, within `burst × per ÷ limit` of the
|
|
764
763
|
limit; switch at a quiet moment. `redisStore(handle.limits.api)` counts by the
|
|
765
|
-
definition's rate
|
|
766
|
-
|
|
767
|
-
rate than the limit enforces, give the definition as the second argument,
|
|
768
|
-
`redisStore(handle.limits.api, api)`, and leave `limit` and `windowMs` out of
|
|
769
|
-
`rateLimit`: the store declares the definition's rate and the headers come
|
|
770
|
-
from it. A `limit` or `windowMs` that differs from it throws
|
|
764
|
+
definition's rate and declares it as its policy: leave `limit` and `windowMs`
|
|
765
|
+
out of `rateLimit` and the headers come from it. A `limit` or `windowMs` that differs from it throws
|
|
771
766
|
[`rateLimit: limit … differs from the store's policy of …`](https://github.com/softistx/alxia/blob/develop/packages/rate-limit/docs/troubleshooting.md#typeerror-ratelimit-limit--differs-from-the-stores-policy-of-)
|
|
772
|
-
at startup;
|
|
773
|
-
`redisStore: the definition "…" is not the
|
|
767
|
+
at startup; the deprecated two-argument form with a definition of another rate
|
|
768
|
+
throws `redisStore: the definition "…" is not the rate this limit counts by`.
|
|
774
769
|
|
|
775
770
|
### The handle is closed while something still uses it
|
|
776
771
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alxia/redis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Redis for alxia on @nxgt/redis: a rate-limit store every process shares, idempotent routes, caches and locks in the context",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,18 +41,18 @@
|
|
|
41
41
|
]
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@alxia/cache": "^0.3.
|
|
45
|
-
"@alxia/core": "^0.
|
|
46
|
-
"@alxia/rate-limit": "^0.4.
|
|
47
|
-
"@nxgt/redis": "^0.
|
|
44
|
+
"@alxia/cache": "^0.3.1",
|
|
45
|
+
"@alxia/core": "^0.6.0",
|
|
46
|
+
"@alxia/rate-limit": "^0.4.2",
|
|
47
|
+
"@nxgt/redis": "^0.7.0",
|
|
48
48
|
"@types/bun": "^1.4.2",
|
|
49
49
|
"zod": "^4.6.5"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@alxia/cache": "^0.3.
|
|
53
|
-
"@alxia/core": "^0.
|
|
54
|
-
"@alxia/rate-limit": "^0.4.
|
|
55
|
-
"@nxgt/redis": "^0.
|
|
52
|
+
"@alxia/cache": "^0.3.1",
|
|
53
|
+
"@alxia/core": "^0.6.0",
|
|
54
|
+
"@alxia/rate-limit": "^0.4.2",
|
|
55
|
+
"@nxgt/redis": "^0.7.0",
|
|
56
56
|
"typescript": "^6.0.3 || ^7.0.0",
|
|
57
57
|
"zod": "^4.6.5"
|
|
58
58
|
},
|