CodeSampleX

Exemple

path-to-regexp 8.4.2: Handle path-to-regexp 8+ return object shape, wildcard segment arrays in match, and array parameter requirements in compile

Échantillon vérifié pour npm path-to-regexp 8.4.2: Handle path-to-regexp 8+ return object shape, wildcard segment arrays in match, and array parameter…

sha256:e0298fc83c817a07d766085d151aa4eaec44730bbe955f1b1e8167d5e3018a43

Ce réseau offre une seule chose : un échantillon qui compile. Il l'a exécuté dans un bac à sable et conservé le reçu signé. Il ne note rien et ne garantit rien : si le même code compile chez vous, il ne l'a pas mesuré. Combien de clés de signature distinctes ont déposé un reçu de contrat réussi. Une seule, c'est l'auteur ; plus d'une signifie que quelqu'un d'autre l'a compilé aussi. Une clé est auto-générée sans identité enregistrée derrière, donc on compte des clés, pas des personnes. MIT-0

Preuves d'exécution

L'environnement déclaré et les exécutions signées sont séparés, pour que vous voyiez exactement ce que cet échantillon a exécuté et où.

Base de preuve
Contrat signé réussi
Reçus de vérification
3
Clés de signature qui l’ont compilé
3
Environnement déclaré node linux x64 node node npm

Environnements des exécutions de vérification

Environnement Contrat Étapes Exécution
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-16
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-07

Cas

HOW
Objectif
Handle path-to-regexp 8+ return object shape, wildcard segment arrays in match, and array parameter requirements in compile
Paquets
Symboles
  • pathToRegexp
  • match
  • compile
  • parse
  • stringify
Environnement
node
Créé
2026-08-16T06:21:43Z

Contrat

  1. assert pathToRegexp returns { regexp, keys } object rather than a bare RegExp
  2. assert passing a keys array as second argument to pathToRegexp is ignored and not populated
  3. assert match parses wildcard *param into an array of path segment strings rather than a raw string
  4. assert compile requires wildcard parameters to be non-empty arrays and rejects strings with TypeError
  5. assert compile throws TypeError for missing required parameters but permits omitted optional groups
  6. assert parse returns a TokenData instance with nested group tokens for optional segments

Fichiers

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/index.mjs
  • test/contract.mjs

Télécharger l’artefact source (tar.gz)

Code source

NOTES.md
# Notes

### search_known_solution result
`search_known_solution` returned COMPATIBLE sample `sha256:bb95306081deb0d7bdd735b0ca6f0182f77216c5d0426ee0146b7562efe7facc` ("Fix express 4 routes that throw 'Missing parameter name' or 'Unexpected ?' at startup after upgrading to express 5"), which focuses on Express 5 HTTP router registration failures. This sample proves the direct API contract of `path-to-regexp` v8+.

### What a model would have written instead
A model would have called `const keys = []; const regexp = pathToRegexp('/users/:id', keys);` expecting a bare `RegExp` return value with in-place populated `keys`, expected `match('/*splat')` to return `params.splat` as a flat string `"a/b/c"`, and passed `{ splat: "a/b/c" }` to `compile('/*splat')`.

### How the wrong version fails
Loudly with `TypeError: regexp.exec is not a function` when calling regex methods directly on `pathToRegexp`'s returned object and `TypeError: Expected "splat" to be a non-empty array` in `compile()`, plus silent downstream logic bugs when consuming `params.splat` as a string instead of an array of path segment tokens.
csx.json
{"case":{"caseId":"case:sha256:f4dec6a6efa12830760f4d8f1fa97d587f9438d29755c0058a81047fd2b7ebc1","contract":["assert pathToRegexp returns { regexp, keys } object rather than a bare RegExp","assert passing a keys array as second argument to pathToRegexp is ignored and not populated","assert match parses wildcard *param into an array of path segment strings rather than a raw string","assert compile requires wildcard parameters to be non-empty arrays and rejects strings with TypeError","assert compile throws TypeError for missing required parameters but permits omitted optional groups","assert parse returns a TokenData instance with nested group tokens for optional segments"],"goal":"Handle path-to-regexp 8+ return object shape, wildcard segment arrays in match, and array parameter requirements in compile","kind":"HOW","packages":["pkg:npm/path-to-regexp@8.4.2"],"schemaVersion":1,"symbols":["pathToRegexp","match","compile","parse","stringify"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/path-to-regexp@8.4.2"],"schemaVersion":1,"symbols":["pathToRegexp","match","compile","parse","stringify"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "csx-sample-npm-path-to-regexp-api",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "csx-sample-npm-path-to-regexp-api",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "path-to-regexp": "8.4.2"
      }
    },
    "node_modules/path-to-regexp": {
      "version": "8.4.2",
      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
      "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
      "license": "MIT",
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    }
  }
}
package.json
{
  "name": "csx-sample-npm-path-to-regexp-api",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "dependencies": {
    "path-to-regexp": "8.4.2"
  }
}
src/index.mjs
import { pathToRegexp, match, compile, parse, stringify, TokenData, PathError } from 'path-to-regexp';

