@workkit/hono 0.0.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 ADDED
@@ -0,0 +1,87 @@
1
+ # @workkit/hono
2
+
3
+ > Hono middleware for env validation, error handling, rate limiting, and caching
4
+
5
+ [![npm](https://img.shields.io/npm/v/@workkit/hono)](https://www.npmjs.com/package/@workkit/hono)
6
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@workkit/hono)](https://bundlephobia.com/package/@workkit/hono)
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ bun add @workkit/hono hono
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ### Before (manual Hono setup)
17
+
18
+ ```ts
19
+ import { Hono } from "hono"
20
+
21
+ const app = new Hono()
22
+
23
+ app.use("*", async (c, next) => {
24
+ // Manual env validation on every request
25
+ if (!c.env.API_KEY) return c.text("Missing API_KEY", 500)
26
+ await next()
27
+ })
28
+
29
+ app.onError((err, c) => {
30
+ // Generic error handling — lose structured error info
31
+ return c.json({ error: err.message }, 500)
32
+ })
33
+ ```
34
+
35
+ ### After (workkit hono)
36
+
37
+ ```ts
38
+ import { Hono } from "hono"
39
+ import { workkit, workkitErrorHandler, rateLimit, cacheResponse } from "@workkit/hono"
40
+ import { z } from "zod"
41
+
42
+ const app = new Hono()
43
+
44
+ // Validate env on first request — typed and cached
45
+ app.use(workkit({ env: { API_KEY: z.string().min(1), DB: z.any() } }))
46
+
47
+ // Structured error handling — WorkkitErrors become proper JSON responses
48
+ app.onError(workkitErrorHandler())
49
+
50
+ // KV-backed rate limiting
51
+ app.use("/api/*", rateLimit({ limit: 100, window: "1m" }))
52
+
53
+ // Response caching
54
+ app.use("/api/public/*", cacheResponse({ ttl: 300 }))
55
+
56
+ app.get("/", (c) => {
57
+ const env = c.get("workkit:env") // fully typed
58
+ return c.json({ key: env.API_KEY })
59
+ })
60
+ ```
61
+
62
+ ## API
63
+
64
+ ### Middleware
65
+
66
+ - **`workkit(options)`** — Validate environment bindings. Stores typed env in `c.get("workkit:env")`.
67
+
68
+ ### Error Handling
69
+
70
+ - **`workkitErrorHandler(options?)`** — Convert `WorkkitError` instances to structured JSON responses with proper status codes.
71
+
72
+ ### Rate Limiting
73
+
74
+ - **`rateLimit(options)`** — KV-backed rate limiting middleware. Options: `limit`, `window`, `keyFn?`
75
+ - **`fixedWindow(options)`** — Fixed window rate limiter (lower-level)
76
+
77
+ ### Caching
78
+
79
+ - **`cacheResponse(options)`** — Cache responses using the Cache API. Options: `ttl`, `vary?`
80
+
81
+ ### Helpers
82
+
83
+ - **`getEnv(c)`** — Get validated env from Hono context (shorthand for `c.get("workkit:env")`)
84
+
85
+ ## License
86
+
87
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,209 @@
1
+ var import_node_module = require("node:module");
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+
30
+ // src/index.ts
31
+ var exports_src = {};
32
+ __export(exports_src, {
33
+ workkitErrorHandler: () => workkitErrorHandler,
34
+ workkit: () => workkit,
35
+ rateLimit: () => rateLimit,
36
+ parseDuration: () => parseDuration,
37
+ getEnv: () => getEnv,
38
+ fixedWindow: () => fixedWindow,
39
+ cacheResponse: () => cacheResponse
40
+ });
41
+ module.exports = __toCommonJS(exports_src);
42
+
43
+ // src/middleware.ts
44
+ var import_env = require("@workkit/env");
45
+ function workkit(options) {
46
+ let cachedEnv = null;
47
+ return async (c, next) => {
48
+ if (!cachedEnv) {
49
+ const rawEnv = c.env;
50
+ cachedEnv = await import_env.parseEnv(rawEnv, options.env);
51
+ }
52
+ c.set("workkit:env", cachedEnv);
53
+ c.set("workkit:envValidated", true);
54
+ await next();
55
+ };
56
+ }
57
+ // src/error-handler.ts
58
+ var import_errors = require("@workkit/errors");
59
+ function workkitErrorHandler(options = {}) {
60
+ const { includeStack = false, onError } = options;
61
+ return async (err, c) => {
62
+ if (onError) {
63
+ try {
64
+ await onError(err, c);
65
+ } catch {}
66
+ }
67
+ if (import_errors.isWorkkitError(err)) {
68
+ return workkitErrorToResponse(err, includeStack);
69
+ }
70
+ const wrapped = new import_errors.InternalError(err instanceof Error ? err.message : "An unexpected error occurred", { cause: err });
71
+ return workkitErrorToResponse(wrapped, includeStack);
72
+ };
73
+ }
74
+ function workkitErrorToResponse(error, includeStack) {
75
+ const body = {
76
+ error: {
77
+ code: error.code,
78
+ message: error.message,
79
+ statusCode: error.statusCode
80
+ }
81
+ };
82
+ if ("issues" in error && Array.isArray(error.issues)) {
83
+ body.error.issues = error.issues;
84
+ }
85
+ if (includeStack && error.stack) {
86
+ body.error.stack = error.stack;
87
+ }
88
+ const headers = {
89
+ "Content-Type": "application/json"
90
+ };
91
+ if (error instanceof import_errors.RateLimitError && error.retryAfterMs) {
92
+ headers["Retry-After"] = String(Math.ceil(error.retryAfterMs / 1000));
93
+ }
94
+ return new Response(JSON.stringify(body), {
95
+ status: error.statusCode,
96
+ headers
97
+ });
98
+ }
99
+ // src/rate-limit.ts
100
+ var import_errors2 = require("@workkit/errors");
101
+ function rateLimit(options) {
102
+ const { limiter, keyFn, onRateLimited } = options;
103
+ return async (c, next) => {
104
+ const key = await keyFn(c);
105
+ const result = await limiter.check(key);
106
+ c.header("X-RateLimit-Limit", String(result.remaining + (result.allowed ? 0 : 1)));
107
+ c.header("X-RateLimit-Remaining", String(result.remaining));
108
+ c.header("X-RateLimit-Reset", String(Math.ceil(result.resetAt / 1000)));
109
+ if (!result.allowed) {
110
+ if (onRateLimited) {
111
+ return onRateLimited(c, result);
112
+ }
113
+ const retryAfterMs = result.resetAt - Date.now();
114
+ throw new import_errors2.RateLimitError("Rate limit exceeded", retryAfterMs > 0 ? retryAfterMs : undefined);
115
+ }
116
+ await next();
117
+ };
118
+ }
119
+ function parseDuration(duration) {
120
+ const match = duration.match(/^(\d+)(s|m|h|d)$/);
121
+ if (!match) {
122
+ throw new Error(`Invalid duration format: "${duration}". Use e.g. '1m', '5m', '1h', '1d'.`);
123
+ }
124
+ const value = Number.parseInt(match[1], 10);
125
+ const unit = match[2];
126
+ switch (unit) {
127
+ case "s":
128
+ return value * 1000;
129
+ case "m":
130
+ return value * 60 * 1000;
131
+ case "h":
132
+ return value * 60 * 60 * 1000;
133
+ case "d":
134
+ return value * 24 * 60 * 60 * 1000;
135
+ default:
136
+ throw new Error(`Unknown duration unit: "${unit}"`);
137
+ }
138
+ }
139
+ function fixedWindow(options) {
140
+ const { namespace, limit, window: windowStr, prefix = "rl:" } = options;
141
+ const windowMs = parseDuration(windowStr);
142
+ return {
143
+ async check(key) {
144
+ const now = Date.now();
145
+ const windowStart = Math.floor(now / windowMs) * windowMs;
146
+ const resetAt = windowStart + windowMs;
147
+ const kvKey = `${prefix}${key}:${windowStart}`;
148
+ const current = await namespace.get(kvKey);
149
+ const count = current ? Number.parseInt(current, 10) : 0;
150
+ if (count >= limit) {
151
+ return { allowed: false, remaining: 0, resetAt };
152
+ }
153
+ const ttlSeconds = Math.ceil(windowMs / 1000);
154
+ await namespace.put(kvKey, String(count + 1), {
155
+ expirationTtl: ttlSeconds
156
+ });
157
+ return { allowed: true, remaining: limit - count - 1, resetAt };
158
+ }
159
+ };
160
+ }
161
+ // src/cache.ts
162
+ function cacheResponse(options) {
163
+ const { ttl, keyFn, methods = ["GET"] } = options;
164
+ return async (c, next) => {
165
+ if (!methods.includes(c.req.method)) {
166
+ await next();
167
+ return;
168
+ }
169
+ const cacheKey = keyFn ? keyFn(c) : c.req.url;
170
+ const cacheRequest = new Request(cacheKey);
171
+ const cache = options.cache ?? (typeof caches !== "undefined" ? caches.default : null);
172
+ if (!cache) {
173
+ await next();
174
+ return;
175
+ }
176
+ const cached = await cache.match(cacheRequest);
177
+ if (cached) {
178
+ return cached;
179
+ }
180
+ await next();
181
+ const response = c.res;
182
+ if (response.status >= 200 && response.status < 300) {
183
+ const cloned = response.clone();
184
+ const cachedResponse = new Response(cloned.body, {
185
+ status: cloned.status,
186
+ statusText: cloned.statusText,
187
+ headers: new Headers(cloned.headers)
188
+ });
189
+ cachedResponse.headers.set("Cache-Control", `s-maxage=${ttl}`);
190
+ const putPromise = cache.put(cacheRequest, cachedResponse);
191
+ try {
192
+ c.executionCtx.waitUntil(putPromise);
193
+ } catch {
194
+ await putPromise;
195
+ }
196
+ }
197
+ };
198
+ }
199
+ // src/helpers.ts
200
+ function getEnv(c) {
201
+ const validated = c.get("workkit:envValidated");
202
+ if (!validated) {
203
+ throw new Error("workkit:env is not available. Did you forget to add the workkit() middleware?");
204
+ }
205
+ return c.get("workkit:env");
206
+ }
207
+
208
+ //# debugId=4C2D147FF9900DA464756E2164756E21
209
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,186 @@
1
+ import { EnvSchema as EnvSchema2 } from "@workkit/env";
2
+ import { MiddlewareHandler } from "hono";
3
+ import { EnvSchema, InferEnv } from "@workkit/env";
4
+ import { Context, Env } from "hono";
5
+ /**
6
+ * Options for the workkit() middleware.
7
+ */
8
+ interface WorkkitOptions<T extends EnvSchema> {
9
+ /** Environment schema to validate against on first request */
10
+ env: T;
11
+ }
12
+ /**
13
+ * Options for the workkitErrorHandler.
14
+ */
15
+ interface ErrorHandlerOptions {
16
+ /** Include stack trace in error response (never in production) */
17
+ includeStack?: boolean;
18
+ /** Custom error callback for logging/reporting */
19
+ onError?: (err: Error, c: Context) => void | Promise<void>;
20
+ }
21
+ /**
22
+ * A rate limiter instance that checks whether a key is allowed.
23
+ */
24
+ interface RateLimiter {
25
+ /** Check if the key is allowed. Returns { allowed, remaining, resetAt } */
26
+ check(key: string): Promise<RateLimitResult>;
27
+ }
28
+ /**
29
+ * Result of a rate limit check.
30
+ */
31
+ interface RateLimitResult {
32
+ /** Whether the request is allowed */
33
+ allowed: boolean;
34
+ /** Remaining requests in the current window */
35
+ remaining: number;
36
+ /** When the window resets (ms since epoch) */
37
+ resetAt: number;
38
+ }
39
+ /**
40
+ * Options for the rateLimit middleware.
41
+ */
42
+ interface RateLimitOptions {
43
+ /** The rate limiter implementation */
44
+ limiter: RateLimiter;
45
+ /** Function to extract the rate limit key from context (e.g., IP address) */
46
+ keyFn: (c: Context) => string | Promise<string>;
47
+ /** Custom response when rate limited (optional) */
48
+ onRateLimited?: (c: Context, result: RateLimitResult) => Response | Promise<Response>;
49
+ }
50
+ /**
51
+ * Options for fixed-window rate limiter.
52
+ */
53
+ interface FixedWindowOptions {
54
+ /** KV namespace for storing counters */
55
+ namespace: KVNamespace;
56
+ /** Maximum requests per window */
57
+ limit: number;
58
+ /** Window duration — e.g. '1m', '5m', '1h', '1d' */
59
+ window: string;
60
+ /** Optional prefix for KV keys */
61
+ prefix?: string;
62
+ }
63
+ /**
64
+ * Options for the cacheResponse middleware.
65
+ */
66
+ interface CacheOptions {
67
+ /** Cache TTL in seconds */
68
+ ttl: number;
69
+ /** Function to generate the cache key (defaults to request URL) */
70
+ keyFn?: (c: Context) => string;
71
+ /** Cache API instance (defaults to caches.default) */
72
+ cache?: Cache;
73
+ /** HTTP methods to cache (defaults to ['GET']) */
74
+ methods?: string[];
75
+ }
76
+ /**
77
+ * Hono environment type with workkit context variables.
78
+ */
79
+ interface WorkkitEnv<T extends EnvSchema = EnvSchema> extends Env {
80
+ Variables: {
81
+ "workkit:env": InferEnv<T>;
82
+ "workkit:envValidated": boolean;
83
+ };
84
+ }
85
+ /**
86
+ * Main workkit middleware — validates environment bindings on first request
87
+ * and stores the parsed, typed env in Hono's context.
88
+ *
89
+ * Validation runs once (on the first request) and the result is cached
90
+ * for subsequent requests within the same Worker invocation.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const app = new Hono()
95
+ * app.use(workkit({ env: { API_KEY: z.string().min(1) } }))
96
+ * app.get('/', (c) => {
97
+ * const env = c.get('workkit:env') // typed
98
+ * })
99
+ * ```
100
+ */
101
+ declare function workkit<T extends EnvSchema2>(options: WorkkitOptions<T>): MiddlewareHandler<WorkkitEnv<T>>;
102
+ import { ErrorHandler } from "hono";
103
+ /**
104
+ * Hono error handler that converts WorkkitErrors to proper HTTP responses.
105
+ *
106
+ * - WorkkitError instances → structured JSON with their status code
107
+ * - RateLimitError → includes Retry-After header
108
+ * - ValidationError → includes issues array
109
+ * - Unknown errors → 500 Internal Server Error
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * app.onError(workkitErrorHandler({
114
+ * includeStack: false,
115
+ * onError: (err, c) => console.error(err),
116
+ * }))
117
+ * ```
118
+ */
119
+ declare function workkitErrorHandler(options?: ErrorHandlerOptions): ErrorHandler;
120
+ import { MiddlewareHandler as MiddlewareHandler2 } from "hono";
121
+ /**
122
+ * Rate limit middleware for Hono.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * app.use('/api/*', rateLimit({
127
+ * limiter: fixedWindow({ namespace: env.KV, limit: 100, window: '1m' }),
128
+ * keyFn: (c) => c.req.header('CF-Connecting-IP') ?? 'unknown',
129
+ * }))
130
+ * ```
131
+ */
132
+ declare function rateLimit(options: RateLimitOptions): MiddlewareHandler2;
133
+ /**
134
+ * Parse a duration string like '1m', '5m', '1h', '1d' into milliseconds.
135
+ */
136
+ declare function parseDuration(duration: string): number;
137
+ /**
138
+ * Creates a fixed-window rate limiter backed by KV.
139
+ *
140
+ * Each window is stored as a KV key with an expiration TTL.
141
+ * Uses KV's eventual consistency — suitable for soft rate limiting,
142
+ * not cryptographic precision.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const limiter = fixedWindow({
147
+ * namespace: env.RATE_LIMIT_KV,
148
+ * limit: 100,
149
+ * window: '1m',
150
+ * })
151
+ * ```
152
+ */
153
+ declare function fixedWindow(options: FixedWindowOptions): RateLimiter;
154
+ import { MiddlewareHandler as MiddlewareHandler3 } from "hono";
155
+ /**
156
+ * Cache middleware for Hono — caches responses using the Cache API.
157
+ *
158
+ * Only caches successful (2xx) responses. Serves cached responses on cache hit.
159
+ * Uses Cloudflare's Cache API by default.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * app.get('/api/data', cacheResponse({ ttl: 300 }), async (c) => {
164
+ * return c.json(await fetchData())
165
+ * })
166
+ * ```
167
+ */
168
+ declare function cacheResponse(options: CacheOptions): MiddlewareHandler3;
169
+ import { EnvSchema as EnvSchema3, InferEnv as InferEnv2 } from "@workkit/env";
170
+ import { Context as Context2 } from "hono";
171
+ /**
172
+ * Get the validated, typed environment from Hono context.
173
+ *
174
+ * Requires the workkit() middleware to have run first.
175
+ * Throws if env has not been validated yet.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * app.get('/test', (c) => {
180
+ * const env = getEnv(c)
181
+ * return c.json({ key: env.API_KEY })
182
+ * })
183
+ * ```
184
+ */
185
+ declare function getEnv<T extends EnvSchema3>(c: Context2<WorkkitEnv<T>>): InferEnv2<T>;
186
+ export { workkitErrorHandler, workkit, rateLimit, parseDuration, getEnv, fixedWindow, cacheResponse, WorkkitOptions, WorkkitEnv, RateLimiter, RateLimitResult, RateLimitOptions, FixedWindowOptions, ErrorHandlerOptions, CacheOptions };
@@ -0,0 +1,186 @@
1
+ import { EnvSchema as EnvSchema2 } from "@workkit/env";
2
+ import { MiddlewareHandler } from "hono";
3
+ import { EnvSchema, InferEnv } from "@workkit/env";
4
+ import { Context, Env } from "hono";
5
+ /**
6
+ * Options for the workkit() middleware.
7
+ */
8
+ interface WorkkitOptions<T extends EnvSchema> {
9
+ /** Environment schema to validate against on first request */
10
+ env: T;
11
+ }
12
+ /**
13
+ * Options for the workkitErrorHandler.
14
+ */
15
+ interface ErrorHandlerOptions {
16
+ /** Include stack trace in error response (never in production) */
17
+ includeStack?: boolean;
18
+ /** Custom error callback for logging/reporting */
19
+ onError?: (err: Error, c: Context) => void | Promise<void>;
20
+ }
21
+ /**
22
+ * A rate limiter instance that checks whether a key is allowed.
23
+ */
24
+ interface RateLimiter {
25
+ /** Check if the key is allowed. Returns { allowed, remaining, resetAt } */
26
+ check(key: string): Promise<RateLimitResult>;
27
+ }
28
+ /**
29
+ * Result of a rate limit check.
30
+ */
31
+ interface RateLimitResult {
32
+ /** Whether the request is allowed */
33
+ allowed: boolean;
34
+ /** Remaining requests in the current window */
35
+ remaining: number;
36
+ /** When the window resets (ms since epoch) */
37
+ resetAt: number;
38
+ }
39
+ /**
40
+ * Options for the rateLimit middleware.
41
+ */
42
+ interface RateLimitOptions {
43
+ /** The rate limiter implementation */
44
+ limiter: RateLimiter;
45
+ /** Function to extract the rate limit key from context (e.g., IP address) */
46
+ keyFn: (c: Context) => string | Promise<string>;
47
+ /** Custom response when rate limited (optional) */
48
+ onRateLimited?: (c: Context, result: RateLimitResult) => Response | Promise<Response>;
49
+ }
50
+ /**
51
+ * Options for fixed-window rate limiter.
52
+ */
53
+ interface FixedWindowOptions {
54
+ /** KV namespace for storing counters */
55
+ namespace: KVNamespace;
56
+ /** Maximum requests per window */
57
+ limit: number;
58
+ /** Window duration — e.g. '1m', '5m', '1h', '1d' */
59
+ window: string;
60
+ /** Optional prefix for KV keys */
61
+ prefix?: string;
62
+ }
63
+ /**
64
+ * Options for the cacheResponse middleware.
65
+ */
66
+ interface CacheOptions {
67
+ /** Cache TTL in seconds */
68
+ ttl: number;
69
+ /** Function to generate the cache key (defaults to request URL) */
70
+ keyFn?: (c: Context) => string;
71
+ /** Cache API instance (defaults to caches.default) */
72
+ cache?: Cache;
73
+ /** HTTP methods to cache (defaults to ['GET']) */
74
+ methods?: string[];
75
+ }
76
+ /**
77
+ * Hono environment type with workkit context variables.
78
+ */
79
+ interface WorkkitEnv<T extends EnvSchema = EnvSchema> extends Env {
80
+ Variables: {
81
+ "workkit:env": InferEnv<T>;
82
+ "workkit:envValidated": boolean;
83
+ };
84
+ }
85
+ /**
86
+ * Main workkit middleware — validates environment bindings on first request
87
+ * and stores the parsed, typed env in Hono's context.
88
+ *
89
+ * Validation runs once (on the first request) and the result is cached
90
+ * for subsequent requests within the same Worker invocation.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const app = new Hono()
95
+ * app.use(workkit({ env: { API_KEY: z.string().min(1) } }))
96
+ * app.get('/', (c) => {
97
+ * const env = c.get('workkit:env') // typed
98
+ * })
99
+ * ```
100
+ */
101
+ declare function workkit<T extends EnvSchema2>(options: WorkkitOptions<T>): MiddlewareHandler<WorkkitEnv<T>>;
102
+ import { ErrorHandler } from "hono";
103
+ /**
104
+ * Hono error handler that converts WorkkitErrors to proper HTTP responses.
105
+ *
106
+ * - WorkkitError instances → structured JSON with their status code
107
+ * - RateLimitError → includes Retry-After header
108
+ * - ValidationError → includes issues array
109
+ * - Unknown errors → 500 Internal Server Error
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * app.onError(workkitErrorHandler({
114
+ * includeStack: false,
115
+ * onError: (err, c) => console.error(err),
116
+ * }))
117
+ * ```
118
+ */
119
+ declare function workkitErrorHandler(options?: ErrorHandlerOptions): ErrorHandler;
120
+ import { MiddlewareHandler as MiddlewareHandler2 } from "hono";
121
+ /**
122
+ * Rate limit middleware for Hono.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * app.use('/api/*', rateLimit({
127
+ * limiter: fixedWindow({ namespace: env.KV, limit: 100, window: '1m' }),
128
+ * keyFn: (c) => c.req.header('CF-Connecting-IP') ?? 'unknown',
129
+ * }))
130
+ * ```
131
+ */
132
+ declare function rateLimit(options: RateLimitOptions): MiddlewareHandler2;
133
+ /**
134
+ * Parse a duration string like '1m', '5m', '1h', '1d' into milliseconds.
135
+ */
136
+ declare function parseDuration(duration: string): number;
137
+ /**
138
+ * Creates a fixed-window rate limiter backed by KV.
139
+ *
140
+ * Each window is stored as a KV key with an expiration TTL.
141
+ * Uses KV's eventual consistency — suitable for soft rate limiting,
142
+ * not cryptographic precision.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const limiter = fixedWindow({
147
+ * namespace: env.RATE_LIMIT_KV,
148
+ * limit: 100,
149
+ * window: '1m',
150
+ * })
151
+ * ```
152
+ */
153
+ declare function fixedWindow(options: FixedWindowOptions): RateLimiter;
154
+ import { MiddlewareHandler as MiddlewareHandler3 } from "hono";
155
+ /**
156
+ * Cache middleware for Hono — caches responses using the Cache API.
157
+ *
158
+ * Only caches successful (2xx) responses. Serves cached responses on cache hit.
159
+ * Uses Cloudflare's Cache API by default.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * app.get('/api/data', cacheResponse({ ttl: 300 }), async (c) => {
164
+ * return c.json(await fetchData())
165
+ * })
166
+ * ```
167
+ */
168
+ declare function cacheResponse(options: CacheOptions): MiddlewareHandler3;
169
+ import { EnvSchema as EnvSchema3, InferEnv as InferEnv2 } from "@workkit/env";
170
+ import { Context as Context2 } from "hono";
171
+ /**
172
+ * Get the validated, typed environment from Hono context.
173
+ *
174
+ * Requires the workkit() middleware to have run first.
175
+ * Throws if env has not been validated yet.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * app.get('/test', (c) => {
180
+ * const env = getEnv(c)
181
+ * return c.json({ key: env.API_KEY })
182
+ * })
183
+ * ```
184
+ */
185
+ declare function getEnv<T extends EnvSchema3>(c: Context2<WorkkitEnv<T>>): InferEnv2<T>;
186
+ export { workkitErrorHandler, workkit, rateLimit, parseDuration, getEnv, fixedWindow, cacheResponse, WorkkitOptions, WorkkitEnv, RateLimiter, RateLimitResult, RateLimitOptions, FixedWindowOptions, ErrorHandlerOptions, CacheOptions };
package/dist/index.js ADDED
@@ -0,0 +1,176 @@
1
+ // src/middleware.ts
2
+ import { parseEnv } from "@workkit/env";
3
+ function workkit(options) {
4
+ let cachedEnv = null;
5
+ return async (c, next) => {
6
+ if (!cachedEnv) {
7
+ const rawEnv = c.env;
8
+ cachedEnv = await parseEnv(rawEnv, options.env);
9
+ }
10
+ c.set("workkit:env", cachedEnv);
11
+ c.set("workkit:envValidated", true);
12
+ await next();
13
+ };
14
+ }
15
+ // src/error-handler.ts
16
+ import { InternalError, RateLimitError, isWorkkitError } from "@workkit/errors";
17
+ function workkitErrorHandler(options = {}) {
18
+ const { includeStack = false, onError } = options;
19
+ return async (err, c) => {
20
+ if (onError) {
21
+ try {
22
+ await onError(err, c);
23
+ } catch {}
24
+ }
25
+ if (isWorkkitError(err)) {
26
+ return workkitErrorToResponse(err, includeStack);
27
+ }
28
+ const wrapped = new InternalError(err instanceof Error ? err.message : "An unexpected error occurred", { cause: err });
29
+ return workkitErrorToResponse(wrapped, includeStack);
30
+ };
31
+ }
32
+ function workkitErrorToResponse(error, includeStack) {
33
+ const body = {
34
+ error: {
35
+ code: error.code,
36
+ message: error.message,
37
+ statusCode: error.statusCode
38
+ }
39
+ };
40
+ if ("issues" in error && Array.isArray(error.issues)) {
41
+ body.error.issues = error.issues;
42
+ }
43
+ if (includeStack && error.stack) {
44
+ body.error.stack = error.stack;
45
+ }
46
+ const headers = {
47
+ "Content-Type": "application/json"
48
+ };
49
+ if (error instanceof RateLimitError && error.retryAfterMs) {
50
+ headers["Retry-After"] = String(Math.ceil(error.retryAfterMs / 1000));
51
+ }
52
+ return new Response(JSON.stringify(body), {
53
+ status: error.statusCode,
54
+ headers
55
+ });
56
+ }
57
+ // src/rate-limit.ts
58
+ import { RateLimitError as RateLimitError2 } from "@workkit/errors";
59
+ function rateLimit(options) {
60
+ const { limiter, keyFn, onRateLimited } = options;
61
+ return async (c, next) => {
62
+ const key = await keyFn(c);
63
+ const result = await limiter.check(key);
64
+ c.header("X-RateLimit-Limit", String(result.remaining + (result.allowed ? 0 : 1)));
65
+ c.header("X-RateLimit-Remaining", String(result.remaining));
66
+ c.header("X-RateLimit-Reset", String(Math.ceil(result.resetAt / 1000)));
67
+ if (!result.allowed) {
68
+ if (onRateLimited) {
69
+ return onRateLimited(c, result);
70
+ }
71
+ const retryAfterMs = result.resetAt - Date.now();
72
+ throw new RateLimitError2("Rate limit exceeded", retryAfterMs > 0 ? retryAfterMs : undefined);
73
+ }
74
+ await next();
75
+ };
76
+ }
77
+ function parseDuration(duration) {
78
+ const match = duration.match(/^(\d+)(s|m|h|d)$/);
79
+ if (!match) {
80
+ throw new Error(`Invalid duration format: "${duration}". Use e.g. '1m', '5m', '1h', '1d'.`);
81
+ }
82
+ const value = Number.parseInt(match[1], 10);
83
+ const unit = match[2];
84
+ switch (unit) {
85
+ case "s":
86
+ return value * 1000;
87
+ case "m":
88
+ return value * 60 * 1000;
89
+ case "h":
90
+ return value * 60 * 60 * 1000;
91
+ case "d":
92
+ return value * 24 * 60 * 60 * 1000;
93
+ default:
94
+ throw new Error(`Unknown duration unit: "${unit}"`);
95
+ }
96
+ }
97
+ function fixedWindow(options) {
98
+ const { namespace, limit, window: windowStr, prefix = "rl:" } = options;
99
+ const windowMs = parseDuration(windowStr);
100
+ return {
101
+ async check(key) {
102
+ const now = Date.now();
103
+ const windowStart = Math.floor(now / windowMs) * windowMs;
104
+ const resetAt = windowStart + windowMs;
105
+ const kvKey = `${prefix}${key}:${windowStart}`;
106
+ const current = await namespace.get(kvKey);
107
+ const count = current ? Number.parseInt(current, 10) : 0;
108
+ if (count >= limit) {
109
+ return { allowed: false, remaining: 0, resetAt };
110
+ }
111
+ const ttlSeconds = Math.ceil(windowMs / 1000);
112
+ await namespace.put(kvKey, String(count + 1), {
113
+ expirationTtl: ttlSeconds
114
+ });
115
+ return { allowed: true, remaining: limit - count - 1, resetAt };
116
+ }
117
+ };
118
+ }
119
+ // src/cache.ts
120
+ function cacheResponse(options) {
121
+ const { ttl, keyFn, methods = ["GET"] } = options;
122
+ return async (c, next) => {
123
+ if (!methods.includes(c.req.method)) {
124
+ await next();
125
+ return;
126
+ }
127
+ const cacheKey = keyFn ? keyFn(c) : c.req.url;
128
+ const cacheRequest = new Request(cacheKey);
129
+ const cache = options.cache ?? (typeof caches !== "undefined" ? caches.default : null);
130
+ if (!cache) {
131
+ await next();
132
+ return;
133
+ }
134
+ const cached = await cache.match(cacheRequest);
135
+ if (cached) {
136
+ return cached;
137
+ }
138
+ await next();
139
+ const response = c.res;
140
+ if (response.status >= 200 && response.status < 300) {
141
+ const cloned = response.clone();
142
+ const cachedResponse = new Response(cloned.body, {
143
+ status: cloned.status,
144
+ statusText: cloned.statusText,
145
+ headers: new Headers(cloned.headers)
146
+ });
147
+ cachedResponse.headers.set("Cache-Control", `s-maxage=${ttl}`);
148
+ const putPromise = cache.put(cacheRequest, cachedResponse);
149
+ try {
150
+ c.executionCtx.waitUntil(putPromise);
151
+ } catch {
152
+ await putPromise;
153
+ }
154
+ }
155
+ };
156
+ }
157
+ // src/helpers.ts
158
+ function getEnv(c) {
159
+ const validated = c.get("workkit:envValidated");
160
+ if (!validated) {
161
+ throw new Error("workkit:env is not available. Did you forget to add the workkit() middleware?");
162
+ }
163
+ return c.get("workkit:env");
164
+ }
165
+ export {
166
+ workkitErrorHandler,
167
+ workkit,
168
+ rateLimit,
169
+ parseDuration,
170
+ getEnv,
171
+ fixedWindow,
172
+ cacheResponse
173
+ };
174
+
175
+ //# debugId=0CE4411E8BEEF18964756E2164756E21
176
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["src/middleware.ts", "src/error-handler.ts", "src/rate-limit.ts", "src/cache.ts", "src/helpers.ts"],
4
+ "sourcesContent": [
5
+ "import { parseEnv } from \"@workkit/env\";\nimport type { EnvSchema, InferEnv } from \"@workkit/env\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { WorkkitEnv, WorkkitOptions } from \"./types\";\n\n/**\n * Main workkit middleware — validates environment bindings on first request\n * and stores the parsed, typed env in Hono's context.\n *\n * Validation runs once (on the first request) and the result is cached\n * for subsequent requests within the same Worker invocation.\n *\n * @example\n * ```ts\n * const app = new Hono()\n * app.use(workkit({ env: { API_KEY: z.string().min(1) } }))\n * app.get('/', (c) => {\n * const env = c.get('workkit:env') // typed\n * })\n * ```\n */\nexport function workkit<T extends EnvSchema>(\n\toptions: WorkkitOptions<T>,\n): MiddlewareHandler<WorkkitEnv<T>> {\n\tlet cachedEnv: InferEnv<T> | null = null;\n\n\treturn async (c, next) => {\n\t\tif (!cachedEnv) {\n\t\t\tconst rawEnv = c.env as Record<string, unknown>;\n\t\t\tcachedEnv = await parseEnv(rawEnv, options.env);\n\t\t}\n\n\t\tc.set(\"workkit:env\", cachedEnv);\n\t\tc.set(\"workkit:envValidated\", true);\n\n\t\tawait next();\n\t};\n}\n",
6
+ "import { InternalError, RateLimitError, type WorkkitError, isWorkkitError } from \"@workkit/errors\";\nimport type { ErrorHandler } from \"hono\";\nimport type { ErrorHandlerOptions } from \"./types\";\n\n/**\n * Hono error handler that converts WorkkitErrors to proper HTTP responses.\n *\n * - WorkkitError instances → structured JSON with their status code\n * - RateLimitError → includes Retry-After header\n * - ValidationError → includes issues array\n * - Unknown errors → 500 Internal Server Error\n *\n * @example\n * ```ts\n * app.onError(workkitErrorHandler({\n * includeStack: false,\n * onError: (err, c) => console.error(err),\n * }))\n * ```\n */\nexport function workkitErrorHandler(options: ErrorHandlerOptions = {}): ErrorHandler {\n\tconst { includeStack = false, onError } = options;\n\n\treturn async (err, c) => {\n\t\tif (onError) {\n\t\t\ttry {\n\t\t\t\tawait onError(err, c);\n\t\t\t} catch {\n\t\t\t\t// Don't let error callback failures break the response\n\t\t\t}\n\t\t}\n\n\t\tif (isWorkkitError(err)) {\n\t\t\treturn workkitErrorToResponse(err, includeStack);\n\t\t}\n\n\t\t// Wrap unknown errors as InternalError\n\t\tconst wrapped = new InternalError(\n\t\t\terr instanceof Error ? err.message : \"An unexpected error occurred\",\n\t\t\t{ cause: err },\n\t\t);\n\n\t\treturn workkitErrorToResponse(wrapped, includeStack);\n\t};\n}\n\nfunction workkitErrorToResponse(error: WorkkitError, includeStack: boolean): Response {\n\tconst body: Record<string, unknown> = {\n\t\terror: {\n\t\t\tcode: error.code,\n\t\t\tmessage: error.message,\n\t\t\tstatusCode: error.statusCode,\n\t\t},\n\t};\n\n\t// Include validation issues if present\n\tif (\"issues\" in error && Array.isArray((error as any).issues)) {\n\t\t(body.error as any).issues = (error as any).issues;\n\t}\n\n\tif (includeStack && error.stack) {\n\t\t(body.error as any).stack = error.stack;\n\t}\n\n\tconst headers: Record<string, string> = {\n\t\t\"Content-Type\": \"application/json\",\n\t};\n\n\t// Set Retry-After header for rate limit errors\n\tif (error instanceof RateLimitError && error.retryAfterMs) {\n\t\theaders[\"Retry-After\"] = String(Math.ceil(error.retryAfterMs / 1000));\n\t}\n\n\treturn new Response(JSON.stringify(body), {\n\t\tstatus: error.statusCode,\n\t\theaders,\n\t});\n}\n",
7
+ "import { RateLimitError } from \"@workkit/errors\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { FixedWindowOptions, RateLimitOptions, RateLimitResult, RateLimiter } from \"./types\";\n\n/**\n * Rate limit middleware for Hono.\n *\n * @example\n * ```ts\n * app.use('/api/*', rateLimit({\n * limiter: fixedWindow({ namespace: env.KV, limit: 100, window: '1m' }),\n * keyFn: (c) => c.req.header('CF-Connecting-IP') ?? 'unknown',\n * }))\n * ```\n */\nexport function rateLimit(options: RateLimitOptions): MiddlewareHandler {\n\tconst { limiter, keyFn, onRateLimited } = options;\n\n\treturn async (c, next) => {\n\t\tconst key = await keyFn(c);\n\t\tconst result = await limiter.check(key);\n\n\t\t// Set rate limit headers regardless of outcome\n\t\tc.header(\"X-RateLimit-Limit\", String(result.remaining + (result.allowed ? 0 : 1)));\n\t\tc.header(\"X-RateLimit-Remaining\", String(result.remaining));\n\t\tc.header(\"X-RateLimit-Reset\", String(Math.ceil(result.resetAt / 1000)));\n\n\t\tif (!result.allowed) {\n\t\t\tif (onRateLimited) {\n\t\t\t\treturn onRateLimited(c, result);\n\t\t\t}\n\n\t\t\tconst retryAfterMs = result.resetAt - Date.now();\n\t\t\tthrow new RateLimitError(\"Rate limit exceeded\", retryAfterMs > 0 ? retryAfterMs : undefined);\n\t\t}\n\n\t\tawait next();\n\t};\n}\n\n/**\n * Parse a duration string like '1m', '5m', '1h', '1d' into milliseconds.\n */\nexport function parseDuration(duration: string): number {\n\tconst match = duration.match(/^(\\d+)(s|m|h|d)$/);\n\tif (!match) {\n\t\tthrow new Error(`Invalid duration format: \"${duration}\". Use e.g. '1m', '5m', '1h', '1d'.`);\n\t}\n\n\tconst value = Number.parseInt(match[1]!, 10);\n\tconst unit = match[2]!;\n\n\tswitch (unit) {\n\t\tcase \"s\":\n\t\t\treturn value * 1000;\n\t\tcase \"m\":\n\t\t\treturn value * 60 * 1000;\n\t\tcase \"h\":\n\t\t\treturn value * 60 * 60 * 1000;\n\t\tcase \"d\":\n\t\t\treturn value * 24 * 60 * 60 * 1000;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown duration unit: \"${unit}\"`);\n\t}\n}\n\n/**\n * Creates a fixed-window rate limiter backed by KV.\n *\n * Each window is stored as a KV key with an expiration TTL.\n * Uses KV's eventual consistency — suitable for soft rate limiting,\n * not cryptographic precision.\n *\n * @example\n * ```ts\n * const limiter = fixedWindow({\n * namespace: env.RATE_LIMIT_KV,\n * limit: 100,\n * window: '1m',\n * })\n * ```\n */\nexport function fixedWindow(options: FixedWindowOptions): RateLimiter {\n\tconst { namespace, limit, window: windowStr, prefix = \"rl:\" } = options;\n\tconst windowMs = parseDuration(windowStr);\n\n\treturn {\n\t\tasync check(key: string): Promise<RateLimitResult> {\n\t\t\tconst now = Date.now();\n\t\t\tconst windowStart = Math.floor(now / windowMs) * windowMs;\n\t\t\tconst resetAt = windowStart + windowMs;\n\t\t\tconst kvKey = `${prefix}${key}:${windowStart}`;\n\n\t\t\tconst current = await namespace.get(kvKey);\n\t\t\tconst count = current ? Number.parseInt(current, 10) : 0;\n\n\t\t\tif (count >= limit) {\n\t\t\t\treturn { allowed: false, remaining: 0, resetAt };\n\t\t\t}\n\n\t\t\t// Increment counter with TTL equal to window duration (in seconds, rounded up)\n\t\t\tconst ttlSeconds = Math.ceil(windowMs / 1000);\n\t\t\tawait namespace.put(kvKey, String(count + 1), {\n\t\t\t\texpirationTtl: ttlSeconds,\n\t\t\t});\n\n\t\t\treturn { allowed: true, remaining: limit - count - 1, resetAt };\n\t\t},\n\t};\n}\n",
8
+ "import type { MiddlewareHandler } from \"hono\";\nimport type { CacheOptions } from \"./types\";\n\n/**\n * Cache middleware for Hono — caches responses using the Cache API.\n *\n * Only caches successful (2xx) responses. Serves cached responses on cache hit.\n * Uses Cloudflare's Cache API by default.\n *\n * @example\n * ```ts\n * app.get('/api/data', cacheResponse({ ttl: 300 }), async (c) => {\n * return c.json(await fetchData())\n * })\n * ```\n */\nexport function cacheResponse(options: CacheOptions): MiddlewareHandler {\n\tconst { ttl, keyFn, methods = [\"GET\"] } = options;\n\n\treturn async (c, next) => {\n\t\t// Only cache specified methods\n\t\tif (!methods.includes(c.req.method)) {\n\t\t\tawait next();\n\t\t\treturn;\n\t\t}\n\n\t\tconst cacheKey = keyFn ? keyFn(c) : c.req.url;\n\t\tconst cacheRequest = new Request(cacheKey);\n\n\t\t// Try to get the cache instance\n\t\tconst cache = options.cache ?? (typeof caches !== \"undefined\" ? caches.default : null);\n\t\tif (!cache) {\n\t\t\t// No cache available, skip caching\n\t\t\tawait next();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for cached response\n\t\tconst cached = await cache.match(cacheRequest);\n\t\tif (cached) {\n\t\t\treturn cached;\n\t\t}\n\n\t\t// Execute handler\n\t\tawait next();\n\n\t\t// Only cache successful responses\n\t\tconst response = c.res;\n\t\tif (response.status >= 200 && response.status < 300) {\n\t\t\t// Clone the response and add cache headers\n\t\t\tconst cloned = response.clone();\n\t\t\tconst cachedResponse = new Response(cloned.body, {\n\t\t\t\tstatus: cloned.status,\n\t\t\t\tstatusText: cloned.statusText,\n\t\t\t\theaders: new Headers(cloned.headers),\n\t\t\t});\n\t\t\tcachedResponse.headers.set(\"Cache-Control\", `s-maxage=${ttl}`);\n\n\t\t\t// Store in cache — use waitUntil if available, otherwise await directly\n\t\t\tconst putPromise = cache.put(cacheRequest, cachedResponse);\n\t\t\ttry {\n\t\t\t\tc.executionCtx.waitUntil(putPromise);\n\t\t\t} catch {\n\t\t\t\t// executionCtx not available (e.g., in tests), await directly\n\t\t\t\tawait putPromise;\n\t\t\t}\n\t\t}\n\t};\n}\n",
9
+ "import type { EnvSchema, InferEnv } from \"@workkit/env\";\nimport type { Context } from \"hono\";\nimport type { WorkkitEnv } from \"./types\";\n\n/**\n * Get the validated, typed environment from Hono context.\n *\n * Requires the workkit() middleware to have run first.\n * Throws if env has not been validated yet.\n *\n * @example\n * ```ts\n * app.get('/test', (c) => {\n * const env = getEnv(c)\n * return c.json({ key: env.API_KEY })\n * })\n * ```\n */\nexport function getEnv<T extends EnvSchema>(c: Context<WorkkitEnv<T>>): InferEnv<T> {\n\tconst validated = c.get(\"workkit:envValidated\");\n\tif (!validated) {\n\t\tthrow new Error(\n\t\t\t\"workkit:env is not available. Did you forget to add the workkit() middleware?\",\n\t\t);\n\t}\n\treturn c.get(\"workkit:env\");\n}\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "4C2D147FF9900DA464756E2164756E21",
13
+ "names": []
14
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@workkit/hono",
3
+ "version": "0.0.1",
4
+ "description": "Hono middleware that integrates workkit utilities — env validation, error handling, rate limiting, caching",
5
+ "license": "MIT",
6
+ "author": "Bikash Dash <beeeku>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/beeeku/workkit",
10
+ "directory": "integrations/hono"
11
+ },
12
+ "type": "module",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.cts",
21
+ "default": "./dist/index.cjs"
22
+ }
23
+ }
24
+ },
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "files": ["dist"],
29
+ "sideEffects": false,
30
+ "scripts": {
31
+ "build": "bunup",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "typecheck": "tsc --noEmit",
35
+ "clean": "rm -rf dist"
36
+ },
37
+ "dependencies": {
38
+ "@workkit/types": "workspace:*",
39
+ "@workkit/errors": "workspace:*",
40
+ "@workkit/env": "workspace:*"
41
+ },
42
+ "peerDependencies": {
43
+ "hono": ">=4.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@cloudflare/workers-types": "^4.20250310.0",
47
+ "bunup": "0.16.31",
48
+ "expect-type": "^1.1.0",
49
+ "hono": "^4.7.0",
50
+ "typescript": "^5.7.0",
51
+ "vitest": "^3.0.0"
52
+ },
53
+ "keywords": [
54
+ "cloudflare",
55
+ "workers",
56
+ "hono",
57
+ "middleware",
58
+ "env",
59
+ "validation",
60
+ "error-handler",
61
+ "rate-limit",
62
+ "cache",
63
+ "workkit"
64
+ ]
65
+ }