Coder Social home page Coder Social logo

idopesok / type-safe-paths Goto Github PK

View Code? Open in Web Editor NEW
12.0 1.0 0.0 118 KB

TypeSafePaths is a TypeScript library that provides a type-safe way to manage URL paths and their parameters in a web application. It leverages the power of Zod for schema validation, ensuring that both path parameters and search parameters conform to specified schemas.

License: MIT License

JavaScript 18.28% TypeScript 81.72%

type-safe-paths's Introduction

TypeSafePaths

TypeSafePaths is a TypeScript library that provides a type-safe way to manage URL paths and their parameters in a web application. It leverages the power of Zod for schema validation, ensuring that both path parameters and search parameters conform to specified schemas.

Features

  • Type-Safe URL Paths: Define URL paths with type-safe parameters.
  • Schema Validation: Use Zod schemas to validate search parameters and metadata.
  • Path Construction: Easily build URLs from defined paths and parameters.
  • Path Matching: Match and extract parameters from URLs.
  • Search Parameter Parsing: Parse and validate search parameters from URLs.

Installation

npm install type-safe-paths

Usage

Defining Paths

First, create an instance of TypeSafePaths and define your paths with the required schemas.

import z from "zod"
import { TypeSafePaths, createPathHelpers } from "type-safe-paths"

// Create a new instance of TypeSafePaths with a metadata schema
const paths = new TypeSafePaths({
  metadataSchema: z.object({
    allowedPermissions: z.array(z.enum(["user", "admin"])),
    mustBeLoggedIn: z.boolean(),
  }),
})
  // Add the first path with metadata and search parameters
  .add("/posts/details/:postId", {
    metadata: {
      allowedPermissions: ["admin"],
      mustBeLoggedIn: true,
    },
    searchParams: z.object({
      query: z.string().optional(),
    }),
  })
  // Add a nested path with different metadata and search parameters
  .add("/posts/details/:postId/:commentId", {
    metadata: {
      allowedPermissions: ["user"],
      mustBeLoggedIn: false,
    },
    searchParams: z.object({
      query: z.string().default("hello world"),
      optional: z.string().default("the parsing worked"),
    }),
  })

Creating Path Helpers

Use createPathHelpers to generate helper functions for building, matching, and parsing paths.

const {
  buildPath,
  matchPath,
  parseSearchParamsForPath,
  extractParamsFromPathName,
} = createPathHelpers(paths)

Building Paths

Construct a URL from a defined path and its parameters.

// Build a URL for the path "/posts/details/:postId/:commentId"
const url = buildPath("/posts/details/:postId/:commentId", {
  params: {
    postId: "123",
    commentId: "456",
  },
  searchParams: {
    query: "example query",
  },
})

console.log(url)
// Output: /posts/details/123/456?query="example+query"

Using buildPath with Next.js

You can use the buildPath function with Next.js component for type-safe routing.

import Link from 'next/link';
import { buildPath } from 'type-safe-paths';

const postId = "123";
const commentId = "456";

const url = buildPath("/posts/details/:postId/:commentId", {
  params: { postId, commentId },
  searchParams: { query: "example query" },
});

const MyComponent = () => (
  <Link href={url}>
    <a>Go to Comment</a>
  </Link>
);

export default MyComponent;

Matching Paths

Match a URL against the defined paths and extract metadata.

// Match a URL against the registered paths
const matchResult = matchPath("/posts/details/123/456?query=example+query")

if (matchResult) {
  console.log(matchResult.path)
  // Output: /posts/details/:postId/:commentId

  console.log(matchResult.metadata)
  // Output: { allowedPermissions: ["user"], testing: "hello world" }
} else {
  console.log("No matching path found.")
}

Parsing Search Parameters

Parse and validate search parameters from a URL.

// Create a URLSearchParams instance with some query parameters
const searchParams = new URLSearchParams()
searchParams.set("query", "example query")

// Parse and validate the search parameters for the specified path
const parsedParams = parseSearchParamsForPath(
  searchParams,
  "/posts/details/:postId/:commentId"
)

console.log(parsedParams)
// Output: { query: "example query", optional: "the parsing worked" }

Extracting Path Parameters

Extract path parameters from a URL based on a defined path.

// Extract path parameters from a URL based on the specified path template
const params = extractParamsFromPathName(
  "/posts/details/123/456?query=example+query",
  "/posts/details/:postId/:commentId"
)

console.log(params)
// Output: { postId: "123", commentId: "456" }

Inferring Types

Use inferPathProps to infer the types of path parameters and search parameters for a given path. This ensures that your components receive the correct types, enhancing type safety and reducing errors.

import { inferPathProps } from "type-safe-paths";

// Infer types for the path "/posts/details/:postId/:commentId"
type PathProps = inferPathProps<typeof paths, "/posts/details/:postId/:commentId">;

// Example component using inferred types
export default async function MyPage({ params, searchParams }: PathProps) {
  // params and searchParams will have the correct types based on the defined schema
  console.log(params.postId); // string
  console.log(params.commentId); // string
  console.log(searchParams.query); // string
  console.log(searchParams.optional); // string

  // Your component logic here
  ...
}

This approach ensures that params and searchParams in your component have the correct types as specified in the path definition, making your code more robust and maintainable.

Type-Safe Link Component

You can create a type-safe link component using the inferLinkComponentProps utility function. This ensures that the link component receives the correct props based on the defined paths.

