npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 export

auth.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 the ADMIN_KEYS / SERVICE_KEYS / FRIEND_KEYS env arrays, which were retired 2026-07-26 — so they had already stopped authenticating anyone. createEdgeAuth is a drop-in (same context shape, same extend option).

// 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 mapping
  • createAuthMiddleware(config) - Custom auth logic
  • validateKeyAndGetType(key, adminKeys, friendKeys, serviceKeys?) - Validate key & return userType (4-arg form opts into the 'service' tier)
  • parseKeysFromEnv(jsonString) - Parse key config from env vars
  • getAuthContext(c) - Get auth from context
  • requireMinTier('friend') - Gate a route at a MINIMUM tier (public < friend < service < wife < admin); every higher tier is admitted by rank
  • tierAtLeast(auth, 'admin') - Same rank check for an already-resolved auth context, for in-handler branching. Never compare auth.userType to a string
  • TIER_RANK / Tier - The rank table and tier union, shared by the edge gate and the in-worker gate
  • ~~requireUserType([...])~~ - Removed 2026-07-25. Translate to requireMinTier(<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, sessionId
  • extractField(c, sources, default?, transform?) - Single field from multiple sources
  • extractContext(c, config) - Multiple fields at once
  • extractPagination(c) - Get page, limit, offset
  • extractSorting(c) - Get sortBy, sortOrder
  • getRequestMetadata(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 rules
  • requireFields(data, ['id', 'title']) - Quick required check
  • validateBody(rules) - Middleware for auto-validation
  • createValidator(rules) - Reusable validator function

CommonRules:

  • CommonRules.id() - UUID validation
  • CommonRules.title({ maxLength }) - String 1-200 chars
  • CommonRules.email(required) - Email pattern
  • CommonRules.url(required) - URL pattern
  • CommonRules.enum('status', ['active', 'inactive']) - Enum validation
  • CommonRules.array('tags', options) - Array validation
  • CommonRules.number('age', { min, max, integer }) - Number constraints

cors.ts

Standard Hadoku CORS:

app.use('*', createHadokuCors());

Functions:

  • createHadokuCors(additionalOrigins?) - Hadoku defaults + custom
  • createCorsMiddleware(config) - Full custom config
  • matchOrigin(origin, pattern) - Wildcard matching

Presets:

  • CORSPresets.hadoku - Production Hadoku origins
  • CORSPresets.development - Includes localhost
  • CORSPresets.production(origins) - Custom production
  • CORSPresets.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 requests
  • logError(method, path, error, context?) - Log errors
  • createLogger(config) - Custom logger instance
  • createRequestLogger(method, path, baseContext) - Request-scoped logger
  • startTimer() - 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?) - 200
  • created(c, data, message?) - 201
  • noContent(c) - 204

Errors:

  • badRequest(c, message, details?) - 400
  • unauthorized(c, message?) - 401
  • forbidden(c, message?) - 403
  • notFound(c, resource?) - 404
  • conflict(c, message, details?) - 409
  • serverError(c, message, details?) - 500
  • validationError(c, errors) - 400 with validation details

Health & Error Handling:

  • healthCheck(c, service, checks?) - Health check response
  • createHealthCheckHandler(service, config) - Health check handler factory
  • handleError(c, error, message) - Generic error handler
  • withErrorHandling(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