@hadoku/worker-utils 0.0.0-stage → 1.6.19

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.
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Data masking utilities for sensitive information logging
3
+ *
4
+ * Provides functions to safely mask keys, session IDs, and other
5
+ * sensitive data before logging to prevent exposure.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { maskKey, maskSessionId } from '../util';
10
+ *
11
+ * const key = 'admin-secret-key-12345';
12
+ * console.log(maskKey(key)); // "admin-secre" (first 50%)
13
+ *
14
+ * const sessionId = '1a2b3c4d5e6f7g8h9i0j1k2l';
15
+ * console.log(maskSessionId(sessionId)); // "1a2b3c4d5e6f7g8h" (first 16 chars)
16
+ * ```
17
+ */
18
+ /**
19
+ * Masking configuration constants
20
+ */
21
+ declare const MASKING: {
22
+ /** Percentage of key to show (default: 50%) */
23
+ readonly KEY_PERCENTAGE: 0.5;
24
+ /** Minimum characters to show for keys */
25
+ readonly KEY_MIN_LENGTH: 4;
26
+ /** Number of characters to show for session IDs (default: 16) */
27
+ readonly SESSION_ID_PREFIX_LENGTH: 16;
28
+ };
29
+ /**
30
+ * Mask a key for logging (show first 50% of characters)
31
+ *
32
+ * Shows the first half of the key to allow identification while
33
+ * protecting the full key from exposure in logs/dashboards.
34
+ * No suffix is added - just the raw truncated key.
35
+ *
36
+ * @param key - Key to mask
37
+ * @param percentage - Percentage of key to show (default: 50%)
38
+ * @returns Masked key (e.g., "N7RZK2YW" for a 16-char key)
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * maskKey('N7RZK2YWABCD1234'); // "N7RZK2YW"
43
+ * maskKey('short'); // "sh"
44
+ * maskKey('ab'); // "ab"
45
+ * ```
46
+ */
47
+ declare function maskKey(key: string, percentage?: number): string;
48
+ /**
49
+ * Mask a session ID for logging (show first 16 characters)
50
+ *
51
+ * @param id - Session ID to mask
52
+ * @returns Masked session ID (e.g., "1a2b3c4d5e6f7g8h")
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * maskSessionId('1a2b3c4d5e6f7g8h9i0j'); // "1a2b3c4d5e6f7g8h"
57
+ * maskSessionId('short-id'); // "short-id"
58
+ * ```
59
+ */
60
+ declare function maskSessionId(id: string): string;
61
+ /**
62
+ * Mask an IP address for logging (show first 3 octets, replace last with *)
63
+ *
64
+ * @param ip - IP address to mask
65
+ * @returns Masked IP (e.g., "203.0.113.*")
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * maskIpAddress('203.0.113.45'); // "203.0.113.*"
70
+ * maskIpAddress('2001:db8::1'); // "2001:db8::*"
71
+ * maskIpAddress(null); // undefined
72
+ * ```
73
+ */
74
+ declare function maskIpAddress(ip: string | null | undefined): string | undefined;
75
+ /**
76
+ * Mask an email address for logging (show first character and domain)
77
+ *
78
+ * @param email - Email to mask
79
+ * @returns Masked email (e.g., "j***@example.com")
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * maskEmail('john@example.com'); // "j***@example.com"
84
+ * maskEmail('invalid-email'); // "invalid-email"
85
+ * ```
86
+ */
87
+ declare function maskEmail(email: string): string;
88
+ /**
89
+ * Redact multiple sensitive fields from an object
90
+ *
91
+ * @param obj - Object to redact
92
+ * @param fields - Field names to redact
93
+ * @returns New object with redacted fields
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const data = { username: 'john', password: 'secret123', email: 'john@example.com' };
98
+ * redactFields(data, ['password']);
99
+ * // { username: 'john', password: '[REDACTED]', email: 'john@example.com' }
100
+ * ```
101
+ */
102
+ declare function redactFields<T extends Record<string, any>>(obj: T, fields: string[]): T;
103
+ /**
104
+ * Common sensitive field names to redact
105
+ */
106
+ declare const SENSITIVE_FIELDS: readonly ["password", "token", "secret", "apiKey", "api_key", "accessToken", "access_token", "refreshToken", "refresh_token", "privateKey", "private_key", "authKey", "auth_key"];
107
+
108
+ export { MASKING, SENSITIVE_FIELDS, maskEmail, maskIpAddress, maskKey, maskSessionId, redactFields };
@@ -0,0 +1,90 @@
1
+ // masking.ts
2
+ var MASKING = {
3
+ /** Percentage of key to show (default: 50%) */
4
+ KEY_PERCENTAGE: 0.5,
5
+ /** Minimum characters to show for keys */
6
+ KEY_MIN_LENGTH: 4,
7
+ /** Number of characters to show for session IDs (default: 16) */
8
+ SESSION_ID_PREFIX_LENGTH: 16
9
+ };
10
+ function maskKey(key, percentage = MASKING.KEY_PERCENTAGE) {
11
+ if (!key) {
12
+ return key;
13
+ }
14
+ const showLength = Math.max(MASKING.KEY_MIN_LENGTH, Math.floor(key.length * percentage));
15
+ if (key.length <= showLength) {
16
+ return key;
17
+ }
18
+ return key.substring(0, showLength);
19
+ }
20
+ function maskSessionId(id) {
21
+ if (!id) {
22
+ return id;
23
+ }
24
+ const length = MASKING.SESSION_ID_PREFIX_LENGTH;
25
+ if (id.length <= length) {
26
+ return id;
27
+ }
28
+ return id.substring(0, length);
29
+ }
30
+ function maskIpAddress(ip) {
31
+ if (!ip) {
32
+ return void 0;
33
+ }
34
+ if (ip.includes(".")) {
35
+ const parts = ip.split(".");
36
+ if (parts.length === 4) {
37
+ return `${parts[0]}.${parts[1]}.${parts[2]}.*`;
38
+ }
39
+ }
40
+ if (ip.includes(":")) {
41
+ const lastColon = ip.lastIndexOf(":");
42
+ if (lastColon > 0) {
43
+ return ip.substring(0, lastColon + 1) + "*";
44
+ }
45
+ }
46
+ return ip;
47
+ }
48
+ function maskEmail(email) {
49
+ if (!email?.includes("@")) {
50
+ return email;
51
+ }
52
+ const [username, domain] = email.split("@");
53
+ if (!username || !domain) {
54
+ return email;
55
+ }
56
+ return `${username[0]}***@${domain}`;
57
+ }
58
+ function redactFields(obj, fields) {
59
+ const redacted = { ...obj };
60
+ for (const field of fields) {
61
+ if (field in redacted) {
62
+ redacted[field] = "[REDACTED]";
63
+ }
64
+ }
65
+ return redacted;
66
+ }
67
+ var SENSITIVE_FIELDS = [
68
+ "password",
69
+ "token",
70
+ "secret",
71
+ "apiKey",
72
+ "api_key",
73
+ "accessToken",
74
+ "access_token",
75
+ "refreshToken",
76
+ "refresh_token",
77
+ "privateKey",
78
+ "private_key",
79
+ "authKey",
80
+ "auth_key"
81
+ ];
82
+ export {
83
+ MASKING,
84
+ SENSITIVE_FIELDS,
85
+ maskEmail,
86
+ maskIpAddress,
87
+ maskKey,
88
+ maskSessionId,
89
+ redactFields
90
+ };
@@ -0,0 +1,252 @@
1
+ import * as hono_utils_types from 'hono/utils/types';
2
+ import * as hono from 'hono';
3
+ import { Context } from 'hono';
4
+
5
+ interface ValidationHookResult {
6
+ success: false;
7
+ error: {
8
+ issues: {
9
+ path: PropertyKey[];
10
+ message: string;
11
+ }[];
12
+ };
13
+ }
14
+ /**
15
+ * Error response format type
16
+ * - 'simple': { error, message } - Used by task-api
17
+ * - 'wrapped': { success: false, error, message } - Used by prompt-api, oss-recon-api
18
+ */
19
+ type ErrorResponseFormat = 'simple' | 'wrapped';
20
+ /**
21
+ * Creates a validation hook for OpenAPIHono that formats Zod validation errors.
22
+ *
23
+ * @param format - The error response format to use
24
+ * @returns A defaultHook function for OpenAPIHono
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const app = new OpenAPIHono({
29
+ * defaultHook: createValidationHook('simple'),
30
+ * });
31
+ * ```
32
+ */
33
+ declare function createValidationHook(format?: ErrorResponseFormat): (result: ValidationHookResult | {
34
+ success: true;
35
+ }, c: Context) => (Response & hono.TypedResponse<{
36
+ error: string;
37
+ message: string;
38
+ }, 400, "json">) | undefined;
39
+ /**
40
+ * Pre-built validation hook for simple error format (task-api style)
41
+ */
42
+ declare const simpleValidationHook: (result: ValidationHookResult | {
43
+ success: true;
44
+ }, c: Context) => (Response & hono.TypedResponse<{
45
+ error: string;
46
+ message: string;
47
+ }, 400, "json">) | undefined;
48
+ /**
49
+ * Pre-built validation hook for wrapped error format (prompt-api style)
50
+ */
51
+ declare const wrappedValidationHook: (result: ValidationHookResult | {
52
+ success: true;
53
+ }, c: Context) => (Response & hono.TypedResponse<{
54
+ error: string;
55
+ message: string;
56
+ }, 400, "json">) | undefined;
57
+ /**
58
+ * Creates 404 and error handlers with consistent response format.
59
+ *
60
+ * @param format - The error response format to use
61
+ * @returns Object with notFoundHandler and errorHandler functions
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const { notFoundHandler, errorHandler } = createErrorHandlers('wrapped');
66
+ * app.notFound(notFoundHandler);
67
+ * app.onError(errorHandler);
68
+ * ```
69
+ */
70
+ declare function createErrorHandlers(format?: ErrorResponseFormat): {
71
+ notFoundHandler: (c: Context) => Response & hono.TypedResponse<{
72
+ error: string;
73
+ message: string;
74
+ }, 404, "json">;
75
+ errorHandler: (err: Error, c: Context) => Response & hono.TypedResponse<{
76
+ error: string;
77
+ message: string;
78
+ }, 500, "json">;
79
+ };
80
+ interface OpenAPIDocOptions {
81
+ /** API title */
82
+ title: string;
83
+ /** API version */
84
+ version: string;
85
+ /** API description (supports markdown) */
86
+ description: string;
87
+ /** Base path for production URL (e.g., '/api' or '/task/api') */
88
+ basePath?: string;
89
+ /** Production server URL */
90
+ production: string;
91
+ /** Local development port (default: 8787) */
92
+ localPort?: number;
93
+ /** OpenAPI tags */
94
+ tags?: {
95
+ name: string;
96
+ description: string;
97
+ }[];
98
+ }
99
+ /**
100
+ * Creates a standardized OpenAPI doc configuration.
101
+ *
102
+ * @param options - Configuration options
103
+ * @returns OpenAPI doc configuration object for app.doc()
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * app.doc('/api/openapi.json', createOpenAPIDocConfig({
108
+ * title: 'Task API',
109
+ * version: '1.0.0',
110
+ * description: 'API for managing tasks',
111
+ * production: 'https://task-api.hadoku.workers.dev',
112
+ * tags: [
113
+ * { name: 'Tasks', description: 'Task operations' },
114
+ * ],
115
+ * }));
116
+ * ```
117
+ */
118
+ declare function createOpenAPIDocConfig(options: OpenAPIDocOptions): {
119
+ openapi: "3.0.0";
120
+ info: {
121
+ title: string;
122
+ version: string;
123
+ description: string;
124
+ };
125
+ servers: {
126
+ url: string;
127
+ description: string;
128
+ }[];
129
+ tags: {
130
+ name: string;
131
+ description: string;
132
+ }[];
133
+ };
134
+ /**
135
+ * Common OpenAPI tags used across multiple APIs
136
+ */
137
+ declare const CommonTags: {
138
+ readonly Health: {
139
+ readonly name: "Health";
140
+ readonly description: "Health check endpoints";
141
+ };
142
+ readonly Admin: {
143
+ readonly name: "Admin";
144
+ readonly description: "Admin operations (requires authentication)";
145
+ };
146
+ };
147
+ /**
148
+ * Success response helper with wrapped format { success: true, data }
149
+ * Used by: prompt-api, oss-recon-api
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * return okWrapped(c, { categories: [...] });
154
+ * ```
155
+ */
156
+ declare function okWrapped<T>(c: Context, data: T): Response & hono.TypedResponse<true | T extends bigint | readonly bigint[] ? never : { [K in keyof {
157
+ success: true;
158
+ data: T;
159
+ } as ({
160
+ success: true;
161
+ data: T;
162
+ }[K] extends infer T_1 ? T_1 extends {
163
+ success: true;
164
+ data: T;
165
+ }[K] ? T_1 extends hono_utils_types.InvalidJSONValue ? true : false : never : never) extends true ? never : K]: boolean extends ({
166
+ success: true;
167
+ data: T;
168
+ }[K] extends infer T_2 ? T_2 extends {
169
+ success: true;
170
+ data: T;
171
+ }[K] ? T_2 extends hono_utils_types.InvalidJSONValue ? true : false : never : never) ? hono_utils_types.JSONParsed<{
172
+ success: true;
173
+ data: T;
174
+ }[K], bigint | readonly bigint[]> | undefined : hono_utils_types.JSONParsed<{
175
+ success: true;
176
+ data: T;
177
+ }[K], bigint | readonly bigint[]>; }, 200, "json">;
178
+ /**
179
+ * Created response helper with wrapped format { success: true, data }
180
+ * Used by: prompt-api admin routes
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * return createdWrapped(c, { category });
185
+ * ```
186
+ */
187
+ declare function createdWrapped<T>(c: Context, data: T): Response & hono.TypedResponse<true | T extends bigint | readonly bigint[] ? never : { [K in keyof {
188
+ success: true;
189
+ data: T;
190
+ } as ({
191
+ success: true;
192
+ data: T;
193
+ }[K] extends infer T_1 ? T_1 extends {
194
+ success: true;
195
+ data: T;
196
+ }[K] ? T_1 extends hono_utils_types.InvalidJSONValue ? true : false : never : never) extends true ? never : K]: boolean extends ({
197
+ success: true;
198
+ data: T;
199
+ }[K] extends infer T_2 ? T_2 extends {
200
+ success: true;
201
+ data: T;
202
+ }[K] ? T_2 extends hono_utils_types.InvalidJSONValue ? true : false : never : never) ? hono_utils_types.JSONParsed<{
203
+ success: true;
204
+ data: T;
205
+ }[K], bigint | readonly bigint[]> | undefined : hono_utils_types.JSONParsed<{
206
+ success: true;
207
+ data: T;
208
+ }[K], bigint | readonly bigint[]>; }, 201, "json">;
209
+ /**
210
+ * Bad request response helper with wrapped format { success: false, error, message }
211
+ * Use in openapi() handlers where the route declares DetailedErrorResponseSchema for 400.
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * return badRequestWrapped(c, 'Missing required field: id');
216
+ * ```
217
+ */
218
+ declare function badRequestWrapped(c: Context, message: string): Response & hono.TypedResponse<{
219
+ success: false;
220
+ error: string;
221
+ message: string;
222
+ }, 400, "json">;
223
+ /**
224
+ * Not found response helper with wrapped format { success: false, error, message }
225
+ * Use in openapi() handlers where the route declares DetailedErrorResponseSchema for 404.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * return notFoundWrapped(c, 'Category');
230
+ * ```
231
+ */
232
+ declare function notFoundWrapped(c: Context, resource?: string): Response & hono.TypedResponse<{
233
+ success: false;
234
+ error: string;
235
+ message: string;
236
+ }, 404, "json">;
237
+ /**
238
+ * Server error response helper with wrapped format { success: false, error, message }
239
+ * Use in openapi() handlers where the route declares DetailedErrorResponseSchema for 500.
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * return serverErrorWrapped(c, 'Failed to fetch categories');
244
+ * ```
245
+ */
246
+ declare function serverErrorWrapped(c: Context, message?: string): Response & hono.TypedResponse<{
247
+ success: false;
248
+ error: string;
249
+ message: string;
250
+ }, 500, "json">;
251
+
252
+ export { CommonTags, type ErrorResponseFormat, badRequestWrapped, createErrorHandlers, createOpenAPIDocConfig, createValidationHook, createdWrapped, notFoundWrapped, okWrapped, serverErrorWrapped, simpleValidationHook, wrappedValidationHook };
@@ -0,0 +1,131 @@
1
+ // openapi-config.ts
2
+ function createValidationHook(format = "simple") {
3
+ return (result, c) => {
4
+ if (!result.success) {
5
+ const issues = result.error.issues;
6
+ const message = issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
7
+ if (format === "wrapped") {
8
+ return c.json(
9
+ {
10
+ success: false,
11
+ error: "Validation Error",
12
+ message
13
+ },
14
+ 400
15
+ );
16
+ }
17
+ return c.json(
18
+ {
19
+ error: "Validation Error",
20
+ message
21
+ },
22
+ 400
23
+ );
24
+ }
25
+ };
26
+ }
27
+ var simpleValidationHook = createValidationHook("simple");
28
+ var wrappedValidationHook = createValidationHook("wrapped");
29
+ function createErrorHandlers(format = "simple") {
30
+ const notFoundHandler = (c) => {
31
+ if (format === "wrapped") {
32
+ return c.json(
33
+ {
34
+ success: false,
35
+ error: "Not found",
36
+ message: "The requested endpoint does not exist"
37
+ },
38
+ 404
39
+ );
40
+ }
41
+ return c.json(
42
+ {
43
+ error: "Not found",
44
+ message: "The requested endpoint does not exist"
45
+ },
46
+ 404
47
+ );
48
+ };
49
+ const errorHandler = (err, c) => {
50
+ console.error("Unhandled error:", err);
51
+ if (format === "wrapped") {
52
+ return c.json(
53
+ {
54
+ success: false,
55
+ error: "Internal server error",
56
+ message: "An unexpected error occurred"
57
+ },
58
+ 500
59
+ );
60
+ }
61
+ return c.json(
62
+ {
63
+ error: "Internal server error",
64
+ message: "An unexpected error occurred"
65
+ },
66
+ 500
67
+ );
68
+ };
69
+ return { notFoundHandler, errorHandler };
70
+ }
71
+ function createOpenAPIDocConfig(options) {
72
+ const { title, version, description, production, localPort = 8787, tags = [] } = options;
73
+ return {
74
+ openapi: "3.0.0",
75
+ info: {
76
+ title,
77
+ version,
78
+ description: description.trim()
79
+ },
80
+ servers: [
81
+ {
82
+ url: production,
83
+ description: "Production"
84
+ },
85
+ {
86
+ url: `http://localhost:${localPort}`,
87
+ description: "Local development"
88
+ }
89
+ ],
90
+ tags
91
+ };
92
+ }
93
+ var CommonTags = {
94
+ Health: { name: "Health", description: "Health check endpoints" },
95
+ Admin: { name: "Admin", description: "Admin operations (requires authentication)" }
96
+ };
97
+ function okWrapped(c, data) {
98
+ return c.json({ success: true, data }, 200);
99
+ }
100
+ function createdWrapped(c, data) {
101
+ return c.json({ success: true, data }, 201);
102
+ }
103
+ function badRequestWrapped(c, message) {
104
+ return c.json({ success: false, error: "Bad Request", message }, 400);
105
+ }
106
+ function notFoundWrapped(c, resource = "Resource") {
107
+ return c.json(
108
+ {
109
+ success: false,
110
+ error: "Not Found",
111
+ message: `${resource} not found`
112
+ },
113
+ 404
114
+ );
115
+ }
116
+ function serverErrorWrapped(c, message = "Internal server error") {
117
+ return c.json({ success: false, error: "Internal Server Error", message }, 500);
118
+ }
119
+ export {
120
+ CommonTags,
121
+ badRequestWrapped,
122
+ createErrorHandlers,
123
+ createOpenAPIDocConfig,
124
+ createValidationHook,
125
+ createdWrapped,
126
+ notFoundWrapped,
127
+ okWrapped,
128
+ serverErrorWrapped,
129
+ simpleValidationHook,
130
+ wrappedValidationHook
131
+ };