@hadoku/worker-utils
v1.7.23
Published
Shared utilities for Cloudflare Workers
Downloads
5,117
Readme
Worker Utilities - Quick Reference
Shared utilities for Cloudflare Workers. Generic, reusable patterns extracted from production workers.
Import: import { ... } from '@wolffm/worker-utils'
Files
workers/shared/
├── types.ts # TypeScript type definitions
├── auth.ts # Authentication middleware
├── context.ts # Request context extraction
├── validation.ts # Input validation
├── cors.ts # CORS configuration
├── logging.ts # Structured logging
├── masking.ts # Data masking for safe logging
├── responses.ts # HTTP response helpers
└── index.ts # Barrel exportauth.ts
Edge-verified authentication:
A worker does NOT resolve keys. edge-router resolves the caller against its KV
key registry, then stamps X-Hadoku-Tier alongside an X-Edge-Auth provenance
header; createEdgeAuth verifies the provenance against EDGE_AUTH_SECRET
before trusting the tier, and degrades to public when it doesn't verify.
Tier priority: admin > service > friend > public. service is for
worker-to-worker / sibling-app traffic.
Removed in 1.3.0:
createHadokuAuth,HadokuAuthEnv,parseKeysFromEnv,parseKeysWithNames,validateKeyAndGetType. They read theADMIN_KEYS/SERVICE_KEYS/FRIEND_KEYSenv arrays, which were retired 2026-07-26 — so they had already stopped authenticating anyone.createEdgeAuthis a drop-in (same context shape, sameextendoption).
// Standard hadoku ecosystem auth:
import { createEdgeAuth } from '@wolffm/worker-utils';
app.use('*', createEdgeAuth());
// ↑ verifies X-Edge-Auth, then trusts the edge-stamped X-Hadoku-Tier
// Generic helper (single-key-per-tier case only):
app.use(
'*',
createKeyAuth((env) => ({
[env.SOME_SINGLE_KEY]: 'admin',
}))
);Functions:
createKeyAuth(keyMap, options?)- Simple key → userType mappingcreateAuthMiddleware(config)- Custom auth logicvalidateKeyAndGetType(key, adminKeys, friendKeys, serviceKeys?)- Validate key & return userType (4-arg form opts into the'service'tier)parseKeysFromEnv(jsonString)- Parse key config from env varsgetAuthContext(c)- Get auth from contextrequireMinTier('friend')- Gate a route at a MINIMUM tier (public < friend < service < wife < admin); every higher tier is admitted by ranktierAtLeast(auth, 'admin')- Same rank check for an already-resolved auth context, for in-handler branching. Never compareauth.userTypeto a stringTIER_RANK/Tier- The rank table and tier union, shared by the edge gate and the in-worker gate- ~~
requireUserType([...])~~ - Removed 2026-07-25. Translate torequireMinTier(<lowest tier in the list>)
Adds to context: c.get('authContext') → { userType, isAdmin, isFriend, isPublic }
masking.ts
Mask sensitive data for logging:
logRequest('POST', '/session/create', {
sessionId: maskSessionId(sessionId), // "1a2b3c4d5e6f7g8h"
key: maskKey(authKey), // "admin-secre" (50%)
});Functions:
maskKey(key, percentage?)- Mask key (show first 50% by default, no suffix)maskSessionId(id)- Mask session ID (show first 16 chars, no suffix)maskEmail(email)- Mask email (show first char + domain)redactFields(obj, fields)- Redact sensitive fields from objects
Constants:
MASKING.KEY_PERCENTAGE- Percentage of key to show (0.5 = 50%)MASKING.KEY_MIN_LENGTH- Minimum characters to show (4)MASKING.SESSION_ID_PREFIX_LENGTH- Default session ID prefix (16)SENSITIVE_FIELDS- Common sensitive field names
Examples:
maskKey('admin-secret-key-123'); // "admin-secre"
maskSessionId('1a2b3c4d5e6f7g8h9i0j'); // "1a2b3c4d5e6f7g8h"
maskEmail('[email protected]'); // "j***@example.com"
redactFields({ username: 'john', password: 'secret' }, ['password']);
// { username: 'john', password: '[REDACTED]' }Extract common fields:
const { userId, sessionId } = extractUserContext(c);
const boardId = extractField(c, ['query:boardId', 'body:boardId'], 'main');Functions:
extractUserContext(c)- Get userId, sessionIdextractField(c, sources, default?, transform?)- Single field from multiple sourcesextractContext(c, config)- Multiple fields at onceextractPagination(c)- Get page, limit, offsetextractSorting(c)- Get sortBy, sortOrdergetRequestMetadata(c)- Get IP, userAgent, country, etc.
Source format: 'header:X-User-Id', 'query:boardId', 'body:title'
validation.ts
Validate request data:
const validation = validateFields(body, [
{ field: 'id', required: true, type: 'string' },
{ field: 'title', required: true, minLength: 1, maxLength: 200 },
]);
if (!validation.valid) return validationError(c, validation.errors);Functions:
validateFields(data, rules)- Full validation with rulesrequireFields(data, ['id', 'title'])- Quick required checkvalidateBody(rules)- Middleware for auto-validationcreateValidator(rules)- Reusable validator function
CommonRules:
CommonRules.id()- UUID validationCommonRules.title({ maxLength })- String 1-200 charsCommonRules.email(required)- Email patternCommonRules.url(required)- URL patternCommonRules.enum('status', ['active', 'inactive'])- Enum validationCommonRules.array('tags', options)- Array validationCommonRules.number('age', { min, max, integer })- Number constraints
cors.ts
Standard Hadoku CORS:
app.use('*', createHadokuCors());Functions:
createHadokuCors(additionalOrigins?)- Hadoku defaults + customcreateCorsMiddleware(config)- Full custom configmatchOrigin(origin, pattern)- Wildcard matching
Presets:
CORSPresets.hadoku- Production Hadoku originsCORSPresets.development- Includes localhostCORSPresets.production(origins)- Custom productionCORSPresets.publicApi- Public API (all origins)
logging.ts
Log requests and errors:
logRequest('GET', '/api/items', { userId, boardId });
logError('POST', '/api/items', 'Validation failed', { errors });Functions:
logRequest(method, path, context?, level?)- Log HTTP requestslogError(method, path, error, context?)- Log errorscreateLogger(config)- Custom logger instancecreateRequestLogger(method, path, baseContext)- Request-scoped loggerstartTimer()- Performance timing (timer.end(message, context))redactFields(obj, fields)- Remove sensitive data
Constants:
SENSITIVE_FIELDS- Common sensitive field names (password, apiKey, token, etc.)
responses.ts
Standard HTTP responses:
return ok(c, { items });
return created(c, { item });
return badRequest(c, 'Missing field: id');
return notFound(c, 'Task');Success:
ok(c, data, message?)- 200created(c, data, message?)- 201noContent(c)- 204
Errors:
badRequest(c, message, details?)- 400unauthorized(c, message?)- 401forbidden(c, message?)- 403notFound(c, resource?)- 404conflict(c, message, details?)- 409serverError(c, message, details?)- 500validationError(c, errors)- 400 with validation details
Health & Error Handling:
healthCheck(c, service, checks?)- Health check responsecreateHealthCheckHandler(service, config)- Health check handler factoryhandleError(c, error, message)- Generic error handlerwithErrorHandling(handler)- Async error wrapper
types.ts
Import types:
import type {
UserType, // 'admin' | 'service' | 'friend' | 'public'
AuthContext, // { userType, userId?, isAdmin, isFriend, isPublic }
ValidationRule, // { field, required?, type?, minLength?, ... }
ValidationResult, // { valid, errors }
LoggerConfig, // { prefix?, minLevel?, includeTimestamp? }
CORSConfig, // { origins, methods, allowedHeaders, ... }
} from '@wolffm/worker-utils';Common Patterns
Minimal Worker
import { Hono } from 'hono';
import {
createKeyAuth,
createHadokuCors,
extractUserContext,
ok,
} from '@wolffm/worker-utils';
const app = new Hono();
app.use('*', createHadokuCors());
app.use('*', createEdgeAuth());
// ↑ verifies X-Edge-Auth, trusts the edge-stamped tier
app.get('/api/items', async (c) => {
const { userId } = extractUserContext(c);
const items = await getItems(userId);
return ok(c, { items });
});
export default app;With Validation
app.post('/api/items', async (c) => {
const body = await c.req.json();
const validation = validateFields(body, [
CommonRules.id(),
CommonRules.title({ maxLength: 200 }),
]);
if (!validation.valid) {
return validationError(c, validation.errors);
}
const item = await createItem(body);
return created(c, { item });
});With Logging
app.post('/api/items', async (c) => {
const { userId } = extractUserContext(c);
logRequest('POST', '/api/items', { userId });
try {
const item = await createItem(body);
return created(c, { item });
} catch (error) {
logError('POST', '/api/items', error.message, { userId });
return serverError(c, 'Failed to create item');
}
});See Also
- WORKERS.md - Worker index and setup guide
- docs/child-apps/NEW_CHILD_APP_CHECKLIST.md - Backend worker setup (see "Optional: Backend API" section)
- AGENTS.md - Worker debugging and common patterns