import Link from 'next/link'
import { TypeSafePaths, createPathHelpers, inferLinkComponentProps } from 'type-safe-paths'
import { forwardRef } from 'react'

// Create a new instance of TypeSafePaths
const paths = new TypeSafePaths({
  // Your path definitions here
  ...
})

// Create a type-safe link component
const TypeSafeLink = forwardRef<
  HTMLAnchorElement,
  inferLinkComponentProps<typeof paths, typeof Link>
>((props, ref) => {
  const { getHrefFromLinkComponentProps } = createPathHelpers(paths)
  return (
    <Link {...props} href={getHrefFromLinkComponentProps(props)} ref={ref} />
  )
})

In this example, we create a TypeSafeLink component using forwardRef. The component receives props of type inferLinkComponentProps<typeof myPaths, typeof Link>, which infers the correct props based on the defined paths in myPaths.

Inside the component, we use the getHrefFromLinkComponentProps function from createPathHelpers to generate the href prop for the Link component based on the provided props.

Now you can use the TypeSafeLink component in your application, and it will ensure that the props you pass to it match the defined paths and their parameters.

<TypeSafeLink
  href={{
    pathname: "/posts/details/:postId/:commentId",
    params: { postId: "123", commentId: "456" },
    searchParams: { query: "example query" },
  }}
>
  Go to Comment
</TypeSafeLink>

The TypeSafeLink component will provide type safety and autocomplete suggestions for the path and params props based on your defined paths.

API

TypeSafePaths

constructor(args?: { metadataSchema?: TMetadataSchema })

Creates an instance of TypeSafePaths with optional metadata schema.

add<TPath extends string, TSearchParams extends z.AnyZodObject | undefined>(path: TPath, opts: { searchParams?: TSearchParams, metadata: z.input<TMetadataSchema> })

Adds a path to the registry with optional search parameters and metadata.

createPathHelpers(registry: TypeSafePaths)

buildPath<TKey extends keyof TRegistry["$registry"]>(k: TKey, opts: { params: { [k in TRegistry["$registry"][TKey]["params"]]: string }, searchParams?: z.input<TRegistry["$registry"][TKey]["searchParams"]> })

Builds a URL from a defined path and its parameters.

matchPath(pathname: string)

Matches a URL against the defined paths and extracts metadata.

extractParamsFromPathName<TPath extends keyof TRegistry["$registry"]>(pathname: string, path: TPath)

Extracts path parameters from a URL.

parseSearchParamsForPath<TPath extends keyof TRegistry["$registry"]>(searchParams: URLSearchParams, path: TPath)

Parses and validates search parameters from a URL.

inferPathProps

inferPathProps<TRegistry extends TypeSafePaths<any, any>, TPath extends keyof TRegistry["$registry"]>

Infers the types of path parameters and search parameters for a given path.

inferLinkComponentProps

inferLinkComponentProps<TRegistry extends TypeSafePaths<any, any>, TLinkComponent extends React.ComponentType<any>>

Infers the props for a type-safe link component based on the defined paths and the underlying link component.

License

MIT

Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.

Acknowledgments

This library was inspired by the need for a type-safe way to manage URL paths and parameters in modern web applications, leveraging the power of Zod for schema validation.


Feel free to reach out if you have any questions or need further assistance!

type-safe-paths's People

Contributors

idopesok avatar github-actions[bot] avatar

Stargazers

Amir Anvarov avatar  avatar Merouane avatar Andrejs Agejevs avatar Thomas Harr avatar Sébastien Lorber avatar Askar Yusupov avatar Nikita avatar Martin Litvaj avatar Vlad Sazonau avatar wes convery avatar  avatar

Watchers

 avatar

type-safe-paths's Issues

xported variable 'postsPaths' has or is using name 'TPathRegistryNode' from external module "XXXXX" but cannot be named.ts(4023)

Hey mate, what you did is pretty awesome work! Very handy tool to use.

I'm struggling with one issue at the moment where typescript is complaining with error "Exported variable 'postsPaths' has or is using name 'TPathRegistryNode' from external module "/Users/xxxxxxxxxx/node_modules/type-safe-paths/dist/index" but cannot be named.ts(4023)", not sure if this is a bug or something. Can you share your thoughts?

image

Here is the code of the whole file:

import z from "zod"
import { TypeSafePaths, createPathHelpers,  } from "type-safe-paths"


// Create a new instance of TypeSafePaths with a metadata schema
const paths = new TypeSafePaths({
	metadataSchema: z.object({
		allowedPermissions: z.array(z.enum(["user", "admin"])),
		mustBeLoggedIn: z.boolean(),
	}),
})
	// Add the first path with metadata and search parameters
	.add("/posts/details/:postId", {
		metadata: {
			allowedPermissions: ["admin"],
			mustBeLoggedIn: true,
		},
		searchParams: z.object({
			query: z.string().optional(),
		}),
	})
	// Add a nested path with different metadata and search parameters
	.add("/posts/details/:postId/:commentId", {
		metadata: {
			allowedPermissions: ["user"],
			mustBeLoggedIn: false,
		},
		searchParams: z.object({
			query: z.string().default("hello world"),
			optional: z.string().default("the parsing worked"),
		}),
	})


export const postsPaths = createPathHelpers(paths);

I'm not sure why it's happening.

For context, my typescript version is 5.5.2.

Would really appreciate your help!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.