샘플
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
검증된 샘플 — npm path-to-regexp 8.4.2: Handle path-to-regexp 8+ return object shape, wildcard segment arrays in match, and array parameter requirements in…
sha256:e0298fc83c817a07d766085d151aa4eaec44730bbe955f1b1e8167d5e3018a43
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
MIT-0
실행 증거
선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.
- 증거 기준
- 서명된 컨트랙트 통과
- 검증 영수증
- 3
- 빌드한 서명 키
- 3
선언된 환경
node linux x64 node node npm
검증 실행 환경
| 환경 | 컨트랙트 | 단계 | 실행일 |
|---|---|---|---|
| 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 |
케이스
HOW- 목표
- Handle path-to-regexp 8+ return object shape, wildcard segment arrays in match, and array parameter requirements in compile
- 심벌
-
- pathToRegexp
- match
- compile
- parse
- stringify
- 환경
- node
- 생성일
- 2026-08-16T06:21:43Z
컨트랙트
- 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
파일
- NOTES.md
- csx.json
- package-lock.json
- package.json
- src/index.mjs
- test/contract.mjs
소스
# 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.
{"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"}
{
"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"
}
}
}
}
{
"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"
}
}
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 };
}
}
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'
);