Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkg: Update path-to-regexp to v8 #3218

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/moody-dots-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@data-client/rest': minor
---

RestEndpoint.path and Resource.path syntax updated

Upgrading path-to-regexp from 6 to 8.
- https://github.com/pillarjs/path-to-regexp/releases/tag/v8.0.0
- https://github.com/pillarjs/path-to-regexp/releases/tag/v7.0.0

BREAKING CHANGES:
- /:optional? -> {/:optional}
- /:repeating+ -> /*repeating
- /:repeating* -> {/*repeating}
- `(`, `)`, `[`, `]` must be escaped `"\\("`
- `()[]{}*:;,!@` are all characters that need escaping
- /:with-dash -> /:"with-dash"
11 changes: 6 additions & 5 deletions __tests__/new.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Temporal } from '@js-temporal/polyfill';
import React, { createContext, useContext } from 'react';

import {
schema,
Endpoint,
Expand All @@ -12,8 +15,6 @@ import {
Resource,
ResourceOptions,
} from '@data-client/rest';
import { Temporal } from '@js-temporal/polyfill';
import React, { createContext, useContext } from 'react';

/** Represents data with primary key being from 'id' field. */
export class IDEntity extends Entity {
Expand Down Expand Up @@ -358,14 +359,14 @@ const CoolerArticleResourceBase = createArticleResource({
export const CoolerArticleResource = {
...CoolerArticleResourceBase,
get: CoolerArticleResourceBase.get.extend({
path: '/:id?/:title?',
path: '{/:id}{/:title}',
}),
};
export const OptimisticArticleResource = createArticleResource({
schema: CoolerArticle,
urlRoot: 'article-cooler',
optimistic: true,
}).extend('get', { path: '/:id?/:title?' });
}).extend('get', { path: '{/:id}{/:title}' });

const CoolerArticleResourceFromMixinBase = createArticleResource({
schema: ArticleFromMixin,
Expand All @@ -374,7 +375,7 @@ const CoolerArticleResourceFromMixinBase = createArticleResource({
export const CoolerArticleResourceFromMixin = {
...CoolerArticleResourceFromMixinBase,
get: CoolerArticleResourceFromMixinBase.get.extend({
path: '/:id?/:title?',
path: '{/:id}{/:title}',
}),
};

Expand Down
2 changes: 1 addition & 1 deletion packages/rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
"dependencies": {
"@babel/runtime": "^7.17.0",
"@data-client/endpoint": "^0.14.12",
"path-to-regexp": "^6.3.0"
"path-to-regexp": "^8.1.0"
},
"devDependencies": {
"@anansi/browserslist-config": "^1.4.2",
Expand Down
28 changes: 19 additions & 9 deletions packages/rest/src/RestHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import { compile, PathFunction, parse } from 'path-to-regexp';
import { compile, PathFunction, parse, Token, ParamData } from 'path-to-regexp';

import { ShortenPath } from './pathTypes.js';

const urlBaseCache: Record<string, PathFunction<object>> = Object.create(null);
export function getUrlBase(path: string): PathFunction {
export function getUrlBase(path: string): PathFunction<ParamData> {
if (!(path in urlBaseCache)) {
urlBaseCache[path] = compile(path, {
encode: encodeURIComponent,
validate: false,
});
urlBaseCache[path] = compile(path);
}
return urlBaseCache[path];
}

const urlTokensCache: Record<string, Set<string>> = Object.create(null);
export function getUrlTokens(path: string): Set<string> {
if (!(path in urlTokensCache)) {
urlTokensCache[path] = new Set(
parse(path).map(t => (typeof t === 'string' ? t : `${t['name']}`)),
);
urlTokensCache[path] = tokenMap(parse(path).tokens);
}
return urlTokensCache[path];
}

function tokenMap(tokens: Token[]): Set<string> {
const tokenNames = new Set<string>();
tokens.forEach(token => {
switch (token.type) {
case 'param':
case 'wildcard':
tokenNames.add(token.name);
break;
case 'group':
return tokenNames.union(tokenMap(token.tokens));
}
});
return tokenNames;
}

const proto = Object.prototype;
const gpo = Object.getPrototypeOf;

Expand Down
5 changes: 2 additions & 3 deletions packages/rest/src/__tests__/RestEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,9 @@ describe('RestEndpoint', () => {
});

it('only optional path means the arg is not required', () => {
const ep = new RestEndpoint({ path: '/users/:id?/:group?' });
const ep = new RestEndpoint({ path: '/users{/:id}{/:group}' });
const epbody = new RestEndpoint({
path: '/users/:id?/:group?',
path: '/users{/:id}{/:group}',
body: { title: '' },
method: 'POST',
});
Expand Down Expand Up @@ -1369,7 +1369,6 @@ describe('RestEndpoint.fetch()', () => {
expect(error).toBeDefined();
expect(error.status).toBe(500);

// eslint-disable-next-line require-atomic-updates
console.error = oldError;
});

Expand Down
19 changes: 8 additions & 11 deletions packages/rest/src/pathTypes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
type OnlyOptional<S extends string> =
S extends `${infer K}}?` ? K
: S extends `${infer K}?` ? K
: never;
type OnlyRequired<S extends string> = S extends `${string}?` ? never : S;
type OnlyOptional<S extends string> = S extends `${infer K}}` ? K : never;
type OnlyRequired<S extends string> = S extends `${string}}` ? never : S;

/** Parameters for a given path */
export type PathArgs<S extends string> =
Expand All @@ -14,18 +11,18 @@ export type PathArgs<S extends string> =
/** Computes the union of keys for a path string */
export type PathKeys<S extends string> =
string extends S ? string
: S extends `${infer A}\\${':' | '?' | '+' | '*' | '{' | '}'}${infer B}` ?
: S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ?
PathKeys<A> | PathKeys<B>
: PathSplits<S>;

type PathSplits<S extends string> =
S extends (
`${string}:${infer K}${'/' | ',' | '%' | '&' | '+' | '*' | '{'}${infer R}`
`${string}${':' | '*'}${infer K}${'/' | '\\' | '%' | '&' | '*' | '{' | ';' | ',' | '!' | '@'}${infer R}`
) ?
PathSplits<`:${K}`> | PathSplits<R>
: S extends `${string}:${infer K}:${infer R}` ?
PathSplits<`:${K}`> | PathSplits<`:${R}`>
: S extends `${string}:${infer K}` ? K
PathSplits<`${':' | '*'}${K}`> | PathSplits<R>
: S extends `${string}${':' | '*'}${infer K}${':' | '*'}${infer R}` ?
PathSplits<`${':' | '*'}${K}`> | PathSplits<`${':' | '*'}${R}`>
: S extends `${string}${':' | '*'}${infer K}` ? K
: never;

export type KeysToArgs<Key extends string> = {
Expand Down
24 changes: 11 additions & 13 deletions packages/rest/typescript-tests/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Entity, schema } from '@data-client/endpoint';
import { useController, useSuspense } from '@data-client/react';
import { User } from '__tests__/new';
Expand Down Expand Up @@ -197,31 +196,31 @@ it('should precisely type function arguments', () => {
// path: '/todos/:id?'
() => {
const optionalUndefSearch = new RestEndpoint({
path: '/todos/:id?',
path: '/todos{/:id}',
searchParams: {} as
| {
userId?: string | number;
}
| undefined,
});
const optionalSearch = new RestEndpoint({
path: '/todos/:id?',
path: '/todos{/:id}',
searchParams: {} as {
userId?: string | number;
},
});
const undef = new RestEndpoint({
path: '/todos/:id?',
path: '/todos{/:id}',
searchParams: undefined,
});
const requiredSearch = new RestEndpoint({
path: '/todos/:id?',
path: '/todos{/:id}',
searchParams: {} as {
userId: string | number;
},
});
const noSearch = new RestEndpoint({
path: '/todos/:id?',
path: '/todos{/:id}',
});
() => optionalUndefSearch();
() => optionalUndefSearch({});
Expand Down Expand Up @@ -573,20 +572,19 @@ it('should handle more open ended type definitions', () => {

() => {
const getThing = new RestEndpoint({
path: '/:id*:bob',
path: '{/*id}:bob',
});

getThing({ id: 5, bob: 'hi' });
// @ts-expect-error
getThing({ id: 'hi' });
// @ts-expect-error
getThing({ bob: 'hi' });
// @ts-expect-error
getThing(5);
};
() => {
const getThing = new RestEndpoint({
path: '/:id+:bob',
path: '/*id:bob',
});

getThing({ id: 5, bob: 'hi' });
Expand All @@ -601,7 +599,7 @@ it('should handle more open ended type definitions', () => {
};
() => {
const getThing = new RestEndpoint({
path: '/:id\\+:bob',
path: '/:id\\,:bob',
});

getThing({ id: 5, bob: 'hi' });
Expand All @@ -616,7 +614,7 @@ it('should handle more open ended type definitions', () => {
};
() => {
const getThing = new RestEndpoint({
path: '/:id:bob+',
path: '/:id/*bob',
});

getThing({ id: 5, bob: 'hi' });
Expand All @@ -631,7 +629,7 @@ it('should handle more open ended type definitions', () => {
};
() => {
const getThing = new RestEndpoint({
path: '/:foo/(.*)',
path: '/:foo/(.)',
});

getThing({ foo: 'hi' });
Expand All @@ -644,7 +642,7 @@ it('should handle more open ended type definitions', () => {
};
() => {
const getThing = new RestEndpoint({
path: '/:attr1?{-:attr2}?{-:attr3}?',
path: '{/:attr1}{-:attr2}{-:attr3}',
});

getThing({ attr1: 'hi' });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PathFunction } from 'path-to-regexp';
import { PathFunction, ParamData } from 'path-to-regexp';

interface NetworkError$1 extends Error {
status: number;
Expand Down Expand Up @@ -1163,13 +1163,13 @@ type ExtractCollection<S extends Schema | undefined> = S extends ({
[K: string]: Schema;
} ? ExtractObject<S> : never;

type OnlyOptional<S extends string> = S extends `${infer K}}?` ? K : S extends `${infer K}?` ? K : never;
type OnlyRequired<S extends string> = S extends `${string}?` ? never : S;
type OnlyOptional<S extends string> = S extends `${infer K}}` ? K : never;
type OnlyRequired<S extends string> = S extends `${string}}` ? never : S;
/** Parameters for a given path */
type PathArgs<S extends string> = PathKeys<S> extends never ? unknown : KeysToArgs<PathKeys<S>>;
/** Computes the union of keys for a path string */
type PathKeys<S extends string> = string extends S ? string : S extends `${infer A}\\${':' | '?' | '+' | '*' | '{' | '}'}${infer B}` ? PathKeys<A> | PathKeys<B> : PathSplits<S>;
type PathSplits<S extends string> = S extends (`${string}:${infer K}${'/' | ',' | '%' | '&' | '+' | '*' | '{'}${infer R}`) ? PathSplits<`:${K}`> | PathSplits<R> : S extends `${string}:${infer K}:${infer R}` ? PathSplits<`:${K}`> | PathSplits<`:${R}`> : S extends `${string}:${infer K}` ? K : never;
type PathKeys<S extends string> = string extends S ? string : S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ? PathKeys<A> | PathKeys<B> : PathSplits<S>;
type PathSplits<S extends string> = S extends (`${string}${':' | '*'}${infer K}${'/' | '\\' | '%' | '&' | '*' | '{' | ';' | ',' | '!' | '@'}${infer R}`) ? PathSplits<`${':' | '*'}${K}`> | PathSplits<R> : S extends `${string}${':' | '*'}${infer K}${':' | '*'}${infer R}` ? PathSplits<`${':' | '*'}${K}`> | PathSplits<`${':' | '*'}${R}`> : S extends `${string}${':' | '*'}${infer K}` ? K : never;
type KeysToArgs<Key extends string> = {
[K in Key as OnlyOptional<K>]?: string | number;
} & (OnlyRequired<Key> extends never ? unknown : {
Expand Down Expand Up @@ -1533,7 +1533,7 @@ type MutateEndpoint<O extends {
*/
declare let RestEndpoint: RestEndpointConstructor;

declare function getUrlBase(path: string): PathFunction;
declare function getUrlBase(path: string): PathFunction<ParamData>;
declare function getUrlTokens(path: string): Set<string>;

type ResourceExtension<R extends {
Expand Down
12 changes: 6 additions & 6 deletions website/src/components/Playground/editor-types/globals.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PathFunction } from 'path-to-regexp';
import { PathFunction, ParamData } from 'path-to-regexp';
import { Manager, State, Controller, EndpointInterface as EndpointInterface$1, FetchFunction as FetchFunction$1, Schema as Schema$1, ResolveType as ResolveType$1, Denormalize as Denormalize$1, DenormalizeNullable as DenormalizeNullable$1, Queryable as Queryable$1, NI as NI$1, SchemaArgs as SchemaArgs$1, NetworkError as NetworkError$2, UnknownError as UnknownError$1, ErrorTypes as ErrorTypes$2 } from '@data-client/core';
export { Manager } from '@data-client/core';
import React, { JSX } from 'react';
Expand Down Expand Up @@ -1167,13 +1167,13 @@ type ExtractCollection<S extends Schema | undefined> = S extends ({
[K: string]: Schema;
} ? ExtractObject<S> : never;

type OnlyOptional<S extends string> = S extends `${infer K}}?` ? K : S extends `${infer K}?` ? K : never;
type OnlyRequired<S extends string> = S extends `${string}?` ? never : S;
type OnlyOptional<S extends string> = S extends `${infer K}}` ? K : never;
type OnlyRequired<S extends string> = S extends `${string}}` ? never : S;
/** Parameters for a given path */
type PathArgs<S extends string> = PathKeys<S> extends never ? unknown : KeysToArgs<PathKeys<S>>;
/** Computes the union of keys for a path string */
type PathKeys<S extends string> = string extends S ? string : S extends `${infer A}\\${':' | '?' | '+' | '*' | '{' | '}'}${infer B}` ? PathKeys<A> | PathKeys<B> : PathSplits<S>;
type PathSplits<S extends string> = S extends (`${string}:${infer K}${'/' | ',' | '%' | '&' | '+' | '*' | '{'}${infer R}`) ? PathSplits<`:${K}`> | PathSplits<R> : S extends `${string}:${infer K}:${infer R}` ? PathSplits<`:${K}`> | PathSplits<`:${R}`> : S extends `${string}:${infer K}` ? K : never;
type PathKeys<S extends string> = string extends S ? string : S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ? PathKeys<A> | PathKeys<B> : PathSplits<S>;
type PathSplits<S extends string> = S extends (`${string}${':' | '*'}${infer K}${'/' | '\\' | '%' | '&' | '*' | '{' | ';' | ',' | '!' | '@'}${infer R}`) ? PathSplits<`${':' | '*'}${K}`> | PathSplits<R> : S extends `${string}${':' | '*'}${infer K}${':' | '*'}${infer R}` ? PathSplits<`${':' | '*'}${K}`> | PathSplits<`${':' | '*'}${R}`> : S extends `${string}${':' | '*'}${infer K}` ? K : never;
type KeysToArgs<Key extends string> = {
[K in Key as OnlyOptional<K>]?: string | number;
} & (OnlyRequired<Key> extends never ? unknown : {
Expand Down Expand Up @@ -1537,7 +1537,7 @@ type MutateEndpoint<O extends {
*/
declare let RestEndpoint: RestEndpointConstructor;

declare function getUrlBase(path: string): PathFunction;
declare function getUrlBase(path: string): PathFunction<ParamData>;
declare function getUrlTokens(path: string): Set<string>;

type ResourceExtension<R extends {
Expand Down
9 changes: 8 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3213,7 +3213,7 @@ __metadata:
"@babel/runtime": "npm:^7.17.0"
"@data-client/endpoint": "npm:^0.14.12"
"@types/node": "npm:^22.0.0"
path-to-regexp: "npm:^6.3.0"
path-to-regexp: "npm:^8.1.0"
languageName: unknown
linkType: soft

Expand Down Expand Up @@ -23022,6 +23022,13 @@ __metadata:
languageName: node
linkType: hard

"path-to-regexp@npm:^8.1.0":
version: 8.1.0
resolution: "path-to-regexp@npm:8.1.0"
checksum: 10c0/1c46be3806ab081bedc51eb238fcb026b61b15f19e8924b26e7dad88812dda499efe357a780665dc915dcab3be67213f145f5e2921b8fc8c6c497608d4e092ed
languageName: node
linkType: hard

"path-type@npm:^3.0.0":
version: 3.0.0
resolution: "path-type@npm:3.0.0"
Expand Down
Loading