├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── coverage.svg ├── jestconfig.json ├── logo.svg ├── package.json ├── src ├── __tests__ │ └── sample.ts ├── example.tsx └── index.ts ├── tsconfig.json ├── tslint.json ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/.DS_Store 3 | node_modules 4 | /lib 5 | /coverage 6 | .vscode 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all", 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Colin McDonnell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

nxtk

2 |

3 | TypeScript utilities for Next.js 4 |

5 | 6 | # Installation 7 | 8 | To install the latest version: 9 | 10 | ```sh 11 | npm install --save nxtk 12 | ``` 13 | 14 | ```sh 15 | yarn add nxtk 16 | ``` 17 | 18 | ⚠️ Requires TypeScript 3.2+ and `"strictNullChecks": true` to work properly! 19 | 20 | ## Importing 21 | 22 | ```tsx 23 | import { nxtk } from 'nxtk'; 24 | ``` 25 | 26 | ## `nxtk.fetch` 27 | 28 | This utility reduces the boilerplate required to implement pages with data fetching. It uses type inference to detect the return type of `getStaticProps`, `getServerSideProps`, or both. Then it merges the types so you can trivially add strong typing to your component props. 29 | 30 | ### Defining fetch functions 31 | 32 | ```tsx 33 | // pages/yourpage.tsx 34 | 35 | import React from 'react'; 36 | import { nxtk } from 'nxtk'; 37 | 38 | const Fetcher = nxtk.fetch({ 39 | async server(ctx) { 40 | // ctx = GetServerSidePropsContext 41 | const props = { serverSideProp: 'Hello' }; 42 | return { props }; 43 | }, 44 | async static(ctx) { 45 | // ctx = GetStaticPropsContext 46 | const props = { staticProp: 'World' }; 47 | return { props }; 48 | }, 49 | }); 50 | ``` 51 | 52 | The `ctx` inputs are automatically typed for you. 53 | 54 | 87 | 88 | 89 | 90 | After creating your "fetcher", export its `getServerSideProps` and `getStaticProps` properties so Next.js can access them. 91 | 92 | ```tsx 93 | export const getServerSideProps = Fetcher.getServerSideProps; 94 | export const getStaticProps = Fetcher.getStaticProps; 95 | ``` 96 | 97 | ### Inferring prop types! 98 | 99 | The best part: `nxtk` automatically **infers** the return types of your fetcher functions and **merges** them together. So you can properly type your page components: 100 | 101 | ```tsx 102 | type InferredProps = typeof Fetcher['props']; // { serverSideProp: string; staticProp: string }; 103 | 104 | export default function Home(props: InferredProps) { 105 | props; 106 | return ( 107 |
108 |

{`${props.serverSideProp} ${props.staticProp}`}

