Exemple
path-to-regexp 8.4.2: Handle silent breaking changes in path-to-regexp v8 across pathToRegexp return shapes, match params prototype, wildcard array segmentation, and compile validation
Échantillon vérifié pour npm path-to-regexp 8.4.2: Handle silent breaking changes in path-to-regexp v8 across pathToRegexp return shapes, match params…
sha256:cb6c1282c918029ff20cfb1014e9ce00ed585175193bd4ad986c134abb431d02
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 silent breaking changes in path-to-regexp v8 across pathToRegexp return shapes, match params prototype, wildcard array segmentation, and compile validation
- Paquets
- Symboles
-
- pathToRegexp
- match
- compile
- Environnement
- node
- Créé
- 2026-08-16T06:21:49Z
Contrat
- assert pathToRegexp returns { regexp, keys } and does not mutate an array passed in the second parameter position
- assert match parses wildcard *splat matches into string arrays of segments rather than flat strings
- assert compile requires non-empty arrays for wildcard tokens and throws TypeError for flat strings or empty arrays
- assert match returns params with a null prototype lacking hasOwnProperty and omits absent optional parameter keys
- assert compile silently drops an entire multi-parameter optional group when any of its parameters are omitted
- assert pathToRegexp enables trailing delimiter matching by default
Fichiers
- NOTES.md
- csx.json
- package-lock.json
- package.json
- test/contract.mjs
Code source
# Notes
## Prior search_known_solution Result
`search_known_solution` returned sample `sha256:bb95306081deb0d7bdd735b0ca6f0182f77216c5d0426ee0146b7562efe7facc` ("Fix express 4 routes that throw \"Missing parameter name\" or \"Unexpected ?\" at startup after upgrading to express 5"), which covers Express 5 route definition syntax errors (loud `TypeError` on invalid route syntax like `*` or `?`). This sample covers a different trap: the silent API and behavioral contract changes introduced in `path-to-regexp` v8 across `pathToRegexp`, `match`, and `compile`.
## What a Model Would Have Written
A model would write `const keys = []; const regexp = pathToRegexp('/users/:id', keys);` expecting in-place mutation of `keys`, assume `match('/*splat')` returns a string for `params.splat`, pass strings like `{ splat: 'docs/intro.md' }` to `compile('/*splat')`, and access `params.hasOwnProperty('id')`.
## Failure Mode
Passing `keys` as the second argument to `pathToRegexp` silently leaves `keys` empty (`[]`) because the second argument is now options, matching `/*splat` silently produces an array of segments (e.g. `['single']` for a single segment) rather than a string breaking downstream string methods, and calling `params.hasOwnProperty` throws `TypeError: params.hasOwnProperty is not a function` at runtime because `params` is created with a `null` prototype (`Object.create(null)`).
{"case":{"caseId":"case:sha256:4a14c8c2e9afa5ede25191b986e6dc1649cdcb464de0eb058b44b2e125df00ff","contract":["assert pathToRegexp returns { regexp, keys } and does not mutate an array passed in the second parameter position","assert match parses wildcard *splat matches into string arrays of segments rather than flat strings","assert compile requires non-empty arrays for wildcard tokens and throws TypeError for flat strings or empty arrays","assert match returns params with a null prototype lacking hasOwnProperty and omits absent optional parameter keys","assert compile silently drops an entire multi-parameter optional group when any of its parameters are omitted","assert pathToRegexp enables trailing delimiter matching by default"],"goal":"Handle silent breaking changes in path-to-regexp v8 across pathToRegexp return shapes, match params prototype, wildcard array segmentation, and compile validation","kind":"HOW","packages":["pkg:npm/path-to-regexp@8.4.2"],"schemaVersion":1,"symbols":["pathToRegexp","match","compile"]},"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"],"verifierAdapter":"node-typescript@1"}
{
"name": "npm-path-to-regexp-version",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "npm-path-to-regexp-version",
"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"
}
}
}
}
{
"name": "npm-path-to-regexp-version",
"version": "1.0.0",
"private": true,
"license": "MIT-0",
"type": "module",
"scripts": {
"test": "node test/contract.mjs"
},
"dependencies": {
"path-to-regexp": "8.4.2"
}
}
import assert from "node:assert/strict";
import { pathToRegexp, match, compile, parse, stringify } from "path-to-regexp";
// 1. Signature change: pathToRegexp returns { regexp, keys }, does NOT mutate keys passed as 2nd arg
{
const oldKeysArray = [];
const result = pathToRegexp("/users/:id", oldKeysArray);
// In v6/earlier: pathToRegexp returned RegExp and populated the passed keys array in-place.
// In v8: pathToRegexp returns { regexp, keys }, 2nd arg is options object, so oldKeysArray remains empty.
assert.equal(result instanceof RegExp, false, "pathToRegexp should return an object, not RegExp");
assert.equal(oldKeysArray.length, 0, "Passed array is not populated as keys");
assert.equal(result.regexp instanceof RegExp, true, "result.regexp is the compiled RegExp");
assert.equal(result.keys.length, 1, "result.keys contains the parsed keys");
assert.equal(result.keys[0].name, "id");
}
// 2. Wildcard segmentation in match(): wildcard returns array of segments, never a raw string
{
const fn = match("/*splat");
// Multi-segment match: splat is split by delimiter into an array of decoded strings
const m1 = fn("/docs/guides/intro.md");
assert.ok(m1, "Route should match");
assert.deepEqual(m1.params.splat, ["docs", "guides", "intro.md"]);
// Single-segment match: splat is STILL an array of length 1, not a string
const m2 = fn("/single");
assert.ok(m2, "Route should match");
assert.deepEqual(m2.params.splat, ["single"]);
assert.equal(typeof m2.params.splat, "object", "Wildcard params must be an Array, not string");
assert.equal(Array.isArray(m2.params.splat), true);
}
// 3. compile() requires an array for wildcards and rejects string or empty array
{
const toPath = compile("/*splat");
// Valid array input builds path with delimiter joined
assert.equal(toPath({ splat: ["docs", "intro.md"] }), "/docs/intro.md");
assert.equal(toPath({ splat: ["single"] }), "/single");
// Providing a string throws TypeError instead of embedding the string
assert.throws(
() => toPath({ splat: "docs/intro.md" }),
/Expected "splat" to be a non-empty array/
);
// Empty array throws TypeError
assert.throws(
() => toPath({ splat: [] }),
/Expected "splat" to be a non-empty array/
);
}
// 4. match() returns null-prototype params without hasOwnProperty, and omits absent optional keys
{
const fn = match("/users{/:id}");
// Optional parameter absent: params has no 'id' property and prototype is null
const mEmpty = fn("/users");
assert.ok(mEmpty, "Route should match /users");
assert.equal(Object.getPrototypeOf(mEmpty.params), null, "params must have null prototype");
assert.equal(mEmpty.params.hasOwnProperty, undefined, "params does not inherit Object.prototype");
assert.equal("id" in mEmpty.params, false, "Absent optional param key must not exist in params");
// Optional parameter present
const mPresent = fn("/users/42");
assert.ok(mPresent, "Route should match /users/42");
assert.equal(mPresent.params.id, "42");
}
// 5. compile() drops entire optional group if any parameter within the group is omitted
{
const toRepoPath = compile("/repo{/:owner/:name}");
// All parameters provided in group
assert.equal(toRepoPath({ owner: "example", name: "project" }), "/repo/example/project");
// Partial parameters provided in group: group is silently omitted entirely without error
assert.equal(toRepoPath({ owner: "example" }), "/repo");
assert.equal(toRepoPath({ name: "project" }), "/repo");
assert.equal(toRepoPath({}), "/repo");
}
// 6. Default trailing delimiter matching in pathToRegexp
{
const { regexp } = pathToRegexp("/api/v1");
// trailing: true by default allows optional trailing slash
assert.equal(regexp.test("/api/v1"), true);
assert.equal(regexp.test("/api/v1/"), true);
assert.equal(regexp.test("/api/v1//"), false);
}