Skip to content

Commit eb0de6f

Browse files
committed
Fix documentation errors and type soundness issues across packages
- cron: README claimed AND semantics for dom+dow, code uses POSIX OR. Fixed README. Added domRestricted/dowRestricted to documented interface. - limiter: README showed adapter as optional, it's required. Fixed all examples to pass memoryAdapter() explicitly. - key: README showed wrong output for objects (missing quotes around keys). - cache/queue: Added NonUndefined constraint ({} | null) to value/item type parameters so undefined can't be used as a value type, which would conflict with the undefined-means-miss/end-of-stream sentinel. - retry: Fixed event listener leak in delay() — abort listener was never removed on normal timer completion. - router: Throw on conflicting param names at the same trie position instead of silently ignoring the second registration. - memo: Added missing License section to README. - example: Reformatted to match project style (oxfmt).
1 parent 7503533 commit eb0de6f

13 files changed

Lines changed: 119 additions & 50 deletions

File tree

example/src/env.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
declare module '*.css' {
2-
const content: string;
3-
export default content;
1+
declare module "*.css" {
2+
const content: string
3+
export default content
44
}

example/src/locales/en.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,21 @@ function timeAgo(ms: number): string {
88
const months = Math.floor(days / 30)
99
const years = Math.floor(days / 365)
1010

11-
if (years > 0) {return `${years} year${years > 1 ? "s" : ""} ago`}
12-
if (months > 0) {return `${months} month${months > 1 ? "s" : ""} ago`}
13-
if (days > 0) {return `${days} day${days > 1 ? "s" : ""} ago`}
14-
if (hours > 0) {return `${hours} hour${hours > 1 ? "s" : ""} ago`}
15-
if (minutes > 0)
16-
{return `${minutes} minute${minutes > 1 ? "s" : ""} ago`}
11+
if (years > 0) {
12+
return `${years} year${years > 1 ? "s" : ""} ago`
13+
}
14+
if (months > 0) {
15+
return `${months} month${months > 1 ? "s" : ""} ago`
16+
}
17+
if (days > 0) {
18+
return `${days} day${days > 1 ? "s" : ""} ago`
19+
}
20+
if (hours > 0) {
21+
return `${hours} hour${hours > 1 ? "s" : ""} ago`
22+
}
23+
if (minutes > 0) {
24+
return `${minutes} minute${minutes > 1 ? "s" : ""} ago`
25+
}
1726
return "just now"
1827
}
1928

example/src/locales/es.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@ function tiempoAtras(ms: number): string {
88
const months = Math.floor(days / 30)
99
const years = Math.floor(days / 365)
1010

11-
if (years > 0) {return `hace ${years} año${years > 1 ? "s" : ""}`}
12-
if (months > 0)
13-
{return `hace ${months} mes${months > 1 ? "es" : ""}`}
14-
if (days > 0) {return `hace ${days} día${days > 1 ? "s" : ""}`}
15-
if (hours > 0) {return `hace ${hours} hora${hours > 1 ? "s" : ""}`}
16-
if (minutes > 0)
17-
{return `hace ${minutes} minuto${minutes > 1 ? "s" : ""}`}
11+
if (years > 0) {
12+
return `hace ${years} año${years > 1 ? "s" : ""}`
13+
}
14+
if (months > 0) {
15+
return `hace ${months} mes${months > 1 ? "es" : ""}`
16+
}
17+
if (days > 0) {
18+
return `hace ${days} día${days > 1 ? "s" : ""}`
19+
}
20+
if (hours > 0) {
21+
return `hace ${hours} hora${hours > 1 ? "s" : ""}`
22+
}
23+
if (minutes > 0) {
24+
return `hace ${minutes} minuto${minutes > 1 ? "s" : ""}`
25+
}
1826
return "justo ahora"
1927
}
2028

@@ -30,8 +38,7 @@ export const es: AppDict = {
3038
send: "Enviar",
3139
contactSuccess: "¡Tu mensaje ha sido enviado con éxito!",
3240
notFound: "Página no encontrada",
33-
tooManyRequests:
34-
"Demasiadas solicitudes. Inténtalo de nuevo más tarde.",
41+
tooManyRequests: "Demasiadas solicitudes. Inténtalo de nuevo más tarde.",
3542
relativeTime: (ms: number): string => tiempoAtras(ms),
3643
postedBy: (author: string): string => `Por ${author}`,
3744
}

packages/cache/src/index.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ function sleep(ms: number): Promise<void> {
1010
}
1111

1212
/** Creates a cache adapter that records calls for assertion. */
13-
function spyAdapter<K extends string, V>(): CacheAdapter<K, V> & {
13+
// oxlint-disable-next-line typescript-eslint/ban-types
14+
function spyAdapter<K extends string, V extends {} | null>(): CacheAdapter<
15+
K,
16+
V
17+
> & {
1418
calls: { method: string; args: unknown[] }[]
1519
store: Map<K, V>
1620
} {

packages/cache/src/index.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
export interface CacheAdapter<K extends string, V> extends AsyncDisposable {
1+
/** Excludes `undefined` from value types so `get()` can use `undefined` as the miss sentinel. */
2+
type NonUndefined = {} | null // oxlint-disable-line typescript-eslint/ban-types
3+
4+
export interface CacheAdapter<
5+
K extends string,
6+
V extends NonUndefined,
7+
> extends AsyncDisposable {
28
get(key: K): Promise<V | undefined>
39
set(key: K, value: V, ttl?: number): Promise<void>
410
delete(key: K): Promise<void>
511
}
612

7-
export interface Cache<K extends string, V> extends AsyncDisposable {
13+
export interface Cache<
14+
K extends string,
15+
V extends NonUndefined,
16+
> extends AsyncDisposable {
817
get(key: K): Promise<V | undefined>
918
set(key: K, value: V, ttl?: number): Promise<void>
1019
delete(key: K): Promise<void>
@@ -16,7 +25,7 @@ interface Entry<V> {
1625
expiresAt: number | undefined
1726
}
1827

19-
export function memoryAdapter<K extends string, V>(
28+
export function memoryAdapter<K extends string, V extends NonUndefined>(
2029
sweepIntervalMs = 30_000,
2130
): CacheAdapter<K, V> {
2231
const store = new Map<K, Entry<V>>()
@@ -71,7 +80,7 @@ export function memoryAdapter<K extends string, V>(
7180
}
7281
}
7382

74-
export function createCache<K extends string, V>(
83+
export function createCache<K extends string, V extends NonUndefined>(
7584
adapter: CacheAdapter<K, V>,
7685
defaultTtl?: number,
7786
): Cache<K, V> {

packages/cron/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ Named values: `JAN`-`DEC` for months, `SUN`-`SAT` for days of week.
5454

5555
Day-of-week `7` is normalized to `0` (both mean Sunday). Expressions like `6-7`, `6,0`, `0,6`, and `7,6` are all equivalent (Saturday and Sunday).
5656

57-
### Day-of-month and day-of-week (AND semantics)
57+
### Day-of-month and day-of-week (OR semantics)
5858

59-
When both day-of-month and day-of-week are specified, a date must match **both** constraints. For example, `0 0 15 * 3` matches only when the 15th falls on a Wednesday.
59+
When both day-of-month and day-of-week are restricted (neither is `*`), a date matches if it satisfies **either** condition (POSIX OR semantics). For example, `0 0 15 * 3` matches any 15th of the month **or** any Wednesday.
6060

6161
## API
6262

@@ -80,6 +80,8 @@ interface CronExpression {
8080
readonly daysOfMonth: ReadonlySet<number>
8181
readonly months: ReadonlySet<number>
8282
readonly daysOfWeek: ReadonlySet<number>
83+
readonly domRestricted: boolean
84+
readonly dowRestricted: boolean
8385
}
8486
```
8587

packages/key/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ stableKey(null) // "N"
2020
stableKey(undefined) // "U"
2121
stableKey(42n) // "b42"
2222
stableKey([1, "a"]) // '[n1,s"a"]'
23-
stableKey({ b: 2, a: 1 }) // "{a:n1,b:n2}" (sorted keys)
23+
stableKey({ b: 2, a: 1 }) // '{"a":n1,"b":n2}' (sorted keys)
2424
```
2525

2626
## Determinism

packages/limiter/README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ npm install @suckless/limiter
1111
## Usage
1212

1313
```ts
14-
import { createLimiter } from "@suckless/limiter"
14+
import { createLimiter, memoryAdapter } from "@suckless/limiter"
1515

16-
const limiter = createLimiter(100, 60_000) // 100 requests per minute
16+
const limiter = createLimiter(100, 60_000, memoryAdapter()) // 100 requests per minute
1717

1818
const { ok, remaining, retryAfter } = await limiter.check("user:123")
1919
if (!ok) {
@@ -24,11 +24,11 @@ if (!ok) {
2424
## With middleware
2525

2626
```ts
27-
import { createLimiter } from "@suckless/limiter"
27+
import { createLimiter, memoryAdapter } from "@suckless/limiter"
2828
import { parse } from "@suckless/duration"
2929
import type { Middleware } from "@suckless/middleware"
3030

31-
const limiter = createLimiter(100, parse("1m"))
31+
const limiter = createLimiter(100, parse("1m"), memoryAdapter())
3232

3333
const rateLimit: Middleware<Request, Response> = async (req, next) => {
3434
const ip = req.headers.get("x-forwarded-for") ?? "unknown"
@@ -81,11 +81,11 @@ This allows short bursts up to `max` while enforcing the average rate over the w
8181

8282
## API
8383

84-
### `createLimiter(max, window, adapter?): Limiter`
84+
### `createLimiter(max, window, adapter): Limiter`
8585

8686
- `max` — maximum tokens (requests) per window
8787
- `window` — time window in milliseconds
88-
- `adapter`optional `LimiterAdapter` (defaults to in-memory)
88+
- `adapter`a `LimiterAdapter` storage backend
8989

9090
### `limiter.check(key): Promise<CheckResult>`
9191

@@ -101,14 +101,14 @@ Clears the bucket for a key, restoring it to full capacity.
101101

102102
### `memoryAdapter(sweepIntervalMs?): LimiterAdapter`
103103

104-
In-memory adapter with automatic stale-bucket sweeping. Used by default when no adapter is provided.
104+
In-memory adapter with automatic stale-bucket sweeping.
105105

106106
### Cleanup
107107

108108
Stale buckets are swept automatically. The limiter implements `AsyncDisposable` for cleanup:
109109

110110
```ts
111-
await using limiter = createLimiter(100, 60_000)
111+
await using limiter = createLimiter(100, 60_000, memoryAdapter())
112112
```
113113

114114
## License

packages/memo/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ const memoized = memo(fn, (...args) => stableKey(args))
4848
- **options.max** — optional; caps cache size with LRU eviction (must be a positive integer)
4949

5050
Returns a function with the same signature as `fn`.
51+
52+
## License
53+
54+
MIT

packages/queue/src/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1+
/** Excludes `undefined` from item types so `pull()` can use `undefined` as the end-of-stream sentinel. */
2+
type NonUndefined = {} | null // oxlint-disable-line typescript-eslint/ban-types
3+
14
/** Storage backend for queue items. */
2-
export interface QueueAdapter<T> extends AsyncDisposable {
5+
export interface QueueAdapter<T extends NonUndefined> extends AsyncDisposable {
36
push(item: T): Promise<void>
47
pull(signal: AbortSignal): Promise<T | undefined>
58
}
69

710
/** Producer/consumer queue with pluggable storage. */
8-
export interface Queue<T> extends AsyncDisposable {
11+
export interface Queue<T extends NonUndefined> extends AsyncDisposable {
912
push(item: T): Promise<void>
1013
drain(): Promise<void>
1114
readonly running: number
1215
}
1316

1417
/** In-memory FIFO queue adapter with blocking pull. */
15-
export function memoryAdapter<T>(): QueueAdapter<T> {
18+
export function memoryAdapter<T extends NonUndefined>(): QueueAdapter<T> {
1619
const buffer: T[] = []
1720
const waiters: ((item: T | undefined) => void)[] = []
1821

@@ -63,7 +66,7 @@ export function memoryAdapter<T>(): QueueAdapter<T> {
6366
}
6467

6568
/** Create a producer/consumer queue with pluggable storage. */
66-
export function createQueue<T>(
69+
export function createQueue<T extends NonUndefined>(
6770
handler: (item: T) => void | Promise<void>,
6871
adapter: QueueAdapter<T>,
6972
options?: {

0 commit comments

Comments
 (0)