clockworklabs/SpacetimeDB · error · TypeError

Route paths may contain only ${ACCEPTABLE_ROUTE_PATH_CHARS_H

Error message

Route paths may contain only ${ACCEPTABLE_ROUTE_PATH_CHARS_HUMAN_DESCRIPTION}: ${path}

What it means

assertValidPath also enforces a strict character whitelist for route paths: lowercase a-z, digits 0-9, '-', '_', '~' and '/'. These are the RFC 3986 unreserved characters, so routes match without percent-decoding ambiguity. Any other character (uppercase letters, ':', '{', '.', '*', spaces) throws a TypeError listing the allowed set.

Source

Thrown at crates/bindings-typescript/src/server/http_handlers.ts:107

}

function characterIsAcceptableForRoutePath(c: string) {
  return (
    (c >= 'a' && c <= 'z') ||
    (c >= '0' && c <= '9') ||
    c === '-' ||
    c === '_' ||
    c === '~' ||
    c === '/'
  );
}

function assertValidPath(path: string) {
  if (path !== '' && !path.startsWith('/')) {
    throw new TypeError(`Route paths must start with \`/\`: ${path}`);
  }
  if (![...path].every(characterIsAcceptableForRoutePath)) {
    throw new TypeError(
      `Route paths may contain only ${ACCEPTABLE_ROUTE_PATH_CHARS_HUMAN_DESCRIPTION}: ${path}`
    );
  }
}

function routesOverlap(a: RouteSpec, b: RouteSpec) {
  const methodsMatch = (left: HttpMethod, right: HttpMethod) => {
    if (left.tag !== right.tag) {
      return false;
    }
    if (left.tag === 'Extension' && right.tag === 'Extension') {
      return left.value === right.value;
    }
    return true;
  };

  return (
    a.path === b.path &&

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Replace path parameters with a static segment and read identifiers from the query string or request body inside the handler
  2. Lowercase the whole path and strip unsupported characters
  3. If per-id routes are required, parse ctx.request.uri inside one handler and dispatch manually

Example fix

// before
router.get('/users/:id', getUser);

// after
router.get('/users', getUser); // read ?id=... from the query string inside the handler
Defensive patterns

Strategy: validation

Validate before calling

const ROUTE_PATH_RE = /^[a-z0-9\-_~/]*$/;
function isValidRoutePath(path: string): boolean {
  return (path === '' || path.startsWith('/')) && ROUTE_PATH_RE.test(path);
}
// reject early with a clear message:
if (!isValidRoutePath(p)) throw new Error(`bad route path: ${p}`);

Type guard

const isStaticRoutePath = (p: string): p is string =>
  (p === '' || p.startsWith('/')) && /^[a-z0-9\-_~/]*$/.test(p);

Prevention

When it happens

Trigger: Writing REST-style parameterized paths like '/users/:id' or '/users/{id}' (path parameters are not supported by this router); camelCase or uppercase segments such as '/api/Users'; extension routes like '/sitemap.xml'.

Common situations: Coming from Express/Koa/Fastify where ':param' syntax is standard; generated route tables containing uppercase or dots; assuming dynamic path segments exist because other frameworks offer them.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/f066af88b129cda3. Report an issue: GitHub.