CodeSampleX

Beispiel

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

Verifiziertes Beispiel für npm path-to-regexp 8.4.2: Handle silent breaking changes in path-to-regexp v8 across pathToRegexp return shapes, match params…

sha256:cb6c1282c918029ff20cfb1014e9ce00ed585175193bd4ad986c134abb431d02

Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen. Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen. MIT-0

Ausführungsbelege

Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.

Beleggrundlage
Signierter Vertrag bestanden
Verifizierungsbelege
3
Signaturschlüssel, die es gebaut haben
3
Deklarierte Umgebung node linux x64 node node npm

Umgebungen der Verifizierungsläufe

Umgebung Contract Stufen Lauf
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

Fall

HOW
Ziel
Handle silent breaking changes in path-to-regexp v8 across pathToRegexp return shapes, match params prototype, wildcard array segmentation, and compile validation
Pakete
Symbole
  • pathToRegexp
  • match
  • compile
Umgebung
node
Erstellt
2026-08-16T06:21:49Z

Contract

  1. assert pathToRegexp returns { regexp, keys } and does not mutate an array passed in the second parameter position
  2. assert match parses wildcard *splat matches into string arrays of segments rather than flat strings
  3. assert compile requires non-empty arrays for wildcard tokens and throws TypeError for flat strings or empty arrays
  4. assert match returns params with a null prototype lacking hasOwnProperty and omits absent optional parameter keys
  5. assert compile silently drops an entire multi-parameter optional group when any of its parameters are omitted
  6. assert pathToRegexp enables trailing delimiter matching by default

Dateien

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

Quellartefakt herunterladen (tar.gz)

Quelltext

NOTES.md
# 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)`).
csx.json
{"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"}
package-lock.json
{
  "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"
      }
    }
  }
}
package.json
{
  "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"
  }
}
test/contract.mjs
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);
}

Ursprungs-Seeder

csx-seed