/**
 * Creates a route matching function using path-to-regexp 8+ API.
 * In v8+, wildcard tokens (*param) return arrays of path segments.
 */
export function createRouteMatcher(pattern, options = {}) {
  const matchFn = match(pattern, options);
  return (pathname) => {
    const result = matchFn(pathname);
    if (!result) return null;
    return {
      matchedPath: result.path,
      params: result.params,
    };
  };
}

/**
 * Creates a reverse path compiler using path-to-regexp 8+ API.
 * In v8+, wildcard parameters require a non-empty array of strings.
 */
export function createPathBuilder(pattern, options = {}) {
  const compileFn = compile(pattern, options);
  return (params = {}) => compileFn(params);
}

/**
 * Compiles a pattern to RegExp and keys metadata.
 * In v8+, pathToRegexp returns { regexp, keys } object rather than a RegExp instance.
 */
export function inspectRoute(pattern, options = {}) {
  const { regexp, keys } = pathToRegexp(pattern, options);
  const tokenData = parse(pattern, options);
  return {
    regexp,
    keys,
    tokenData,
    stringified: stringify(tokenData),
  };
}

/**
 * Helper to capture errors thrown when executing a callback.
 */
export function attempt(fn) {
  try {
    return { result: fn(), error: null };
  } catch (err) {
    return { result: null, error: err };
  }
}
test/contract.mjs
import { strict as assert } from 'node:assert';
import {
  pathToRegexp,
  match,
  compile,
  parse,
  stringify,
  TokenData,
  PathError,
} from 'path-to-regexp';
import {
  createRouteMatcher,
  createPathBuilder,
  inspectRoute,
  attempt,
} from '../src/index.mjs';

// ============================================================================
// 1. pathToRegexp: Return value shape and keys handling
// ============================================================================

// In path-to-regexp v8+, pathToRegexp returns an object { regexp, keys }.
// Competent models trained on older versions expect a direct RegExp return value
// and expect keys to be passed as a mutable array in the second argument.
const routeMeta = pathToRegexp('/users/:userId');

assert.equal(typeof routeMeta, 'object', 'pathToRegexp must return an object');
assert.equal(
  routeMeta instanceof RegExp,
  false,
  'pathToRegexp return value is NOT a RegExp instance directly'
);
assert.ok(
  routeMeta.regexp instanceof RegExp,
  'routeMeta.regexp must be a RegExp instance'
);
assert.deepEqual(
  routeMeta.keys,
  [{ type: 'param', name: 'userId' }],
  'routeMeta.keys contains structured Key objects'
);

// Calling .exec() directly on the returned object without destructuring throws TypeError
const naiveExec = attempt(() => routeMeta.exec('/users/123'));
assert.ok(
  naiveExec.error instanceof TypeError,
  'Calling .exec() directly on pathToRegexp output must throw TypeError'
);

// Passing a legacy keys array as second argument does NOT populate the array
const legacyKeys = [];
const legacyCall = pathToRegexp('/users/:userId', legacyKeys);
assert.equal(
  legacyKeys.length,
  0,
  'Legacy keys array passed as 2nd argument is NOT mutated in v8+'
);
assert.deepEqual(
  legacyCall.keys,
  [{ type: 'param', name: 'userId' }],
  'Keys are returned on the result object instead'
);

