diff --git a/demo/demo.ts b/demo/demo.ts index 3e4bf23..50c284b 100644 --- a/demo/demo.ts +++ b/demo/demo.ts @@ -19,8 +19,8 @@ const tableRoute = rootRoute.createChildRoute("table", { async function Content(url: string) { return rootRoute.map(url, async ({ hasChildRouteActive }) => hasChildRouteActive - ? tableRoute.map(url, async ({ hasChildRouteActive, parameter }) => { - const { items, totalCount } = await getItems({ + ? tableRoute.map(url, async ({ parameter }) => { + const { items } = await getItems({ page: parameter.table.page, size: parameter.table.size, sortField: parameter.table.sort.field, diff --git a/flake.lock b/flake.lock index e4f0ef8..03cb250 100644 --- a/flake.lock +++ b/flake.lock @@ -34,27 +34,10 @@ "type": "github" } }, - "npmlock2nixSrc": { - "flake": false, - "locked": { - "lastModified": 1758100811, - "narHash": "sha256-qJc3ffjHVXUdZqytKcDK9XZ2b3BQ1RdYfZFuYgxbrn4=", - "owner": "nix-community", - "repo": "npmlock2nix", - "rev": "4d9060afbaa5f57ee0b8ef11c7044ed287a7d302", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "npmlock2nix", - "type": "github" - } - }, "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "npmlock2nixSrc": "npmlock2nixSrc" + "nixpkgs": "nixpkgs" } }, "systems": { diff --git a/flake.nix b/flake.nix index 58c9d71..9272a35 100644 --- a/flake.nix +++ b/flake.nix @@ -2,17 +2,12 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; - npmlock2nixSrc = { - url = "github:nix-community/npmlock2nix"; - flake = false; - }; }; - outputs = { self, nixpkgs, flake-utils, npmlock2nixSrc }: + outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; - npmlock2nix = import npmlock2nixSrc { inherit pkgs; }; in { devShells.default = pkgs.mkShell (with pkgs; { @@ -27,19 +22,14 @@ }; }); - checks.default = pkgs.importNpmLock.buildNodeModules (with pkgs; { - npmRoot = ./.; - inherit nodejs; - derivationArgs = { - buildCommands = [ - "npm exec tsc -- --noEmit" - "CHROME_PATH=${pkgs.chromium}/bin/chromium XDG_CONFIG_HOME=$TMPDIR/chromium-config XDG_CACHE_HOME=$TMPDIR=/chromium-cache npm run test" - ]; - installPhase = '' - touch $out - ''; + checks.default = with pkgs; buildNpmPackage { + name = "plusnew-router"; + src = ./.; + npmDeps = importNpmLock.buildNodeModules { + npmRoot = ./.; + inherit nodejs; }; - }); + }; } ); } diff --git a/package-lock.json b/package-lock.json index 7d22045..24c6276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,13 @@ "name": "@plusnew/router", "version": "4.0.1", "devDependencies": { - "@esm-bundle/chai": "^4.3.4-fix.0", + "@types/chai": "^5.2.3", "@types/mocha": "^10.0.6", "@typescript-eslint/eslint-plugin": "^8.58.0", "@typescript-eslint/parser": "^8.58.0", "@web/dev-server-esbuild": "^1.0.2", "@web/test-runner": "^0.20.2", + "chai": "^6.2.2", "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", @@ -583,16 +584,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@esm-bundle/chai": { - "version": "4.3.4-fix.0", - "resolved": "https://registry.npmjs.org/@esm-bundle/chai/-/chai-4.3.4-fix.0.tgz", - "integrity": "sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^4.2.12" - } - }, "node_modules/@hapi/bourne": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", @@ -1207,11 +1198,15 @@ } }, "node_modules/@types/chai": { - "version": "4.3.20", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", - "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } }, "node_modules/@types/co-body": { "version": "6.1.3", @@ -1275,6 +1270,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -2127,6 +2129,16 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -2393,6 +2405,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6248,3 +6270,4 @@ } } } + diff --git a/package.json b/package.json index e4af46b..2a6891b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "@plusnew/router", - "version": "4.0.1", + "version": "5.0.0", "description": "typesafe router", "main": "src/index.ts", "module": "src/index.ts", "scripts": { + "check": "tsc --noEmit", "start": "web-dev-server", "test": "web-test-runner --coverage", "test:watch": "web-test-runner --watch --coverage" @@ -18,12 +19,13 @@ }, "author": "Carlo Jeske", "devDependencies": { - "@esm-bundle/chai": "^4.3.4-fix.0", + "@types/chai": "^5.2.3", "@types/mocha": "^10.0.6", "@typescript-eslint/eslint-plugin": "^8.58.0", "@typescript-eslint/parser": "^8.58.0", "@web/dev-server-esbuild": "^1.0.2", "@web/test-runner": "^0.20.2", + "chai": "^6.2.2", "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", diff --git a/src/index.ts b/src/index.ts index ac4e24a..5cc4a17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,10 @@ -export { createRootRoute } from "./routeHandler"; -export type { RouteToParameter } from "./types"; +export { mapPath, createPath } from "./routeHandler"; + export * as schema from "./schema"; + +import type { InferschemaFromUrl } from "./types"; +import { object } from "./schema"; + +export type RouteToParameter = InferschemaFromUrl< + ReturnType> +>; diff --git a/src/routeHandler.ts b/src/routeHandler.ts index fe6b150..822dd3d 100644 --- a/src/routeHandler.ts +++ b/src/routeHandler.ts @@ -1,185 +1,82 @@ import { object } from "./schema"; -import { containerHandler, flattenUrlResult, isDefault } from "./schema/util"; -import { Tokenizer, state, tokenize } from "./tokenizer"; -import { TOKENS } from "./tokenizer"; +import { containerHandler, flattenUrlResult } from "./schema/util"; +import { Tokenizer, TOKENS } from "./tokenizer"; import type { - NamespaceTemplate, - NamespaceToLinkParameter, - NamespaceToParameter, + InferschemaToUrl, + InferschemaFromUrl, ParameterSpecificationTemplate, + toUrlResult, } from "./types"; -export function createRootRoute( - parameterSchema: T, -): Route<{ "/": T }>; -export function createRootRoute( - segment: string, - parameterSchema: T, -): Route<{ "/": T }>; -export function createRootRoute( - segmentOrParameterSchema: string | T, - parameterSpec?: T, -): Route<{ "/": T }> { - if (parameterSpec === undefined) { - return new Route(TOKENS.PATH_SEPERATOR, segmentOrParameterSchema as T); - } - return new Route(segmentOrParameterSchema as string, parameterSpec); -} - -class Route { - constructor( - protected namespace: string, - protected parameterSpec: ParameterSpecificationTemplate, - protected parentRoute?: Route, - ) {} - map( - url: string, - cb: (data: { - parameter: NamespaceToParameter; - hasChildRouteActive: boolean; - }) => U, - ): U | null { - let isOuterMap = false; - if (state.index === null) { - state.index = 0; - state.tokens = tokenize(url); - - if ( - state.tokens.length === 0 || - state.tokens[state.tokens.length - 1].type !== "PATH_SEPERATOR" - ) { - state.tokens.push({ type: "PATH_SEPERATOR" }); - } - - isOuterMap = true; - } - - const tokenizer = new Tokenizer(); - - let parameter = - this.parentRoute === undefined - ? null - : this.parentRoute.map(url, ({ parameter }) => parameter); - let hasChildRouteActive = false; - let validRoute = this.parentRoute === undefined ? true : parameter !== null; - - if (validRoute) { - const segmentTokens = tokenize(this.namespace); - for (const segmentToken of segmentTokens) { - if (tokenizer.lookahead(segmentToken) === null) { - validRoute = false; - break; - } else { - tokenizer.eat(segmentToken); - } - } - - const hasParameter = - tokenizer.lookahead({ type: "VALUE_SEPERATOR" }) !== null; - - if (validRoute === true) - try { - parameter = { - ...parameter, - [this.parentRoute === undefined - ? TOKENS.PATH_SEPERATOR - : this.namespace]: handleParameter(this.parameterSpec, tokenizer), - }; - } catch (error) { - validRoute = false; - } - - if ( - validRoute === true && - (this.namespace !== TOKENS.PATH_SEPERATOR || hasParameter === true) - ) { - if (tokenizer.lookahead({ type: "PATH_SEPERATOR" }) === null) { - validRoute = false; - } else { - tokenizer.eat({ type: "PATH_SEPERATOR" }); - } - } +export function createPath( + namespace: string, + parameterSpec: T, + parameter: InferschemaToUrl>>, +) { + const parameterUrl = object(parameterSpec).toUrl(parameter as any); - hasChildRouteActive = tokenizer.isDone() === false; + return `${TOKENS.PATH_SEPERATOR}${namespace}${Object.entries( + parameterUrl as { [parameter: string]: toUrlResult }, + ).reduce((accumulator, [name, value]) => { + if (value === "" || value === null) { + return accumulator; } + return `${accumulator}${flattenUrlResult(name, value).reduce( + (accumulator, [name, value]) => + `${accumulator}${TOKENS.VALUE_SEPERATOR}${name}${TOKENS.VALUE_ASSIGNMENT}${value}`, + "", + )}`; + }, "")}`; +} - if (isOuterMap === true) { - state.index = null; +export function mapPath( + namespace: string, + parameterSpec: T, + path: string, + cb: ( + parameter: InferschemaFromUrl>>, + rest: string | null, + ) => U, +): U | null { + const tokenizer = new Tokenizer(path); + + tokenizer.eat({ type: "PATH_SEPERATOR" }); + + const namespaceTokenizer = new Tokenizer(namespace); + let matches = true; + + while (namespaceTokenizer.currentToken !== null && matches === true) { + matches = tokenizer.lookahead(namespaceTokenizer.currentToken) !== null; + if (matches === true) { + tokenizer.eat(namespaceTokenizer.currentToken); + namespaceTokenizer.eat(namespaceTokenizer.currentToken); } - - return validRoute === true - ? cb({ - parameter: parameter as NamespaceToParameter, - hasChildRouteActive: hasChildRouteActive, - }) - : null; - } - createChildRoute( - namespace: U, - parameterSpec: V, - ): Route { - return new Route(namespace, parameterSpec, this); } - createPath(namespacedParameter: NamespaceToLinkParameter): string { - let path = - this.parentRoute === undefined - ? "" - : this.parentRoute.createPath(namespacedParameter); - - path += this.namespace; + if (matches === true) { + const hasParameter = + tokenizer.lookahead({ type: "VALUE_SEPERATOR" }) !== null; - path += parameterToUrl( - this.parameterSpec, - namespacedParameter[ - this.parentRoute === undefined ? TOKENS.PATH_SEPERATOR : this.namespace - ], - ); - - if (path.endsWith(TOKENS.PATH_SEPERATOR) === false) { - path += TOKENS.PATH_SEPERATOR; + if (hasParameter) { + tokenizer.eat({ type: "VALUE_SEPERATOR" }); } - return path; - } -} - -function parameterToUrl( - parameterSpec: ParameterSpecificationTemplate, - parameter: object, -) { - return Object.entries(parameter).reduce((accumulator, [name, value]) => { - if (isDefault(parameterSpec[name], value)) { - return accumulator; + let result; + try { + result = containerHandler(object(parameterSpec), tokenizer, hasParameter); + } catch (error) { + matches = false; } - const urlResult = parameterSpec[name].toUrl(value); - if (urlResult === "") { - return accumulator; - } - return `${accumulator}${flattenUrlResult(name, urlResult).reduce( - (accumulator, [name, value]) => - `${accumulator}${TOKENS.VALUE_SEPERATOR}${name}${TOKENS.VALUE_ASSIGNMENT}${value}`, - "", - )}`; - }, ""); -} -function handleParameter( - parameterSpecification: T, - tokenizer: Tokenizer, -) { - let hasValues = true; - const valueSeperator = tokenizer.lookahead({ type: "VALUE_SEPERATOR" }); - if (valueSeperator === null) { - hasValues = false; - } else { - tokenizer.eat({ type: "VALUE_SEPERATOR" }); + if (matches === true) { + return cb( + result, + tokenizer.currentToken === null + ? null + : tokenizer.path.slice(tokenizer.index - 1), + ); + } } - const result = containerHandler( - object(parameterSpecification), - tokenizer, - hasValues, - ); - return result; + return null; } diff --git a/src/schema/object.ts b/src/schema/object.ts index 59cfe40..5799ccf 100644 --- a/src/schema/object.ts +++ b/src/schema/object.ts @@ -6,11 +6,23 @@ export default function }>( ): NoInfer< schema< { - [PropertyName in keyof T]: InferschemaFromUrl; + -readonly [PropertyName in keyof T]: InferschemaFromUrl; }, - { + { [PropertyName in keyof T]: null } extends { [PropertyName in keyof T]: InferschemaToUrl; } + ? + | null + | { + -readonly [PropertyName in keyof T]: InferschemaToUrl< + T[PropertyName] + >; + } + : { + -readonly [PropertyName in keyof T]: InferschemaToUrl< + T[PropertyName] + >; + } > > { const schemaEntries = Object.entries(schemas); @@ -89,9 +101,9 @@ export default function }>( return [ key, - isDefault(schema, parameter[key]) + isDefault(schema, parameter?.[key] ?? null) ? null - : schema.toUrl(parameter[key]), + : schema.toUrl(parameter?.[key] ?? null), ]; }), ); diff --git a/src/schema/string.ts b/src/schema/string.ts index 9ab8b6c..2fcd8c6 100644 --- a/src/schema/string.ts +++ b/src/schema/string.ts @@ -24,7 +24,7 @@ export default function < } let result = getText(tokenizer) ?? ""; - while (tokenizer.isDone() === false) { + while (tokenizer.currentToken !== null) { const more = getText(tokenizer); if (more === null) { break; diff --git a/src/tokenizer.ts b/src/tokenizer.ts index c60cbe3..d5084ee 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -13,23 +13,18 @@ export const TOKENS = { const TOKEN_KEYS = Object.keys(TOKENS) as (keyof typeof TOKENS)[]; const TOKEN_VALUES = Object.values(TOKENS); -export const state: { index: number | null; tokens: Token[] } = { - index: null, - tokens: [], -}; - export class Tokenizer { - isDone() { - return state.index === state.tokens.length; + public currentToken: Token | null = null; + public index = 0; + + constructor(public path: string) { + this.tokenize(); } - eat( + public eat( eatToken: { type: T } | { type: "TEXT"; value: string }, ): Extract { - if (state.index === null) { - throw new Error("Tokens are not initialised"); - } - if (this.isDone() === true) { + if (this.currentToken === null) { throw new Error("No new tokens available"); } @@ -37,81 +32,73 @@ export class Tokenizer { if (currentToken === null) { throw new Error( - `Couldnt find token ${eatToken.type}, but ${state.tokens[state.index].type}`, + `Couldnt find token ${eatToken.type}, but ${this.currentToken.type}`, ); } - state.index++; + this.tokenize(); return currentToken; } - lookahead( + + public lookahead( eatToken: { type: T } | { type: "TEXT"; value: string }, ): Extract | null { - if (state.index === null) { - throw new Error("Tokens are not initialised"); - } - if (this.isDone()) { + if (this.currentToken === null) { return null; } - const currentToken = state.tokens[state.index]; - - if (currentToken.type === eatToken.type) { + if (this.currentToken.type === eatToken.type) { if ( - "value" in currentToken && + "value" in this.currentToken && "value" in eatToken && - currentToken.value !== eatToken.value + this.currentToken.value !== eatToken.value ) { return null; } - return currentToken as any; + return this.currentToken as any; } else { return null; } } -} - -export function tokenize(url: string) { - const tokens: Token[] = []; - for (let i = 0; i < url.length; i++) { - let result = getToken(url, i); - if (result === null) { - result = { - type: "TEXT", - value: url[i], - }; - while (i + 1 < url.length) { - const nextToken = getToken(url, i + 1); - if (nextToken === null) { - result.value += url[i + 1]; - - i++; - } else { - tokens.push(result); - result = nextToken; - - i++; - - break; - } + private tokenize() { + if (this.index >= this.path.length) { + this.currentToken = null; + } else { + let token = this.getToken(); + let result = token; + if (result === null) { + result = { + type: "TEXT", + value: "", + }; + + do { + if (token === null) { + result.value += this.path[this.index]; + + this.index++; + } else { + break; + } + } while (((token = this.getToken()), this.index < this.path.length)); + } else { + this.index++; } - } - tokens.push(result); + this.currentToken = result; + } } - return tokens; -} - -function getToken(url: string, index: number): Token | null { - const result = (TOKEN_VALUES as string[]).indexOf(url[index]); + private getToken(): Token | null { + const result = (TOKEN_VALUES as string[]).indexOf(this.path[this.index]); - if (result !== -1) { - return { - type: TOKEN_KEYS[result], - }; + if (result !== -1) { + return { + type: TOKEN_KEYS[result], + }; + } + return null; } - return null; } diff --git a/src/types.ts b/src/types.ts index 2920cb2..62d7b93 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,45 +27,3 @@ export type InferschemaFromUrl = T extends schema ? R : never; export interface ParameterSpecificationTemplate { [ParameterName: string]: schema; } - -export interface NamespaceTemplate { - [Namespace: string]: ParameterSpecificationTemplate; -} - -export interface Route { - map: ( - url: string, - cb: (data: { - parameter: NamespaceToParameter; - hasChildRouteActive: boolean; - }) => U, - ) => U | null; - createChildRoute: < - U extends string, - V extends ParameterSpecificationTemplate, - >( - namespace: U, - parameterSpec: V, - ) => Route; - createPath: (parameter: NamespaceToLinkParameter) => string; -} - -export type NamespaceToLinkParameter = { - [NamespaceName in keyof T]: { - [ParameterName in keyof T[NamespaceName]]: InferschemaToUrl< - T[NamespaceName][ParameterName] - >; - }; -}; - -export type NamespaceToParameter = { - [NamespaceName in keyof T]: { - [ParameterName in keyof T[NamespaceName]]: InferschemaFromUrl< - T[NamespaceName][ParameterName] - >; - }; -}; - -export type RouteToParameter> = Parameters< - Parameters["1"] ->["0"]["parameter"]; diff --git a/test/index.test.ts b/test/index.test.ts index 8593a65..90e034b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,15 +1,11 @@ -import { expect } from "@esm-bundle/chai"; -import { createRootRoute, schema } from "../"; +import { expect } from "chai"; +import { mapPath, createPath, schema } from "../"; import { TOKENS } from "../src/tokenizer"; function assertType(): T { return true as T; } -function id(value: T) { - return value; -} - type IsEqual = (() => T extends CheckA ? 1 : 2) extends () => T extends CheckB ? 1 : 2 ? true @@ -17,492 +13,516 @@ type IsEqual = describe("map", () => { describe("Path handling", () => { - it("root", () => { - const rootRoute = createRootRoute({ foo: schema.number() }); - const childRoute = rootRoute.createChildRoute("foo", {}); - const inputValue = { "/": { foo: 3 } }; - - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: number }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: number }; - } - > - >(); - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, - }); - - expect(childRoute.map(rootRoute.createPath(inputValue), id)).to.eql(null); - }); - - it("optional handling", () => { - const rootRoute = createRootRoute({ - foo: schema.number(), - bar: schema.number({ default: 23 }), - }); - const inputValue = { "/": { foo: 3, bar: null, baz: null } }; - - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: number; bar: number | null }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: number; bar: number }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { "/": { foo: 3, bar: 23 } }, - hasChildRouteActive: false, - }); - }); - - it("child handling", () => { - const rootRoute = createRootRoute({}); - const childRoute = rootRoute.createChildRoute("foo", {}); - const inputValue = { "/": {}, foo: {} }; - - const outputValue = rootRoute.map(childRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - // eslint-disable-next-line @typescript-eslint/ban-types - "/": {}; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - // eslint-disable-next-line @typescript-eslint/ban-types - "/": {}; - } - > - >(); - expect(outputValue).to.eql({ - parameter: { "/": {} }, - hasChildRouteActive: true, - }); - - expect(childRoute.map(rootRoute.createPath(inputValue), id)).to.eql(null); + it("basic", () => { + const route = ["name", { foo: schema.number() }] as const; + // type foo = RouteToParameter; + const path = createPath(...route, { foo: 2 }); // "/name;foo=2" + const mapResult = mapPath(...route, path, (parameter, rest) => ({ + parameter, + rest, + })); + expect(mapResult).to.eql({ rest: null, parameter: { foo: 2 } }); }); - it("custom path prefix", () => { - const rootRoute = createRootRoute("/foo", { foo: schema.number() }); - const rootRouteWithoutPrefix = createRootRoute({ - foo: schema.number(), - }); - const childRoute = rootRoute.createChildRoute("foo", {}); - const inputValue = { "/": { foo: 3 } }; - - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: number }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: number }; - } - > - >(); - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, - }); - - expect(childRoute.map(rootRoute.createPath(inputValue), id)).to.eql(null); - expect( - rootRouteWithoutPrefix.map(rootRoute.createPath(inputValue), id), - ).to.eql(null); - }); - - it("child", () => { - const rootRoute = createRootRoute({ foo: schema.number() }); - const childRoute = rootRoute.createChildRoute("child", { - bar: schema.number(), - }); - const anotherChildRoute = rootRoute.createChildRoute( - "anotherChildRoute", - { - bar: schema.number(), - }, + it("name mismatch", () => { + const route = ["name", { foo: schema.number() }] as const; + const path = createPath(...route, { foo: 2 }); // "/name;foo=2" + const mapResult = mapPath( + "anothername", + route[1], + path, + (parameter, rest) => ({ + parameter, + rest, + }), ); - const inputValue = { "/": { foo: 1 }, child: { bar: 2 } }; - const outputValue = childRoute.map(childRoute.createPath(inputValue), id); + expect(mapResult).to.eql(null); + }); - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: number }; - child: { bar: number }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: number }; - child: { bar: number }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, - }); + it("parameter mismatch", () => { + const route = ["name", { foo: schema.number() }] as const; + const path = createPath(...route, { foo: 2 }); // "/name;foo=2" - expect( - anotherChildRoute.map( - childRoute.createPath(inputValue), - (result) => result, - ), - ).to.eql(null); + const mapResult = mapPath(route[0], {}, path, (parameter, rest) => ({ + parameter, + rest, + })); + expect(mapResult).to.eql(null); }); - it("child", () => { - const rootRoute = createRootRoute({}); - const childRoute = rootRoute.createChildRoute("child", { - bar: schema.number(), - }); - const anotherChildRoute = rootRoute.createChildRoute( - "anotherChildRoute", - { - bar: schema.number(), - }, + it("nested", () => { + const route = ["name", { foo: schema.number() }] as const; + const nestedRoute = ["anotherName", { bar: schema.number() }] as const; + const path = `${createPath(...route, { foo: 2 })}${createPath(...nestedRoute, { bar: 3 })}`; // "/name;foo=2/anotherName;bar=3" + + const mapResult = mapPath( + ...route, + path, + ( + parameter, // {foo: 2} + rest, // "/anotherName;bar=3" + ) => + rest === null + ? null + : mapPath(...nestedRoute, rest, (nestedParameter, rest) => ({ + root: parameter, + nested: nestedParameter, + rest, + })), ); - const inputValue = { "/": {}, child: { bar: 2 } }; - const outputValue = childRoute.map(childRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - // eslint-disable-next-line @typescript-eslint/ban-types - "/": {}; - child: { bar: number }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - // eslint-disable-next-line @typescript-eslint/ban-types - "/": {}; - child: { bar: number }; - } - > - >(); - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, + expect(mapResult).to.eql({ + rest: null, + root: { foo: 2 }, + nested: { bar: 3 }, }); - expect( - anotherChildRoute.map( - childRoute.createPath(inputValue), - (result) => result, - ), - ).to.eql(null); - }); - - it("to few parameters", () => { - const rootRoute = createRootRoute({ foo: schema.number() }); - const anotherRootRoute = createRootRoute({}); - - expect( - rootRoute.map(anotherRootRoute.createPath({ "/": {} }), id), - ).to.eql(null); }); - it("to many parameters", () => { - const rootRoute = createRootRoute({}); - const anotherRootRoute = createRootRoute({ foo: schema.number() }); - - expect( - rootRoute.map(anotherRootRoute.createPath({ "/": { foo: 1 } }), id), - ).to.eql(null); - }); - }); + // it("root", () => { + // const rootRoute = [({ foo: schema.number() })] as const; + // const childRoute = rootRoute.createChildRoute("foo", {}); + // const inputValue = { foo: 3 }; + // const outputValue = mapPath(...rootRoute,createPath(...rootRoute,inputValue), id); + // assertType< + // IsEqual< + // Parameters>[2], + // { + // foo: number ; + // } + // > + // >(); + // assertType< + // IsEqual< + // Exclude["parameter"], + // { + // foo: number ; + // } + // > + // >(); + // expect(outputValue).to.eql({ + // parameter: inputValue, + // hasChildRouteActive: true, + // }); + // expect(childRoute.map(createPath(...rootRoute,inputValue), id)).to.eql(null); + // }); + // it("optional handling", () => { + // const rootRoute = [({ + // foo: schema.number(), + // bar: schema.number({ default: 23 }), + // })] as const; + // const inputValue = { foo: 3, bar: null, baz: null }; + // const outputValue = mapPath(...rootRoute,createPath(...rootRoute,inputValue), id); + // assertType< + // IsEqual< + // Parameters>[2], + // { + // foo: number; bar: number | null ; + // } + // > + // >(); + // assertType< + // IsEqual< + // Exclude["parameter"], + // { + // foo: number; bar: number ; + // } + // > + // >(); + // expect(outputValue).to.eql({ + // parameter: { foo: 3, bar: 23 }, + // hasChildRouteActive: false, + // }); + // }); + // it("child handling", () => { + // const rootRoute = [({})] as const; + // const childRoute = rootRoute.createChildRoute("foo", {}); + // const inputValue = { , foo: {} }; + // const outputValue = mapPath(...rootRoute,childRoute.createPath(inputValue), id); + // assertType< + // IsEqual< + // Parameters>[2], + // { + // // eslint-disable-next-line @typescript-eslint/ban-types + // ; + // } + // > + // >(); + // assertType< + // IsEqual< + // Exclude["parameter"], + // { + // // eslint-disable-next-line @typescript-eslint/ban-types + // ; + // } + // > + // >(); + // expect(outputValue).to.eql({ + // parameter: { }, + // hasChildRouteActive: true, + // }); + // expect(childRoute.map(createPath(...rootRoute,inputValue), id)).to.eql(null); + // }); + // it("custom path prefix", () => { + // const rootRoute = [("/foo", { foo: schema.number() })] as const; + // const rootRouteWithoutPrefix = [({ + // foo: schema.number(), + // })] as const; + // const childRoute = rootRoute.createChildRoute("foo", {}); + // const inputValue = { foo: 3 }; + // const outputValue = mapPath(...rootRoute,createPath(...rootRoute,inputValue), id); + // assertType< + // IsEqual< + // Parameters>[2], + // { + // foo: number ; + // } + // > + // >(); + // assertType< + // IsEqual< + // Exclude["parameter"], + // { + // foo: number ; + // } + // > + // >(); + // expect(outputValue).to.eql({ + // parameter: inputValue, + // hasChildRouteActive: false, + // }); + // expect(childRoute.map(createPath(...rootRoute,inputValue), id)).to.eql(null); + // expect( + // rootRouteWithoutPrefix.map(createPath(...rootRoute,inputValue), id), + // ).to.eql(null); + // }); + // it("child", () => { + // const rootRoute = [({ foo: schema.number() })] as const; + // const childRoute = rootRoute.createChildRoute("child", { + // bar: schema.number(), + // }); + // const anotherChildRoute = rootRoute.createChildRoute( + // "anotherChildRoute", + // { + // bar: schema.number(), + // }, + // ); + // const inputValue = { foo: 1 , child: { bar: 2 } }; + // const outputValue = childRoute.map(childRoute.createPath(inputValue), id); + // assertType< + // IsEqual< + // Parameters[0], + // { + // foo: number ; + // child: { bar: number }; + // } + // > + // >(); + // assertType< + // IsEqual< + // Exclude["parameter"], + // { + // foo: number ; + // child: { bar: number }; + // } + // > + // >(); + // expect(outputValue).to.eql({ + // parameter: inputValue, + // hasChildRouteActive: false, + // }); + // expect( + // anotherChildRoute.map( + // childRoute.createPath(inputValue), + // (result) => result, + // ), + // ).to.eql(null); + // }); + // it("child", () => { + // const rootRoute = [({})] as const; + // const childRoute = rootRoute.createChildRoute("child", { + // bar: schema.number(), + // }); + // const anotherChildRoute = rootRoute.createChildRoute( + // "anotherChildRoute", + // { + // bar: schema.number(), + // }, + // ); + // const inputValue = { , child: { bar: 2 } }; + // const outputValue = childRoute.map(childRoute.createPath(inputValue), id); + // assertType< + // IsEqual< + // Parameters[0], + // { + // // eslint-disable-next-line @typescript-eslint/ban-types + // ; + // child: { bar: number }; + // } + // > + // >(); + // assertType< + // IsEqual< + // Exclude["parameter"], + // { + // // eslint-disable-next-line @typescript-eslint/ban-types + // ; + // child: { bar: number }; + // } + // > + // >(); + // expect(outputValue).to.eql({ + // parameter: inputValue, + // hasChildRouteActive: false, + // }); + // expect( + // anotherChildRoute.map( + // childRoute.createPath(inputValue), + // (result) => result, + // ), + // ).to.eql(null); + // }); + // it("to few parameters", () => { + // const rootRoute = [({ foo: schema.number() })] as const; + // const anotherRootRoute = [({})] as const; + // expect( + // mapPath(...rootRoute,anotherRootRoute.createPath({ }), id), + // ).to.eql(null); + // }); + // it("to many parameters", () => { + // const rootRoute = [({})] as const; + // const anotherRootRoute = [({ foo: schema.number() })] as const; + // expect( + // mapPath(...rootRoute,anotherRootRoute.createPath({ foo: 1 }), id), + // ).to.eql(null); + // }); + // }); + describe("schema", () => { + describe("objects", () => { + it("standard", () => { + const rootRoute = [ + "root", + { + foo: schema.object({ + bar: schema.number(), + baz: schema.number(), + }), + mep: schema.number(), + }, + ] as const; + + const inputValue = { foo: { bar: 2, baz: 5 }, mep: 1 }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); - describe("schema", () => { - describe("objects", () => { - it("standard", () => { - const rootRoute = createRootRoute({ - foo: schema.object({ - bar: schema.number(), - baz: schema.number(), - }), - mep: schema.number(), - }); - const inputValue = { "/": { foo: { bar: 2, baz: 5 }, mep: 1 } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); + assertType< + IsEqual< + Parameters>[2], + { + foo: { bar: number; baz: number }; + mep: number; + } + > + >(); - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: { bar: number; baz: number }; mep: number }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: { bar: number; baz: number }; mep: number }; - } - > - >(); - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, - }); - }); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: { bar: number; baz: number }; + mep: number; + } + > + >(); - it("default", () => { - const rootRoute = createRootRoute({ - foo: schema.object({ - bar: schema.number(), - baz: schema.number({ default: 10 }), - }), + expect(outputValue).to.eql({ + parameter: inputValue, + hasChildRouteActive: false, + }); }); - const inputValue = { "/": { foo: { bar: 2, baz: null } } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - assertType< - IsEqual< - Parameters[0], + it("default", () => { + const rootRoute = [ + "root", { - "/": { foo: { bar: number; baz: number | null } }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: { bar: number; baz: number } }; - } - > - >(); - expect(outputValue).to.eql({ - parameter: { - "/": { + foo: schema.object({ + bar: schema.number(), + baz: schema.number({ default: 10 }), + }), + }, + ] as const; + const inputValue = { foo: { bar: 2, baz: null } }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: { bar: number; baz: number | null }; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: { bar: number; baz: number }; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: { bar: 2, baz: 10, }, }, - }, - hasChildRouteActive: false, - }); - }); - }); - - describe("number", () => { - it("default", () => { - const rootRoute = createRootRoute({ - foo: schema.number({ default: 10 }), - }); - const alternativeDefault = createRootRoute({ - foo: schema.number({ default: 20 }), - }); - const inputValue = { "/": { foo: null } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: number | null }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: number }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { "/": { foo: 10 } }, - hasChildRouteActive: false, - }); - expect( - alternativeDefault.map( - rootRoute.createPath({ "/": { foo: 10 } }), - id, - ), - ).to.eql({ - parameter: { "/": { foo: 20 } }, - hasChildRouteActive: false, - }); - }); - }); - - it("validate", () => { - const rootRoute = createRootRoute({ - foo: schema.number({ - validate: function (value): value is 10 | 20 { - return [10, 20].includes(value); - }, - }), - }); - - const inputValue = { "/": { foo: 10 as const } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: 10 | 20 }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: 10 | 20 }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { "/": { foo: 10 } }, - hasChildRouteActive: false, - }); - - expect(() => { - rootRoute.createPath({ - "/": { - foo: 30 as 20, - }, - }); - }).to.throw(); - }); - - describe("string", () => { - it("standard", () => { - const rootRoute = createRootRoute({ - foo: schema.string(), + hasChildRouteActive: false, + }); }); - const inputValue = { "/": { foo: "bar" } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: string }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], + it("all-default", () => { + const rootRoute = [ + "root", { - "/": { foo: string }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, + foo: schema.object({ + bar: schema.number({ default: 5 }), + baz: schema.number({ default: 10 }), + }), + }, + ] as const; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, null), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + null | { + foo: null | { bar: number | null; baz: number | null }; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: { bar: number; baz: number }; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { + foo: { + bar: 5, + baz: 10, + }, + }, + hasChildRouteActive: false, + }); }); }); - - it("tokens", () => { - const rootRoute = createRootRoute({ - foo: schema.string(), - }); - - const inputValue = { "/": { foo: Object.values(TOKENS).join("") } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], + describe("number", () => { + it("default", () => { + const rootRoute = [ + "root", { - "/": { foo: string }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], + foo: schema.number({ default: 10 }), + }, + ] as const; + const alternativeDefault = [ + "root", { - "/": { foo: string }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, + foo: schema.number({ default: 20 }), + }, + ] as const; + const inputValue = { foo: null }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + null | { + foo: number | null; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: number; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: 10 }, + hasChildRouteActive: false, + }); + expect( + mapPath( + ...alternativeDefault, + createPath(...rootRoute, { foo: 10 }), + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ), + ).to.eql({ + parameter: { foo: 20 }, + hasChildRouteActive: false, + }); }); }); - it("validate", () => { - const rootRoute = createRootRoute({ - foo: schema.string({ - validate: function (value): value is "bar" | "baz" { - return ["bar", "baz"].includes(value); - }, + const rootRoute = [ + "root", + { + foo: schema.number({ + validate: function (value): value is 10 | 20 { + return [10, 20].includes(value); + }, + }), + }, + ] as const; + const inputValue = { foo: 10 as const }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, }), - }); - - const inputValue = { "/": { foo: "bar" as const } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - + ); assertType< IsEqual< - Parameters[0], + Parameters>[2], { - "/": { foo: "bar" | "baz" }; + foo: 10 | 20; } > >(); @@ -510,185 +530,250 @@ describe("map", () => { IsEqual< Exclude["parameter"], { - "/": { foo: "bar" | "baz" }; + foo: 10 | 20; } > >(); - expect(outputValue).to.eql({ - parameter: { "/": { foo: "bar" } }, + parameter: { foo: 10 }, hasChildRouteActive: false, }); - expect(() => { - rootRoute.createPath({ - "/": { - foo: "mep" as "bar", - }, + createPath(...rootRoute, { + foo: 30 as 20, }); }).to.throw(); }); - - it("default", () => { - const rootRoute = createRootRoute({ - foo: schema.string({ - default: null, - }), - }); - - const inputValue = { "/": { foo: null } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: string | null }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], + describe("string", () => { + it("standard", () => { + const rootRoute = [ + "root", { - "/": { foo: string | null }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { "/": { foo: null } }, - hasChildRouteActive: false, - }); - }); - - it("empty", () => { - const rootRoute = createRootRoute({ - foo: schema.string({ - default: "", - }), + foo: schema.string(), + }, + ] as const; + const inputValue = { foo: "bar" }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: string; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: string; + } + > + >(); + expect(outputValue).to.eql({ + parameter: inputValue, + hasChildRouteActive: false, + }); }); - - const inputValue = { "/": { foo: "" } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], + it("tokens", () => { + const rootRoute = [ + "root", { - "/": { foo: string | null }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: string }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { "/": { foo: "" } }, - hasChildRouteActive: false, - }); - }); - }); - - describe("list", () => { - it("standard", () => { - const rootRoute = createRootRoute({ - foo: schema.list({ - entities: schema.number(), - }), + foo: schema.string(), + }, + ] as const; + const inputValue = { foo: Object.values(TOKENS).join("") }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: string; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: string; + } + > + >(); + expect(outputValue).to.eql({ + parameter: inputValue, + hasChildRouteActive: false, + }); }); - - const inputValue = { "/": { foo: [1, 2, 3] } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: number[] }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], + it("validate", () => { + const rootRoute = [ + "root", { - "/": { foo: number[] }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { "/": { foo: [1, 2, 3] } }, - hasChildRouteActive: false, - }); - }); - - it("empty", () => { - const rootRoute = createRootRoute({ - foo: schema.list({ - entities: schema.number(), - }), + foo: schema.string({ + validate: function (value): value is "bar" | "baz" { + return ["bar", "baz"].includes(value); + }, + }), + }, + ] as const; + const inputValue = { foo: "bar" as const }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: "bar" | "baz"; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: "bar" | "baz"; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: "bar" }, + hasChildRouteActive: false, + }); + expect(() => { + createPath(...rootRoute, { + foo: "mep" as "bar", + }); + }).to.throw(); }); - - const inputValue = { "/": { foo: [] } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], + it("default", () => { + const rootRoute = [ + "root", { - "/": { foo: number[] }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], + foo: schema.string({ + default: null, + }), + }, + ] as const; + const inputValue = { foo: null }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + null | { + foo: string | null; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: string | null; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: null }, + hasChildRouteActive: false, + }); + }); + it("empty", () => { + const rootRoute = [ + "root", { - "/": { foo: number[] }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { "/": { foo: [] } }, - hasChildRouteActive: false, + foo: schema.string({ + default: "", + }), + }, + ] as const; + const inputValue = { foo: "" }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + null | { + foo: string | null; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: string; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: "" }, + hasChildRouteActive: false, + }); }); }); - - describe("default", () => { - it("empty", () => { - const rootRoute = createRootRoute({ - foo: schema.list({ - default: [], - entities: schema.number(), - }), - }); - const alternativeDefault = createRootRoute({ - foo: schema.list({ - default: [1], - entities: schema.number(), + describe("list", () => { + it("standard", () => { + const rootRoute = [ + "root", + { + foo: schema.list({ + entities: schema.number(), + }), + }, + ] as const; + const inputValue = { foo: [1, 2, 3] }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, }), - }); - - const inputValue = { "/": { foo: [] } }; - const outputValue = rootRoute.map( - rootRoute.createPath(inputValue), - id, ); - assertType< IsEqual< - Parameters[0], + Parameters>[2], { - "/": { foo: number[] | null }; + foo: number[]; } > >(); @@ -696,60 +781,39 @@ describe("map", () => { IsEqual< Exclude["parameter"], { - "/": { foo: number[] }; + foo: number[]; } > >(); - expect(outputValue).to.eql({ - parameter: { "/": { foo: [] } }, - hasChildRouteActive: false, - }); - expect( - alternativeDefault.map( - rootRoute.createPath({ "/": { foo: [] } }), - id, - ), - ).to.eql({ - parameter: { "/": { foo: [1] } }, - hasChildRouteActive: false, - }); - expect( - alternativeDefault.map( - rootRoute.createPath({ "/": { foo: null } }), - id, - ), - ).to.eql({ - parameter: { "/": { foo: [1] } }, + parameter: { foo: [1, 2, 3] }, hasChildRouteActive: false, }); }); - it("empty", () => { - const rootRoute = createRootRoute({ - foo: schema.list({ - default: [1], - entities: schema.number(), - }), - }); - const alternativeDefault = createRootRoute({ - foo: schema.list({ - default: [2], - entities: schema.number(), + const rootRoute = [ + "root", + { + foo: schema.list({ + entities: schema.number(), + }), + }, + ] as const; + const inputValue = { foo: [] }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, }), - }); - - const inputValue = { "/": { foo: [] } }; - const outputValue = rootRoute.map( - rootRoute.createPath(inputValue), - id, ); - assertType< IsEqual< - Parameters[0], + Parameters>[2], { - "/": { foo: number[] | null }; + foo: number[]; } > >(); @@ -757,39 +821,171 @@ describe("map", () => { IsEqual< Exclude["parameter"], { - "/": { foo: number[] }; + foo: number[]; } > >(); - expect(outputValue).to.eql({ - parameter: { "/": { foo: [] } }, - hasChildRouteActive: false, - }); - expect( - alternativeDefault.map( - rootRoute.createPath({ "/": { foo: [1] } }), - id, - ), - ).to.eql({ - parameter: { "/": { foo: [2] } }, + parameter: { foo: [] }, hasChildRouteActive: false, }); }); - }); - - it("object", () => { - const rootRoute = createRootRoute({ - foo: schema.list({ - entities: schema.object({ - bar: schema.string(), - baz: schema.number(), - }), - }), + describe("default", () => { + it("empty", () => { + const rootRoute = [ + "root", + { + foo: schema.list({ + default: [], + entities: schema.number(), + }), + }, + ] as const; + const alternativeDefault = [ + "root", + { + foo: schema.list({ + default: [1], + entities: schema.number(), + }), + }, + ] as const; + const inputValue = { foo: [] }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + null | { + foo: number[] | null; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: number[]; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: [] }, + hasChildRouteActive: false, + }); + expect( + mapPath( + ...alternativeDefault, + createPath(...rootRoute, { foo: [] }), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ), + ).to.eql({ + parameter: { foo: [1] }, + hasChildRouteActive: false, + }); + expect( + mapPath( + ...alternativeDefault, + createPath(...rootRoute, { foo: null }), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ), + ).to.eql({ + parameter: { foo: [1] }, + hasChildRouteActive: false, + }); + }); + it("empty", () => { + const rootRoute = [ + "root", + { + foo: schema.list({ + default: [1], + entities: schema.number(), + }), + }, + ] as const; + const alternativeDefault = [ + "root", + { + foo: schema.list({ + default: [2], + entities: schema.number(), + }), + }, + ] as const; + const inputValue = { foo: [] }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + null | { + foo: number[] | null; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: number[]; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: [] }, + hasChildRouteActive: false, + }); + expect( + mapPath( + ...alternativeDefault, + createPath(...rootRoute, { foo: [1] }), + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ), + ).to.eql({ + parameter: { foo: [2] }, + hasChildRouteActive: false, + }); + }); }); - - const inputValue = { - "/": { + it("object", () => { + const rootRoute = [ + "root", + { + foo: schema.list({ + entities: schema.object({ + bar: schema.string(), + baz: schema.number(), + }), + }), + }, + ] as const; + const inputValue = { foo: [ { bar: "mep", @@ -800,30 +996,34 @@ describe("map", () => { baz: 2, }, ], - }, - }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: { bar: string; baz: number }[] }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: { bar: string; baz: number }[] }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: { - "/": { + }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: { bar: string; baz: number }[]; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: { bar: string; baz: number }[]; + } + > + >(); + expect(outputValue).to.eql({ + parameter: { foo: [ { bar: "mep", @@ -835,152 +1035,177 @@ describe("map", () => { }, ], }, - }, - hasChildRouteActive: false, + hasChildRouteActive: false, + }); }); }); - }); - - describe("boolean", () => { - it("true", () => { - const rootRoute = createRootRoute({ - foo: schema.boolean(), - }); - - const inputValue = { "/": { foo: true } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], + describe("boolean", () => { + it("true", () => { + const rootRoute = [ + "root", { - "/": { foo: boolean }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { foo: boolean }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, - }); - }); - - it("false", () => { - const rootRoute = createRootRoute({ - foo: schema.boolean(), + foo: schema.boolean(), + }, + ] as const; + const inputValue = { foo: true }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: boolean; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: boolean; + } + > + >(); + expect(outputValue).to.eql({ + parameter: inputValue, + hasChildRouteActive: false, + }); }); - - const inputValue = { "/": { foo: false } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: boolean }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], + it("false", () => { + const rootRoute = [ + "root", { - "/": { foo: boolean }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, + foo: schema.boolean(), + }, + ] as const; + const inputValue = { foo: false }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: boolean; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: boolean; + } + > + >(); + expect(outputValue).to.eql({ + parameter: inputValue, + hasChildRouteActive: false, + }); }); }); - }); - - describe("date", () => { - it("standard", () => { - const rootRoute = createRootRoute({ - foo: schema.date(), - }); - - const now = new Date(); - const inputValue = { "/": { foo: now } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], - { - "/": { foo: Date }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], + describe("date", () => { + it("standard", () => { + const rootRoute = [ + "root", { - "/": { foo: Date }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, - }); - }); - }); - - describe("enum", () => { - it("standard", () => { - const rootRoute = createRootRoute({ - foo: schema.enum({ - enumerations: { - bar: null, - baz: schema.number(), + foo: schema.date(), }, - }), + ] as const; + const now = new Date(); + const inputValue = { foo: now }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { + foo: Date; + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { + foo: Date; + } + > + >(); + expect(outputValue).to.eql({ + parameter: inputValue, + hasChildRouteActive: false, + }); }); - - const inputValue = { "/": { foo: { type: "baz" as const, value: 2 } } }; - const outputValue = rootRoute.map(rootRoute.createPath(inputValue), id); - - assertType< - IsEqual< - Parameters[0], + }); + describe("enum", () => { + it("standard", () => { + const rootRoute = [ + "root", { - "/": { + foo: schema.enum({ + enumerations: { + bar: null, + baz: schema.number(), + }, + }), + }, + ] as const; + const inputValue = { + foo: { type: "baz" as const, value: 2 }, + }; + const outputValue = mapPath( + ...rootRoute, + createPath(...rootRoute, inputValue), + (parameter, rest) => ({ + parameter, + hasChildRouteActive: rest !== null, + }), + ); + assertType< + IsEqual< + Parameters>[2], + { foo: | { type: "bar"; value: null } | { type: "baz"; value: number }; - }; - } - > - >(); - assertType< - IsEqual< - Exclude["parameter"], - { - "/": { + } + > + >(); + assertType< + IsEqual< + Exclude["parameter"], + { foo: | { type: "bar"; value: null } | { type: "baz"; value: number }; - }; - } - > - >(); - - expect(outputValue).to.eql({ - parameter: inputValue, - hasChildRouteActive: false, + } + > + >(); + expect(outputValue).to.eql({ + parameter: inputValue, + hasChildRouteActive: false, + }); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 0d60de3..9c05736 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,12 +39,11 @@ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": "./src/", /* Base directory to resolve non-absolute module names. */ + "moduleResolution": "bundler", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ + "types": ["mocha"], /* Type declaration files to be included in compilation. */ "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ @@ -56,4 +55,4 @@ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ } -} \ No newline at end of file +}