109 |
110 | ); 111 | } 112 | ``` 113 | 114 | As you can see, the return type of `getServerSideProps` (`{ serverSideProp: string}`) and `getStaticProps` (`{ staticProp: string }`) are inferred and merged into `{ serverSideProp: string; staticProp: string }`. You can access this typing with `typeof Fetcher['props']`. 115 | 116 | This may not look like much with a simple example, but imagine you are doing a serious of complex database queries using a type-safe ORM like TypeORM or Prisma. No matter how compicated your fetching logic gets, `nxtk` can infer it. No need to keep your component `props` in sync with your fetching logic! 117 | 118 | ### Full example 119 | 120 | A full sample page is available at [https://github.com/vriad/nxtk/blob/master/src/example.tsx](https://github.com/vriad/nxtk/blob/master/src/example.tsx). 121 | 122 | ## `nxtk.api` 123 | 124 | This is a helper function for defining API routes. 125 | 126 | ```tsx 127 | // /api/hello.ts 128 | import { nxtk } from 'nxtk'; 129 | 130 | export default nxtk.api((req, res) => { 131 | if (req.method !== 'POST') return res.status(200).json({ name: 'unsupported' }); 132 | res.status(200).json({ message: 'yay post!' }); 133 | }); 134 | ``` 135 | 136 | ## `nxtk.???` 137 | 138 | If you have any other suggestions of how nxtk could make using Next.js and TypeScript more painless, create an issue! I hope to expand the scope of `nxtk` wherever pain points exist. 139 | 140 | Created by [@vriad](https://twitter.com/vriad)
141 | MIT License 142 | -------------------------------------------------------------------------------- /coverage.svg: -------------------------------------------------------------------------------- 1 | CoverageCoverage93.13%93.13% -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.(t|j)sx?$": "ts-jest" 4 | }, 5 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 6 | "moduleFileExtensions": [ 7 | "ts", 8 | "tsx", 9 | "js", 10 | "jsx", 11 | "json", 12 | "node" 13 | ], 14 | "coverageReporters": [ 15 | "json-summary", 16 | "text", 17 | "lcov" 18 | ] 19 | } -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nxtk", 3 | "version": "0.4.0", 4 | "description": "A project template for TypeScript npm packages", 5 | "main": "./lib/src/index.js", 6 | "types": "./lib/src/index.d.ts", 7 | "files": [ 8 | "lib" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/vriad/nxtk" 13 | }, 14 | "author": "Colin McDonnell ", 15 | "license": "MIT", 16 | "sideEffects": false, 17 | "bugs": { 18 | "url": "https://github.com/vriad/nxtk/issues" 19 | }, 20 | "scripts": { 21 | "clean": "rm -rf lib/*", 22 | "build": "npm run clean && tsc" 23 | }, 24 | "homepage": "https://github.com/vriad/nxtk", 25 | "dependencies": {}, 26 | "tags": [], 27 | "keywords": [], 28 | "devDependencies": { 29 | "@types/jest": "^25.1.4", 30 | "@types/react": "^16.9.44", 31 | "jest": "^25.1.0", 32 | "make-coverage-badge": "^1.2.0", 33 | "next": "^10.0.3", 34 | "nodemon": "^2.0.2", 35 | "prettier": "^1.19.1", 36 | "react": "^16.13.1", 37 | "react-dom": "^16.13.1", 38 | "ts-jest": "^25.2.1", 39 | "tslint": "^6.1.0", 40 | "tslint-config-prettier": "^1.18.0", 41 | "typescript": "3.2" 42 | }, 43 | "peerDependencies": { 44 | "next": "^10" 45 | } 46 | } -------------------------------------------------------------------------------- /src/__tests__/sample.ts: -------------------------------------------------------------------------------- 1 | test('passing test', () => { 2 | console.log('passes!'); 3 | }); 4 | 5 | test('failing test', () => { 6 | throw new Error('fails!'); 7 | }); 8 | 9 | test('expect example', () => { 10 | expect(5).toEqual(6); // fails 11 | }); 12 | -------------------------------------------------------------------------------- /src/example.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { nxtk } from '.'; 3 | 4 | const Fetcher = nxtk.fetch({ 5 | async server(_ctx) { 6 | return { props: { greeting: 'Hello' } }; 7 | }, 8 | async static(_ctx) { 9 | return { props: { subject: 'World' } }; 10 | }, 11 | }); 12 | 13 | export const getServerSideProps = Fetcher.getServerSideProps; 14 | export const getStaticProps = Fetcher.getStaticProps; 15 | 16 | export default function Home(props: typeof Fetcher['props']) { 17 | return

{`${props.greeting} ${props.subject}`}

; 18 | } 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { GetServerSidePropsContext, GetStaticPropsContext, NextApiRequest, NextApiResponse } from 'next'; 2 | 3 | export namespace nxtk { 4 | type getServerSideProps = (ctx: GetServerSidePropsContext) => Promise<{ props: T }>; 5 | const getServerSideProps = (func: getServerSideProps) => func; 6 | 7 | type getStaticProps = (ctx: GetStaticPropsContext) => Promise<{ props: T }>; 8 | const getStaticProps = (func: getStaticProps) => func; 9 | 10 | type getters = Partial<{ 11 | server: getServerSideProps; 12 | static: getStaticProps; 13 | }>; 14 | 15 | type unwrapReturnedPromiseProps Promise<{ props: any }>> = T extends ( 16 | ...args: any[] 17 | ) => Promise<{ props: infer U }> 18 | ? U 19 | : {}; 20 | 21 | type identity = T; 22 | type flatten = identity<{ [k in keyof T]: T[k] }>; 23 | type props< 24 | A extends (...args: any[]) => Promise<{ props: any }>, 25 | B extends (...args: any[]) => Promise<{ props: any }> = () => Promise<{ 26 | props: {}; 27 | }> 28 | > = flatten & unwrapReturnedPromiseProps>; 29 | 30 | export const fetch = >(args: C) => { 31 | return class NextConfig { 32 | static props: props< 33 | C['server'] extends (...args: any[]) => Promise<{ props: any }> ? C['server'] : () => Promise<{ props: {} }>, 34 | C['static'] extends (...args: any[]) => Promise<{ props: any }> ? C['static'] : () => Promise<{ props: {} }> 35 | >; 36 | static getServerSideProps = args.server; 37 | static getStaticProps = args.static; 38 | }; 39 | }; 40 | 41 | export type ApiRoute = (req: NextApiRequest, res: NextApiResponse) => void; 42 | export const api = (route: ApiRoute) => route; 43 | } 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es5", 5 | "es6", 6 | "es7", 7 | "esnext", 8 | "dom" 9 | ], 10 | "target": "es5", 11 | "module": "commonjs", 12 | "declaration": true, 13 | // "moduleResolution": "node", 14 | "skipLibCheck": true, 15 | "outDir": "./lib", 16 | "allowUnreachableCode": false, 17 | "allowUnusedLabels": false, 18 | "esModuleInterop": true, 19 | "emitDecoratorMetadata": true, 20 | "experimentalDecorators": true, 21 | "sourceMap": true, 22 | "jsx": "react", 23 | "resolveJsonModule": true, 24 | "strict": true, 25 | "noImplicitAny": true, 26 | "strictNullChecks": true, 27 | "noImplicitThis": true, 28 | "alwaysStrict": true, 29 | "strictPropertyInitialization": false, 30 | "noUnusedLocals": true, 31 | "noUnusedParameters": true, 32 | "noImplicitReturns": true, 33 | "noFallthroughCasesInSwitch": true, 34 | }, 35 | "include": [ 36 | "src", 37 | "tsconfig.json" 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | ] 42 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-config-prettier" 5 | ] 6 | } --------------------------------------------------------------------------------