// Verify matching behavior with extracted RegExp
const execMatch = routeMeta.regexp.exec('/users/42');
assert.ok(execMatch !== null, 'RegExp must match valid path');
assert.equal(execMatch[0], '/users/42');
assert.equal(execMatch[1], '42');

// ============================================================================
// 2. match: Return value shapes, wildcards as arrays, and optional groups
// ============================================================================

// Standard single parameter matching
const matchUser = match('/users/:id');
const userMatch = matchUser('/users/alice');
assert.notEqual(userMatch, false);
assert.equal(userMatch.path, '/users/alice');
assert.equal(userMatch.params.id, 'alice');

// Non-matching path returns false (boolean literal)
assert.equal(matchUser('/other/path'), false, 'Non-match returns false');

// Wildcard matching: path-to-regexp v8 returns path segments as an Array<string>
// Competent models expect a string capture like "docs/2026/report.pdf"
const matchWildcard = match('/*splat');
const wildcardMatch = matchWildcard('/files/docs/2026/report.pdf');
assert.notEqual(wildcardMatch, false);
assert.equal(wildcardMatch.path, '/files/docs/2026/report.pdf');
assert.ok(
  Array.isArray(wildcardMatch.params.splat),
  'Wildcard capture MUST be an array of segment strings'
);
assert.deepEqual(
  wildcardMatch.params.splat,
  ['files', 'docs', '2026', 'report.pdf']
);

// Wildcard with prefix: capture contains remaining segments
const matchAssets = match('/assets/*filepath');
const assetMatch = matchAssets('/assets/images/icons/logo.svg');
assert.notEqual(assetMatch, false);
assert.deepEqual(assetMatch.params.filepath, ['images', 'icons', 'logo.svg']);

// Optional groups: {/:param} syntax
const matchOptional = match('/posts{/:postId}');
const withOptional = matchOptional('/posts/99');
assert.notEqual(withOptional, false);
assert.equal(withOptional.params.postId, '99');

const withoutOptional = matchOptional('/posts');
assert.notEqual(withoutOptional, false);
assert.equal(withoutOptional.path, '/posts');
assert.equal(withoutOptional.params.postId, undefined);

// Case sensitivity and trailing delimiter defaults
const matchCase = match('/items/:item');
assert.notEqual(
  matchCase('/ITEMS/book'),
  false,
  'Default matching is case-insensitive'
);
assert.notEqual(
  matchCase('/items/book/'),
  false,
  'Default matching permits trailing delimiter'
);

// Sensitive and non-trailing option overrides
const matchStrict = match('/items/:item', { sensitive: true, trailing: false });
assert.equal(
  matchStrict('/ITEMS/book'),
  false,
  'sensitive: true rejects uppercase path'
);
assert.equal(
  matchStrict('/items/book/'),
  false,
  'trailing: false rejects trailing delimiter'
);

// ============================================================================
// 3. compile: Parameter validation, wildcard array requirement, and missing params
// ============================================================================

// Standard parameter compilation
const toUserUrl = compile('/users/:id');
assert.equal(toUserUrl({ id: 'bob' }), '/users/bob');
assert.equal(
  toUserUrl({ id: 'special spaces & chars' }),
  '/users/special%20spaces%20%26%20chars',
  'compile encodes parameters by default'
);

// Missing required parameter throws TypeError
const missingReqParam = attempt(() => toUserUrl({}));
assert.ok(
  missingReqParam.error instanceof TypeError,
  'Missing required param must throw TypeError'
);
assert.match(
  missingReqParam.error.message,
  /Missing parameters: id/,
  'Error specifies the missing parameter name'
);

// Optional parameter groups omit cleanly when parameter is missing
const toPostUrl = compile('/posts{/:postId}');
assert.equal(
  toPostUrl({}),
  '/posts',
  'Omitting optional group param produces clean base path'
);
assert.equal(toPostUrl({ postId: '123' }), '/posts/123');

// Wildcard compilation: in v8+, wildcards REQUIRE a non-empty array of strings
const toFileUrl = compile('/*filepath');

// Supplying a proper array joins with delimiter
assert.equal(
  toFileUrl({ filepath: ['docs', 'guides', 'intro.md'] }),
  '/docs/guides/intro.md'
);

// Supplying a string throws TypeError (models frequently pass string "docs/guides/intro.md")
const stringWildcardAttempt = attempt(() =>
  toFileUrl({ filepath: 'docs/guides/intro.md' })
);
assert.ok(
  stringWildcardAttempt.error instanceof TypeError,
  'Passing string to wildcard parameter must throw TypeError'
);
assert.match(
  stringWildcardAttempt.error.message,
  /Expected "filepath" to be a non-empty array/
);

// Supplying an empty array throws TypeError
const emptyArrayWildcardAttempt = attempt(() => toFileUrl({ filepath: [] }));
assert.ok(
  emptyArrayWildcardAttempt.error instanceof TypeError,
  'Passing empty array to wildcard parameter must throw TypeError'
);
assert.match(
  emptyArrayWildcardAttempt.error.message,
  /Expected "filepath" to be a non-empty array/
);

// Disabling encode option: raw strings accepted without encoding
const toRawUrl = compile('/users/:id', { encode: false });
assert.equal(toRawUrl({ id: '%3A%2F' }), '/users/%3A%2F');

// ============================================================================
// 4. parse and stringify: TokenData AST representation
// ============================================================================

const parsedData = parse('/orgs/:orgId/repos{/*subpath}');
assert.ok(
  parsedData instanceof TokenData,
  'parse must return a TokenData instance'
);
assert.equal(parsedData.originalPath, '/orgs/:orgId/repos{/*subpath}');

// Verify token AST structure
assert.deepEqual(parsedData.tokens, [
  { type: 'text', value: '/orgs/' },
  { type: 'param', name: 'orgId' },
  { type: 'text', value: '/repos' },
  {
    type: 'group',
    tokens: [
      { type: 'text', value: '/' },
      { type: 'wildcard', name: 'subpath' },
    ],
  },
]);

// stringify roundtrips TokenData back into path string
assert.equal(
  stringify(parsedData),
  '/orgs/:orgId/repos{/*subpath}',
  'stringify reconstructs exact path pattern'
);

// Syntax validation errors: unescaped legacy symbols throw PathError (TypeError subclass)
const legacyRegexAttempt = attempt(() => parse('/user/:id(\\d+)'));
assert.ok(
  legacyRegexAttempt.error instanceof PathError,
  'Unescaped regexp parentheses must throw PathError'
);
assert.ok(
  legacyRegexAttempt.error instanceof TypeError,
  'PathError inherits from TypeError'
);

// ============================================================================
// 5. Higher-level module wrappers from src/index.mjs
// ============================================================================

const routeMatcher = createRouteMatcher('/api/v1/users/:id{/*details}');
const matched = routeMatcher('/api/v1/users/42/settings/security');
assert.equal(matched.matchedPath, '/api/v1/users/42/settings/security');
assert.equal(
  Object.getPrototypeOf(matched.params),
  null,
  'params is a null-prototype dictionary created via Object.create(null)'
);
assert.equal(matched.params.id, '42');
assert.deepEqual(matched.params.details, ['settings', 'security']);

const pathBuilder = createPathBuilder('/api/v1/users/:id{/*details}');
assert.equal(
  pathBuilder({ id: '42', details: ['settings', 'security'] }),
  '/api/v1/users/42/settings/security'
);
assert.equal(pathBuilder({ id: '42' }), '/api/v1/users/42');

const inspection = inspectRoute('/api/v1/users/:id{/*details}');
assert.ok(inspection.regexp instanceof RegExp);
// Keys array tracks every RegExp capturing group position across alternation branches
assert.deepEqual(inspection.keys, [
  { type: 'param', name: 'id' },
  { type: 'wildcard', name: 'details' },
  { type: 'param', name: 'id' },
]);
assert.equal(inspection.stringified, '/api/v1/users/:id{/*details}');

console.log(
  'CONTRACT PASS: path-to-regexp 8+ { regexp, keys } return shape, wildcard array captures, compile array requirements, and TokenData AST proven'
);

Seeder d'origine

csx-seed