├── .nvmrc
├── public
├── sw.js
├── og.png
├── ditto.png
├── favicon.ico
└── porygon.png
├── cdk.json
├── .github
└── FUNDING.yml
├── next-env.d.ts
├── src
├── components
│ ├── button.tsx
│ ├── spacer.tsx
│ └── pokemon.tsx
├── pages
│ ├── _app.tsx
│ ├── [ditto].tsx
│ ├── random.tsx
│ ├── ssr.tsx
│ ├── index.tsx
│ ├── isr.tsx
│ ├── pokemons
│ │ └── [porygon].tsx
│ └── _document.tsx
└── styles.css
├── lib
├── fetch.ts
└── gtag.ts
├── .gitignore
├── bin.ts
├── stack.ts
├── next.config.js
├── package.json
├── LICENSE
├── tsconfig.json
├── README.md
└── types
├── Pokedex.ts
└── Pokemon.ts
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14
--------------------------------------------------------------------------------
/public/sw.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npm run deploy"
3 | }
--------------------------------------------------------------------------------
/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ibrahimcesar/nextjs-ssr-isr-cdk-aws/HEAD/public/og.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ibrahimcesar]
4 |
--------------------------------------------------------------------------------
/public/ditto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ibrahimcesar/nextjs-ssr-isr-cdk-aws/HEAD/public/ditto.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ibrahimcesar/nextjs-ssr-isr-cdk-aws/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/porygon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ibrahimcesar/nextjs-ssr-isr-cdk-aws/HEAD/public/porygon.png
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/src/components/button.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link"
2 |
3 | const Button = () => {
4 | return (
5 |
6 |
7 |
14 |
15 |
16 | )
17 | }
18 |
19 | export default Button
20 |
--------------------------------------------------------------------------------
/lib/fetch.ts:
--------------------------------------------------------------------------------
1 | import axios from "redaxios";
2 |
3 | export async function getPokemons(n?: number) {
4 | const pokemons = n || 3000;
5 | const { data: response } = await axios(
6 | `https://pokeapi.co/api/v2/pokemon?limit=${pokemons}`
7 | );
8 | return response;
9 | }
10 |
11 | export async function getPokemonData(name: string) {
12 | const { data: response } = await axios(
13 | `https://pokeapi.co/api/v2/pokemon/${name}`
14 | );
15 | return response;
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
32 | cdk.out
33 | .vscode
--------------------------------------------------------------------------------
/bin.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "@aws-cdk/core";
2 | import { Builder } from "@sls-next/lambda-at-edge";
3 | import { NextStack } from "./stack";
4 |
5 | const builder = new Builder(".", "./build", {args: ['build']});
6 |
7 | builder
8 | .build(true)
9 | .then(() => {
10 | const app = new cdk.App();
11 | new NextStack(app, "NextJsPokeStack", {
12 | env: {
13 | region: 'us-east-1',
14 | },
15 | analyticsReporting: true,
16 | description: "Testing deploying NextJS Serverless Construct"
17 | });
18 | })
19 | .catch((e) => {
20 | console.error(e);
21 | process.exit(1);
22 | });
23 |
--------------------------------------------------------------------------------
/src/components/spacer.tsx:
--------------------------------------------------------------------------------
1 | interface Spacer {
2 | size: string
3 | axis?: 'vertical' | 'horizontal'
4 | delegated?: {}
5 | style?: {}
6 | }
7 |
8 | const Spacer = (props: Spacer) => {
9 | const width = props.axis === 'vertical' ? 1 : props.size;
10 | const height = props.axis === 'horizontal' ? 1 : props.size;
11 | return (
12 |
23 | );
24 | };
25 |
26 | export default Spacer;
27 |
--------------------------------------------------------------------------------
/stack.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "@aws-cdk/core";
2 | import { Duration } from "@aws-cdk/core";
3 | import { NextJSLambdaEdge } from "@sls-next/cdk-construct";
4 | import { Runtime } from "@aws-cdk/aws-lambda";
5 |
6 | export class NextStack extends cdk.Stack {
7 | constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) {
8 | super(scope, id, props);
9 | new NextJSLambdaEdge(this, "NextJsApp", {
10 | serverlessBuildOutDir: "./build",
11 | runtime: Runtime.NODEJS_12_X,
12 | memory: 1024,
13 | timeout: Duration.seconds(30),
14 | withLogging: true,
15 | name: {
16 | apiLambda: `${id}Api`,
17 | defaultLambda: `Fn${id}`,
18 | imageLambda: `${id}Image`,
19 | },
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/gtag.ts:
--------------------------------------------------------------------------------
1 | export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID;
2 |
3 | type GTagEvent = {
4 | action: string;
5 | category: string;
6 | label: string;
7 | value: number;
8 | };
9 |
10 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages
11 | export const pageview = (url: URL) => {
12 | if (typeof GA_TRACKING_ID === "string") {
13 | window.gtag("config", GA_TRACKING_ID, {
14 | page_path: url,
15 | });
16 | }
17 | };
18 |
19 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events
20 | export const event = ({ action, category, label, value }: GTagEvent) => {
21 | window.gtag("event", action, {
22 | event_category: category,
23 | event_label: label,
24 | value: value,
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const terryPratchett = {
2 | key: 'X-Clacks-Overhead',
3 | value: 'GNU Terry Pratchett',
4 | }
5 |
6 | module.exports = {
7 | async headers() {
8 | return [
9 | {
10 | source: '/',
11 | headers: [
12 | terryPratchett
13 | ],
14 | },
15 | {
16 | source: '/:ditto',
17 | headers: [
18 | terryPratchett
19 | ],
20 | },
21 | {
22 | source: '/_next\/([^\/]+\/?)*',
23 | headers: [
24 | terryPratchett
25 | ],
26 | },
27 | ]
28 | },
29 | env: {
30 | baseUrl: "https://aws-ssr-pokemon.ibrahimcesar.cloud",
31 | NEXT_PUBLIC_GA_ID: "G-0H4982YVLL"
32 | },
33 | images: {
34 | domains: ['raw.githubusercontent.com'],
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pokeserverless",
3 | "version": "2.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "cdk:deploy": "cdk deploy --profile demo",
10 | "deploy": "ts-node bin.ts"
11 | },
12 | "dependencies": {
13 | "next": "^12.1.0",
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "redaxios": "^0.4.1"
17 | },
18 | "devDependencies": {
19 | "@aws-cdk/aws-lambda": "^1.119.0",
20 | "@aws-cdk/core": "^1.119.0",
21 | "@sls-next/cdk-construct": "3.2.0",
22 | "@sls-next/lambda-at-edge": "3.2.0",
23 | "@types/gtag.js": "^0.0.7",
24 | "@types/node": "^16.6.1",
25 | "@types/react": "^17.0.18",
26 | "aws-cdk": "^1.119.0",
27 | "ts-node": "^10.2.0",
28 | "typescript": "^4.3.5"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ibrahim Cesar
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 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps, NextWebVitalsMetric } from 'next/app'
2 | import { useRouter } from 'next/router'
3 | import { useEffect } from 'react'
4 | import * as gtag from '@/lib/gtag'
5 |
6 | import '../styles.css'
7 |
8 | function PokeServerless({ Component, pageProps }: AppProps) {
9 | const router = useRouter()
10 | useEffect(() => {
11 | const handleRouteChange = (url: URL) => {
12 | gtag.pageview(url)
13 | }
14 | router.events.on('routeChangeComplete', handleRouteChange)
15 | return () => {
16 | router.events.off('routeChangeComplete', handleRouteChange)
17 | }
18 | }, [router.events])
19 | return
20 | }
21 |
22 | export function reportWebVitals(metric: NextWebVitalsMetric) {
23 | if (window) {
24 | window.gtag('event', metric.name, {
25 | event_category:
26 | metric.label === 'web-vital' ? 'Web Vitals' : 'Next.js custom metric',
27 | value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value), // values must be integers
28 | event_label: metric.id, // id unique to current page load
29 | non_interaction: true, // avoids affecting bounce rate.
30 | })
31 | }
32 | }
33 |
34 | export default PokeServerless
35 |
--------------------------------------------------------------------------------
/src/pages/[ditto].tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps } from 'next'
2 | import Head from "next/head"
3 | import { getPokemonData } from '@/lib/fetch'
4 | import PokemonForm from '@/components/pokemon'
5 |
6 | import type { Pokemon } from '@/types/Pokemon'
7 |
8 | interface PokemonApi {
9 | data: Pokemon
10 | }
11 |
12 | const Ditto = (props: PokemonApi) => {
13 | if (!props?.data?.name) return null;
14 |
15 | const pokeName = props.data.species.name.charAt(0).toUpperCase() + props.data.species.name.slice(1)
16 |
17 | return (
18 |
19 |
20 | {pokeName} | PokéServeless - AWS Serverless Lambda@Edge
21 |
22 |
23 |
24 | PokéServerless — Server Side Rendering
25 |
26 |
27 |
28 | )
29 |
30 | }
31 |
32 |
33 | export const getServerSideProps: GetServerSideProps = async (context) => {
34 | let data;
35 |
36 | const { ditto} = context.query;
37 |
38 | if (typeof ditto === 'string') {
39 | data = await getPokemonData(ditto)
40 | } else {
41 | data = {}
42 | }
43 |
44 | return { props: { data } }
45 | }
46 |
47 | export default Ditto
48 |
--------------------------------------------------------------------------------
/src/pages/random.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps } from 'next'
2 | import Head from "next/head"
3 | import { getPokemons, getPokemonData } from '@/lib/fetch'
4 | import PokemonForm from '@/components/pokemon'
5 |
6 | import type { Pokemon } from '@/types/Pokemon'
7 | import type { Pokedex } from '@/types/Pokedex'
8 |
9 | interface PokemonApi {
10 | data: Pokemon
11 | }
12 |
13 | const Ditto = (props: PokemonApi) => {
14 | if (!props.data.name) return null;
15 |
16 | const pokeName = props.data.species.name.charAt(0).toUpperCase() + props.data.species.name.slice(1)
17 |
18 | return (
19 |
20 |
21 | A wild {pokeName} appears! | PokéSSR - AWS Serverless Lambda@Edge
22 |
23 |
24 |
25 |
26 | )
27 | }
28 |
29 |
30 | export const getServerSideProps: GetServerSideProps = async () => {
31 | const random = await getPokemons() as Pokedex
32 |
33 | const ditto = random?.results[random.results.length * Math.random() | 0]?.name
34 |
35 | let data;
36 |
37 | if (typeof ditto === 'string') {
38 | data = await getPokemonData(ditto)
39 | } else {
40 | data = {}
41 | }
42 |
43 | return {
44 | props: {
45 | data
46 | }
47 | }
48 | }
49 |
50 | export default Ditto
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "downlevelIteration": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "inlineSourceMap": true,
8 | "lib": [
9 | "es2020",
10 | "DOM"
11 | ],
12 | "moduleResolution": "node",
13 | "noEmitOnError": true,
14 | "noFallthroughCasesInSwitch": true,
15 | "noImplicitAny": true,
16 | "noImplicitThis": true,
17 | "noImplicitReturns": true,
18 | "noUncheckedIndexedAccess": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "resolveJsonModule": true,
22 | "strict": true,
23 | "strictBindCallApply": true,
24 | "strictFunctionTypes": true,
25 | "strictNullChecks": true,
26 | "strictPropertyInitialization": true,
27 | "stripInternal": true,
28 | "target": "ES2020",
29 | "typeRoots": [
30 | "node_modules/@types"
31 | ],
32 | "useDefineForClassFields": true,
33 | "allowJs": true,
34 | "skipLibCheck": true,
35 | "noEmit": true,
36 | "module": "commonjs",
37 | "isolatedModules": true,
38 | "jsx": "preserve",
39 | "baseUrl": ".",
40 | "paths": {
41 | "@/components/*": [
42 | "./src/components/*"
43 | ],
44 | "@/lib/*": [
45 | "./lib/*"
46 | ],
47 | "@/pages/*": [
48 | "./src/pages/*"
49 | ],
50 | "@/types/*": [
51 | "./types/*"
52 | ]
53 | },
54 | },
55 | "exclude": [
56 | "node_modules"
57 | ],
58 | "include": [
59 | "src",
60 | "next-env.d.ts",
61 | "**/*.ts",
62 | "**/*.tsx",
63 | "lib"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/src/pages/ssr.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps } from 'next'
2 | import Link from "next/link"
3 | import { getPokemons } from '@/lib/fetch'
4 | import Button from "@/components/button"
5 | import Spacer from "@/components/spacer"
6 |
7 | import type { Pokedex, Result } from '@/types/Pokedex'
8 |
9 | interface PokeDexApi {
10 | data: Pokedex
11 | }
12 |
13 | const PokemonsPage = (props: PokeDexApi) => {
14 |
15 | return (
16 | <>
17 |
18 |
19 |
20 |
Pokémons
21 |
Page to test SSR deploy with Next.js.
22 |
Total of Pokémons: {props.data.count}
23 |
24 |
25 |
26 |
27 | {props.data.results.map((pokemon: Result, index: number) => (
28 | -
29 |
30 | {index + 1}
31 | {pokemon.name}
32 |
33 |
34 | ))}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | >
43 | )
44 | }
45 |
46 | export const getServerSideProps: GetServerSideProps = async () => {
47 | const data = await getPokemons()
48 | return {
49 | props: {
50 | data
51 | }
52 | }
53 | }
54 |
55 | export default PokemonsPage
56 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image"
2 | import Link from "next/link"
3 | import Button from "@/components/button"
4 | import Spacer from "@/components/spacer"
5 |
6 |
7 | const PokemonsPage = () => {
8 |
9 | return (
10 | <>
11 |
12 |
13 |
14 |
PokéServerless
15 |
16 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | >
47 | )
48 | }
49 |
50 |
51 |
52 | export default PokemonsPage
53 |
--------------------------------------------------------------------------------
/src/pages/isr.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps } from 'next'
2 | import Link from "next/link"
3 | import { getPokemons } from '@/lib/fetch'
4 | import Button from "@/components/button"
5 | import Spacer from "@/components/spacer"
6 |
7 | import type { Pokedex, Result } from '@/types/Pokedex'
8 |
9 | interface PokeDexApi {
10 | data: Pokedex
11 | }
12 |
13 | const PokemonsPage = (props: PokeDexApi) => {
14 |
15 | return (
16 | <>
17 |
18 |
19 |
20 |
Pokémons
21 |
Page to test ISR deploy with NextJS.
22 |
Total of Pokémons: {props.data.count}
23 |
24 |
25 |
26 |
27 | {props.data.results.map((pokemon: Result, index: number) => (
28 | -
29 |
30 | {index + 1}
31 | {pokemon.name}
32 |
33 |
34 | ))}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | >
43 | )
44 | }
45 |
46 | export const getServerSideProps: GetServerSideProps = async () => {
47 | const data = await getPokemons()
48 | return {
49 | props: {
50 | data
51 | }
52 | }
53 | }
54 |
55 | export default PokemonsPage
56 |
--------------------------------------------------------------------------------
/src/pages/pokemons/[porygon].tsx:
--------------------------------------------------------------------------------
1 | // Example of ISG
2 | import { GetStaticPaths, GetStaticProps } from 'next';
3 | import Head from "next/head"
4 | import { useRouter } from 'next/router';
5 | import { getPokemons, getPokemonData } from '@/lib/fetch'
6 | import PokemonForm from '@/components/pokemon'
7 |
8 | import type { Pokemon } from '@/types/Pokemon'
9 | import type { Pokedex } from '@/types/Pokedex'
10 |
11 | interface PokemonApi {
12 | data: Pokemon,
13 | date: string
14 | }
15 |
16 | const Porygon = (props: PokemonApi) => {
17 | if (!props?.data?.name) return null;
18 | const router = useRouter();
19 |
20 | if (router.isFallback) {
21 | return Loading......I had to fetch incrementally!!
;
22 | }
23 |
24 | const pokeName = props.data.species.name.charAt(0).toUpperCase() + props.data.species.name.slice(1)
25 |
26 | return (
27 | <>
28 |
29 |
30 | {pokeName} | PokéServerless - AWS Serverless Lambda@Edge
31 |
32 |
33 |
34 | PokéServerless — Incremental Static Regeneration
35 |
36 |
37 |
38 | {`Generated at ${new Date(props.date).toLocaleString()}`}
39 | >
40 | )
41 | }
42 |
43 | export const getStaticProps: GetStaticProps = async (context) => {
44 | let data
45 |
46 | if (context.params) {
47 |
48 | data = await getPokemonData(context.params.porygon as string)
49 | } else {
50 | data = {}
51 | }
52 |
53 | return {
54 | props: {
55 | data,
56 | date: new Date().toISOString(),
57 | },
58 | revalidate: 60 * 5
59 | }
60 | };
61 |
62 | export const getStaticPaths: GetStaticPaths<{ porygon: string }> = async () => {
63 |
64 | const pokemons = await getPokemons(25) as Pokedex
65 |
66 | const paths = pokemons.results.map((pokemon) => {
67 | return { params: { porygon: pokemon.name.toString() } };
68 | });
69 |
70 | return {
71 | fallback: true,
72 | paths,
73 | };
74 | };
75 |
76 | export default Porygon
77 |
--------------------------------------------------------------------------------
/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/document'
2 | import Link from 'next/link'
3 | import { GA_TRACKING_ID } from '@/lib/gtag'
4 |
5 | class PokeDoc extends Document {
6 | static async getInitialProps(ctx: DocumentContext) {
7 | const initialProps = await Document.getInitialProps(ctx)
8 |
9 | return initialProps
10 | }
11 |
12 | render() {
13 | return (
14 |
15 |
16 | PokéServerless - AWS Serverless Lambda@Edge
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {/* Global Site Tag (gtag.js) - Google Analytics */}
28 |
32 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 | }
53 | }
54 |
55 | export default PokeDoc
56 |
--------------------------------------------------------------------------------
/src/components/pokemon.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import Link from 'next/link'
3 | import Button from '@/components/button'
4 | import Spacer from '@/components/spacer'
5 | import { Pokemon, Type } from '@/types/Pokemon'
6 |
7 | interface PokemonInfo {
8 | poke: {
9 | data: Pokemon
10 | }
11 | }
12 |
13 | const PokemonForm = (props: PokemonInfo) => {
14 |
15 |
16 | const pokeImage = props.poke?.data?.sprites?.other?.["official-artwork"]?.front_default ?? props.poke?.data?.sprites?.front_default
17 |
18 | const number = props?.poke?.data?.order;
19 | const isPositive = number >= 1;
20 |
21 | const pokeNumber = isPositive ? number : "Max version"
22 |
23 | return (
24 | <>
25 |
26 |
32 | {props?.poke?.data?.name}
33 | Number: {pokeNumber}
34 | Type:
35 |
36 | {props?.poke?.data?.types?.map((info: Type, index: number) => (
37 | - {info.type.name}
38 | ))}
39 |
40 |
41 |
42 |
43 |
71 | >
72 | )
73 | }
74 |
75 | export default PokemonForm
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 | # AWS CDK serverless deploy for Next.js with SSR and ISR
6 |
7 |
8 |
9 | > Next.js webapp using Server Side Rendering (SSR) and Incremental Static
10 | > Regeneration (ISR) deployed with Serverless Nextjs CDK construct on AWS using
11 | > CloudFront and Lambda@Edge
12 |
13 | ## How to deploy?
14 |
15 | 🌟
16 | [Read the post for more context](https://ibrahimcesar.cloud/blog/nextjs-typescript-serverless-deploy-with-ssr-and-isr-with-aws-cdk/)
17 |
18 | > **[Live Demo](https://d3k4okkgstczau.cloudfront.net)**
19 |
20 | ## PokéServerless is _just_ a Proof of Concept!
21 |
22 | Minimal NextJS with Server Side Rendering (SSR) and Incremental Static
23 | Generation (ISR) – **Gotta Fetch'Em All!** – and deployed with
24 | [Serverless Nextjs CDK construct](https://serverless-nextjs.com/docs/cdkconstruct/)
25 | on AWS using CloudFront and Lambda@Edge.
26 |
27 | ### Special Thanks
28 |
29 | - [NextJS](https://nextjs.org/), a great framework! And the freedom to build and
30 | deploy wherever we need or want! Great fan
31 | - [Serverless Nextjs CDK construct](https://serverless-nextjs.com/docs/cdkconstruct/),
32 | in special [Henry Kirkness](https://github.com/kirkness) and
33 | - [Daniel Phang](https://github.com/dphang) for all work at
34 | [this feature](https://github.com/serverless-nextjs/serverless-next.js/pull/878)
35 | and [ISR](https://github.com/serverless-nextjs/serverless-next.js/pull/1028)
36 | - [Adam Elmore](https://twitter.com/aeduhm) for put a
37 | [bounty on the ISR issue](https://twitter.com/aeduhm/status/1382093398077796357?s=20)!
38 |
39 | ### MIT License
40 |
41 | © Copyright 2021 [Ibrahim Cesar](https://ibrahimcesar.cloud)
42 |
43 | Permission is hereby granted, free of charge, to any person obtaining a copy of
44 | this software and associated documentation files (the "Software"), to deal in
45 | the Software without restriction, including without limitation the rights to
46 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
47 | the Software, and to permit persons to whom the Software is furnished to do so,
48 | subject to the following conditions:
49 |
50 | The above copyright notice and this permission notice shall be included in all
51 | copies or substantial portions of the Software.
52 |
53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
54 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
55 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
56 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
57 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
58 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "IBM Plex Sans", sans-serif;
3 | }
4 |
5 | h1 {
6 | text-transform: capitalize;
7 | }
8 |
9 | p,
10 | ul {
11 | margin-bottom: 0;
12 | margin-top: 0;
13 | }
14 |
15 | article {
16 | font-size: 1.5rem;
17 | }
18 |
19 | footer {
20 | margin-bottom: 20px;
21 | text-align: center;
22 | }
23 |
24 | header h1 {
25 | text-align: center;
26 | color: #002147;
27 |
28 | }
29 |
30 | .wrapper {
31 | display: grid;
32 | grid-template-columns: 1fr 1fr 1fr;
33 | }
34 |
35 | .container {
36 | display: grid;
37 | grid-template-columns: 1fr;
38 | }
39 |
40 | .ditto {
41 | justify-self: center;
42 | padding-top: 5%;
43 | }
44 |
45 | .poke-options {
46 | display: grid;
47 | grid-template-columns: 1fr 1fr;
48 | gap: 3rem;
49 | text-align: center;
50 | }
51 |
52 | footer a,
53 | .poke-options a {
54 | color: #002147;
55 | }
56 |
57 | .poke-footer {
58 | display: grid;
59 | grid-template-columns: 1fr 2fr 1fr;
60 | margin-top: 6rem;
61 | }
62 |
63 | .poke-list {
64 | list-style: none;
65 | }
66 |
67 | .poke-names {
68 | color: #002147;
69 | font-size: 1.8rem;
70 | font-weight: 700;
71 | text-transform: capitalize;
72 | text-decoration: none;
73 | }
74 |
75 | .poke-names:visited {
76 | color: #00408b;
77 | }
78 |
79 | .poke-names:hover {
80 | color: #001a39;
81 | }
82 |
83 | .poke-name {
84 | font-size: 2.5rem;
85 | }
86 |
87 | .poke-content {
88 | align-self: center;
89 | text-align: center;
90 | }
91 |
92 | .poke-center {
93 | margin-bottom: 1%;
94 | text-align: center;
95 | }
96 |
97 | .poke-home-link {
98 | display: grid;
99 | font-size: 1rem;
100 | font-weight: 600;
101 | grid-template-columns: 1fr;
102 | margin-bottom: 30px;
103 | margin-top: 30px;
104 | text-align: center;
105 | text-decoration: none;
106 | }
107 |
108 | .poke-home {
109 | font-size: 5rem;
110 | }
111 |
112 | .poke-home:hover {
113 | font-size: 6rem;
114 | }
115 |
116 | /*
117 | Building a Magical 3D Button
118 | From: https://www.joshwcomeau.com/animation/3d-button/
119 | */
120 |
121 | .pushable {
122 | position: relative;
123 | border: none;
124 | background: transparent;
125 | padding: 0;
126 | cursor: pointer;
127 | outline-offset: 4px;
128 | transition: filter 250ms;
129 | }
130 |
131 | .shadow {
132 | position: absolute;
133 | top: 0;
134 | left: 0;
135 | width: 100%;
136 | height: 100%;
137 | border-radius: 12px;
138 | background: hsl(0deg 0% 0% / 0.25);
139 | will-change: transform;
140 | transform: translateY(2px);
141 | transition: transform 600ms cubic-bezier(0.3, 0.7, 0.4, 1);
142 | }
143 |
144 | .edge {
145 | position: absolute;
146 | top: 0;
147 | left: 0;
148 | width: 100%;
149 | height: 100%;
150 | border-radius: 12px;
151 | background: linear-gradient(to left,
152 | hsl(340deg 100% 16%) 0%,
153 | hsl(340deg 100% 32%) 8%,
154 | hsl(340deg 100% 32%) 92%,
155 | hsl(340deg 100% 16%) 100%);
156 | }
157 |
158 | .front {
159 | display: block;
160 | position: relative;
161 | padding: 12px 42px;
162 | border-radius: 12px;
163 | font-size: 1.25rem;
164 | color: white;
165 | background: hsl(345deg 100% 47%);
166 | will-change: transform;
167 | transform: translateY(-4px);
168 | transition: transform 600ms cubic-bezier(0.3, 0.7, 0.4, 1);
169 | }
170 |
171 | .pushable:hover {
172 | filter: brightness(110%);
173 | }
174 |
175 | .pushable:hover .front {
176 | transform: translateY(-6px);
177 | transition: transform 250ms cubic-bezier(0.3, 0.7, 0.4, 1.5);
178 | }
179 |
180 | .pushable:active .front {
181 | transform: translateY(-2px);
182 | transition: transform 34ms;
183 | }
184 |
185 | .pushable:hover .shadow {
186 | transform: translateY(4px);
187 | transition: transform 250ms cubic-bezier(0.3, 0.7, 0.4, 1.5);
188 | }
189 |
190 | .pushable:active .shadow {
191 | transform: translateY(1px);
192 | transition: transform 34ms;
193 | }
194 |
195 | .pushable:focus:not(:focus-visible) {
196 | outline: none;
197 | }
--------------------------------------------------------------------------------
/types/Pokedex.ts:
--------------------------------------------------------------------------------
1 | export interface Pokedex {
2 | count: number;
3 | next: string;
4 | previous: null;
5 | results: Result[];
6 | }
7 |
8 | export interface Result {
9 | name: string;
10 | url: string;
11 | }
12 |
13 | // Converts JSON strings to/from your types
14 | // and asserts the results of JSON.parse at runtime
15 | export class Convert {
16 | public static toPokedex(json: string): Pokedex {
17 | return cast(JSON.parse(json), r("Pokedex"));
18 | }
19 |
20 | public static pokedexToJson(value: Pokedex): string {
21 | return JSON.stringify(uncast(value, r("Pokedex")), null, 2);
22 | }
23 | }
24 |
25 | function invalidValue(typ: any, val: any, key: any = ""): never {
26 | if (key) {
27 | throw Error(
28 | `Invalid value for key "${key}". Expected type ${JSON.stringify(
29 | typ
30 | )} but got ${JSON.stringify(val)}`
31 | );
32 | }
33 | throw Error(
34 | `Invalid value ${JSON.stringify(val)} for type ${JSON.stringify(typ)}`
35 | );
36 | }
37 |
38 | function jsonToJSProps(typ: any): any {
39 | if (typ.jsonToJS === undefined) {
40 | const map: any = {};
41 | typ.props.forEach((p: any) => (map[p.json] = { key: p.js, typ: p.typ }));
42 | typ.jsonToJS = map;
43 | }
44 | return typ.jsonToJS;
45 | }
46 |
47 | function jsToJSONProps(typ: any): any {
48 | if (typ.jsToJSON === undefined) {
49 | const map: any = {};
50 | typ.props.forEach((p: any) => (map[p.js] = { key: p.json, typ: p.typ }));
51 | typ.jsToJSON = map;
52 | }
53 | return typ.jsToJSON;
54 | }
55 |
56 | function transform(val: any, typ: any, getProps: any, key: any = ""): any {
57 | function transformPrimitive(typ: string, val: any): any {
58 | if (typeof typ === typeof val) return val;
59 | return invalidValue(typ, val, key);
60 | }
61 |
62 | function transformUnion(typs: any[], val: any): any {
63 | // val must validate against one typ in typs
64 | const l = typs.length;
65 | for (let i = 0; i < l; i++) {
66 | const typ = typs[i];
67 | try {
68 | return transform(val, typ, getProps);
69 | } catch (_) {}
70 | }
71 | return invalidValue(typs, val);
72 | }
73 |
74 | function transformEnum(cases: string[], val: any): any {
75 | if (cases.indexOf(val) !== -1) return val;
76 | return invalidValue(cases, val);
77 | }
78 |
79 | function transformArray(typ: any, val: any): any {
80 | // val must be an array with no invalid elements
81 | if (!Array.isArray(val)) return invalidValue("array", val);
82 | return val.map(el => transform(el, typ, getProps));
83 | }
84 |
85 | function transformDate(val: any): any {
86 | if (val === null) {
87 | return null;
88 | }
89 | const d = new Date(val);
90 | if (isNaN(d.valueOf())) {
91 | return invalidValue("Date", val);
92 | }
93 | return d;
94 | }
95 |
96 | function transformObject(
97 | props: { [k: string]: any },
98 | additional: any,
99 | val: any
100 | ): any {
101 | if (val === null || typeof val !== "object" || Array.isArray(val)) {
102 | return invalidValue("object", val);
103 | }
104 | const result: any = {};
105 | Object.getOwnPropertyNames(props).forEach(key => {
106 | const prop = props[key];
107 | const v = Object.prototype.hasOwnProperty.call(val, key)
108 | ? val[key]
109 | : undefined;
110 | result[prop.key] = transform(v, prop.typ, getProps, prop.key);
111 | });
112 | Object.getOwnPropertyNames(val).forEach(key => {
113 | if (!Object.prototype.hasOwnProperty.call(props, key)) {
114 | result[key] = transform(val[key], additional, getProps, key);
115 | }
116 | });
117 | return result;
118 | }
119 |
120 | if (typ === "any") return val;
121 | if (typ === null) {
122 | if (val === null) return val;
123 | return invalidValue(typ, val);
124 | }
125 | if (typ === false) return invalidValue(typ, val);
126 | while (typeof typ === "object" && typ.ref !== undefined) {
127 | typ = typeMap[typ.ref];
128 | }
129 | if (Array.isArray(typ)) return transformEnum(typ, val);
130 | if (typeof typ === "object") {
131 | return typ.hasOwnProperty("unionMembers")
132 | ? transformUnion(typ.unionMembers, val)
133 | : typ.hasOwnProperty("arrayItems")
134 | ? transformArray(typ.arrayItems, val)
135 | : typ.hasOwnProperty("props")
136 | ? transformObject(getProps(typ), typ.additional, val)
137 | : invalidValue(typ, val);
138 | }
139 | // Numbers can be parsed by Date but shouldn't be.
140 | if (typ === Date && typeof val !== "number") return transformDate(val);
141 | return transformPrimitive(typ, val);
142 | }
143 |
144 | function cast(val: any, typ: any): T {
145 | return transform(val, typ, jsonToJSProps);
146 | }
147 |
148 | function uncast(val: T, typ: any): any {
149 | return transform(val, typ, jsToJSONProps);
150 | }
151 |
152 | function a(typ: any) {
153 | return { arrayItems: typ };
154 | }
155 |
156 | function o(props: any[], additional: any) {
157 | return { props, additional };
158 | }
159 |
160 | function r(name: string) {
161 | return { ref: name };
162 | }
163 |
164 | const typeMap: any = {
165 | Pokedex: o(
166 | [
167 | { json: "count", js: "count", typ: 0 },
168 | { json: "next", js: "next", typ: "" },
169 | { json: "previous", js: "previous", typ: null },
170 | { json: "results", js: "results", typ: a(r("Result")) },
171 | ],
172 | false
173 | ),
174 | Result: o(
175 | [
176 | { json: "name", js: "name", typ: "" },
177 | { json: "url", js: "url", typ: "" },
178 | ],
179 | false
180 | ),
181 | };
182 |
--------------------------------------------------------------------------------
/types/Pokemon.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface Pokemon {
3 | abilities: Ability[];
4 | base_experience: number;
5 | forms: Species[];
6 | game_indices: GameIndex[];
7 | height: number;
8 | held_items: HeldItem[];
9 | id: number;
10 | is_default: boolean;
11 | location_area_encounters: string;
12 | moves: Move[];
13 | name: string;
14 | order: number;
15 | past_types: any[];
16 | species: Species;
17 | sprites: Sprites;
18 | stats: Stat[];
19 | types: Type[];
20 | weight: number;
21 | }
22 |
23 | export interface Ability {
24 | ability: Species;
25 | is_hidden: boolean;
26 | slot: number;
27 | }
28 |
29 | export interface Species {
30 | name: string;
31 | url: string;
32 | }
33 |
34 | export interface GameIndex {
35 | game_index: number;
36 | version: Species;
37 | }
38 |
39 | export interface HeldItem {
40 | item: Species;
41 | version_details: VersionDetail[];
42 | }
43 |
44 | export interface VersionDetail {
45 | rarity: number;
46 | version: Species;
47 | }
48 |
49 | export interface Move {
50 | move: Species;
51 | version_group_details: VersionGroupDetail[];
52 | }
53 |
54 | export interface VersionGroupDetail {
55 | level_learned_at: number;
56 | move_learn_method: Species;
57 | version_group: Species;
58 | }
59 |
60 | export interface GenerationV {
61 | "black-white": Sprites;
62 | }
63 |
64 | export interface GenerationIv {
65 | "diamond-pearl": Sprites;
66 | "heartgold-soulsilver": Sprites;
67 | "platinum": Sprites;
68 | }
69 |
70 | export interface Versions {
71 | "generation-i": GenerationI;
72 | "generation-ii": GenerationIi;
73 | "generation-iii": GenerationIii;
74 | "generation-iv": GenerationIv;
75 | "generation-v": GenerationV;
76 | "generation-vi": { [key: string]: GenerationVi };
77 | "generation-vii": GenerationVii;
78 | "generation-viii": GenerationViii;
79 | }
80 |
81 | export interface Sprites {
82 | back_default: string;
83 | back_female: string;
84 | back_shiny: string;
85 | back_shiny_female: string;
86 | front_default: string;
87 | front_female: string;
88 | front_shiny: string;
89 | front_shiny_female: string;
90 | other?: Other;
91 | versions?: Versions;
92 | animated?: Sprites;
93 | }
94 |
95 | export interface GenerationI {
96 | "red-blue": RedBlue;
97 | "yellow": RedBlue;
98 | }
99 |
100 | export interface RedBlue {
101 | back_default: string;
102 | back_gray: string;
103 | front_default: string;
104 | front_gray: string;
105 | }
106 |
107 | export interface GenerationIi {
108 | crystal: Crystal;
109 | gold: Crystal;
110 | silver: Crystal;
111 | }
112 |
113 | export interface Crystal {
114 | back_default: string;
115 | back_shiny: string;
116 | front_default: string;
117 | front_shiny: string;
118 | }
119 |
120 | export interface GenerationIii {
121 | "emerald": Emerald;
122 | "firered-leafgreen": Crystal;
123 | "ruby-sapphire": Crystal;
124 | }
125 |
126 | export interface Emerald {
127 | front_default: string;
128 | front_shiny: string;
129 | }
130 |
131 | export interface GenerationVi {
132 | front_default: string;
133 | front_female: string;
134 | front_shiny: string;
135 | front_shiny_female: string;
136 | }
137 |
138 | export interface GenerationVii {
139 | "icons": DreamWorld;
140 | "ultra-sun-ultra-moon": GenerationVi;
141 | }
142 |
143 | export interface DreamWorld {
144 | front_default: string;
145 | front_female: null | string;
146 | }
147 |
148 | export interface GenerationViii {
149 | icons: DreamWorld;
150 | }
151 |
152 | export interface Other {
153 | "dream_world": DreamWorld;
154 | "official-artwork": OfficialArtwork;
155 | }
156 |
157 | export interface OfficialArtwork {
158 | front_default: string;
159 | }
160 |
161 | export interface Stat {
162 | base_stat: number;
163 | effort: number;
164 | stat: Species;
165 | }
166 |
167 | export interface Type {
168 | slot: number;
169 | type: Species;
170 | }
171 |
172 | // Converts JSON strings to/from your types
173 | // and asserts the results of JSON.parse at runtime
174 | export class Convert {
175 | public static toPokemon(json: string): Pokemon {
176 | return cast(JSON.parse(json), r("Pokemon"));
177 | }
178 |
179 | public static pokemonToJson(value: Pokemon): string {
180 | return JSON.stringify(uncast(value, r("Pokemon")), null, 2);
181 | }
182 | }
183 |
184 | function invalidValue(typ: any, val: any, key: any = ""): never {
185 | if (key) {
186 | throw Error(
187 | `Invalid value for key "${key}". Expected type ${JSON.stringify(
188 | typ
189 | )} but got ${JSON.stringify(val)}`
190 | );
191 | }
192 | throw Error(
193 | `Invalid value ${JSON.stringify(val)} for type ${JSON.stringify(typ)}`
194 | );
195 | }
196 |
197 | function jsonToJSProps(typ: any): any {
198 | if (typ.jsonToJS === undefined) {
199 | const map: any = {};
200 | typ.props.forEach((p: any) => (map[p.json] = { key: p.js, typ: p.typ }));
201 | typ.jsonToJS = map;
202 | }
203 | return typ.jsonToJS;
204 | }
205 |
206 | function jsToJSONProps(typ: any): any {
207 | if (typ.jsToJSON === undefined) {
208 | const map: any = {};
209 | typ.props.forEach((p: any) => (map[p.js] = { key: p.json, typ: p.typ }));
210 | typ.jsToJSON = map;
211 | }
212 | return typ.jsToJSON;
213 | }
214 |
215 | function transform(val: any, typ: any, getProps: any, key: any = ""): any {
216 | function transformPrimitive(typ: string, val: any): any {
217 | if (typeof typ === typeof val) return val;
218 | return invalidValue(typ, val, key);
219 | }
220 |
221 | function transformUnion(typs: any[], val: any): any {
222 | // val must validate against one typ in typs
223 | const l = typs.length;
224 | for (let i = 0; i < l; i++) {
225 | const typ = typs[i];
226 | try {
227 | return transform(val, typ, getProps);
228 | } catch (_) {}
229 | }
230 | return invalidValue(typs, val);
231 | }
232 |
233 | function transformEnum(cases: string[], val: any): any {
234 | if (cases.indexOf(val) !== -1) return val;
235 | return invalidValue(cases, val);
236 | }
237 |
238 | function transformArray(typ: any, val: any): any {
239 | // val must be an array with no invalid elements
240 | if (!Array.isArray(val)) return invalidValue("array", val);
241 | return val.map(el => transform(el, typ, getProps));
242 | }
243 |
244 | function transformDate(val: any): any {
245 | if (val === null) {
246 | return null;
247 | }
248 | const d = new Date(val);
249 | if (isNaN(d.valueOf())) {
250 | return invalidValue("Date", val);
251 | }
252 | return d;
253 | }
254 |
255 | function transformObject(
256 | props: { [k: string]: any },
257 | additional: any,
258 | val: any
259 | ): any {
260 | if (val === null || typeof val !== "object" || Array.isArray(val)) {
261 | return invalidValue("object", val);
262 | }
263 | const result: any = {};
264 | Object.getOwnPropertyNames(props).forEach(key => {
265 | const prop = props[key];
266 | const v = Object.prototype.hasOwnProperty.call(val, key)
267 | ? val[key]
268 | : undefined;
269 | result[prop.key] = transform(v, prop.typ, getProps, prop.key);
270 | });
271 | Object.getOwnPropertyNames(val).forEach(key => {
272 | if (!Object.prototype.hasOwnProperty.call(props, key)) {
273 | result[key] = transform(val[key], additional, getProps, key);
274 | }
275 | });
276 | return result;
277 | }
278 |
279 | if (typ === "any") return val;
280 | if (typ === null) {
281 | if (val === null) return val;
282 | return invalidValue(typ, val);
283 | }
284 | if (typ === false) return invalidValue(typ, val);
285 | while (typeof typ === "object" && typ.ref !== undefined) {
286 | typ = typeMap[typ.ref];
287 | }
288 | if (Array.isArray(typ)) return transformEnum(typ, val);
289 | if (typeof typ === "object") {
290 | return typ.hasOwnProperty("unionMembers")
291 | ? transformUnion(typ.unionMembers, val)
292 | : typ.hasOwnProperty("arrayItems")
293 | ? transformArray(typ.arrayItems, val)
294 | : typ.hasOwnProperty("props")
295 | ? transformObject(getProps(typ), typ.additional, val)
296 | : invalidValue(typ, val);
297 | }
298 | // Numbers can be parsed by Date but shouldn't be.
299 | if (typ === Date && typeof val !== "number") return transformDate(val);
300 | return transformPrimitive(typ, val);
301 | }
302 |
303 | function cast(val: any, typ: any): T {
304 | return transform(val, typ, jsonToJSProps);
305 | }
306 |
307 | function uncast(val: T, typ: any): any {
308 | return transform(val, typ, jsToJSONProps);
309 | }
310 |
311 | function a(typ: any) {
312 | return { arrayItems: typ };
313 | }
314 |
315 | function u(...typs: any[]) {
316 | return { unionMembers: typs };
317 | }
318 |
319 | function o(props: any[], additional: any) {
320 | return { props, additional };
321 | }
322 |
323 | function m(additional: any) {
324 | return { props: [], additional };
325 | }
326 |
327 | function r(name: string) {
328 | return { ref: name };
329 | }
330 |
331 | const typeMap: any = {
332 | Pokemon: o(
333 | [
334 | { json: "abilities", js: "abilities", typ: a(r("Ability")) },
335 | { json: "base_experience", js: "base_experience", typ: 0 },
336 | { json: "forms", js: "forms", typ: a(r("Species")) },
337 | { json: "game_indices", js: "game_indices", typ: a(r("GameIndex")) },
338 | { json: "height", js: "height", typ: 0 },
339 | { json: "held_items", js: "held_items", typ: a(r("HeldItem")) },
340 | { json: "id", js: "id", typ: 0 },
341 | { json: "is_default", js: "is_default", typ: true },
342 | {
343 | json: "location_area_encounters",
344 | js: "location_area_encounters",
345 | typ: "",
346 | },
347 | { json: "moves", js: "moves", typ: a(r("Move")) },
348 | { json: "name", js: "name", typ: "" },
349 | { json: "order", js: "order", typ: 0 },
350 | { json: "past_types", js: "past_types", typ: a("any") },
351 | { json: "species", js: "species", typ: r("Species") },
352 | { json: "sprites", js: "sprites", typ: r("Sprites") },
353 | { json: "stats", js: "stats", typ: a(r("Stat")) },
354 | { json: "types", js: "types", typ: a(r("Type")) },
355 | { json: "weight", js: "weight", typ: 0 },
356 | ],
357 | false
358 | ),
359 | Ability: o(
360 | [
361 | { json: "ability", js: "ability", typ: r("Species") },
362 | { json: "is_hidden", js: "is_hidden", typ: true },
363 | { json: "slot", js: "slot", typ: 0 },
364 | ],
365 | false
366 | ),
367 | Species: o(
368 | [
369 | { json: "name", js: "name", typ: "" },
370 | { json: "url", js: "url", typ: "" },
371 | ],
372 | false
373 | ),
374 | GameIndex: o(
375 | [
376 | { json: "game_index", js: "game_index", typ: 0 },
377 | { json: "version", js: "version", typ: r("Species") },
378 | ],
379 | false
380 | ),
381 | HeldItem: o(
382 | [
383 | { json: "item", js: "item", typ: r("Species") },
384 | {
385 | json: "version_details",
386 | js: "version_details",
387 | typ: a(r("VersionDetail")),
388 | },
389 | ],
390 | false
391 | ),
392 | VersionDetail: o(
393 | [
394 | { json: "rarity", js: "rarity", typ: 0 },
395 | { json: "version", js: "version", typ: r("Species") },
396 | ],
397 | false
398 | ),
399 | Move: o(
400 | [
401 | { json: "move", js: "move", typ: r("Species") },
402 | {
403 | json: "version_group_details",
404 | js: "version_group_details",
405 | typ: a(r("VersionGroupDetail")),
406 | },
407 | ],
408 | false
409 | ),
410 | VersionGroupDetail: o(
411 | [
412 | { json: "level_learned_at", js: "level_learned_at", typ: 0 },
413 | { json: "move_learn_method", js: "move_learn_method", typ: r("Species") },
414 | { json: "version_group", js: "version_group", typ: r("Species") },
415 | ],
416 | false
417 | ),
418 | GenerationV: o(
419 | [{ json: "black-white", js: "black-white", typ: r("Sprites") }],
420 | false
421 | ),
422 | GenerationIv: o(
423 | [
424 | { json: "diamond-pearl", js: "diamond-pearl", typ: r("Sprites") },
425 | {
426 | json: "heartgold-soulsilver",
427 | js: "heartgold-soulsilver",
428 | typ: r("Sprites"),
429 | },
430 | { json: "platinum", js: "platinum", typ: r("Sprites") },
431 | ],
432 | false
433 | ),
434 | Versions: o(
435 | [
436 | { json: "generation-i", js: "generation-i", typ: r("GenerationI") },
437 | { json: "generation-ii", js: "generation-ii", typ: r("GenerationIi") },
438 | { json: "generation-iii", js: "generation-iii", typ: r("GenerationIii") },
439 | { json: "generation-iv", js: "generation-iv", typ: r("GenerationIv") },
440 | { json: "generation-v", js: "generation-v", typ: r("GenerationV") },
441 | { json: "generation-vi", js: "generation-vi", typ: m(r("GenerationVi")) },
442 | { json: "generation-vii", js: "generation-vii", typ: r("GenerationVii") },
443 | {
444 | json: "generation-viii",
445 | js: "generation-viii",
446 | typ: r("GenerationViii"),
447 | },
448 | ],
449 | false
450 | ),
451 | Sprites: o(
452 | [
453 | { json: "back_default", js: "back_default", typ: "" },
454 | { json: "back_female", js: "back_female", typ: "" },
455 | { json: "back_shiny", js: "back_shiny", typ: "" },
456 | { json: "back_shiny_female", js: "back_shiny_female", typ: "" },
457 | { json: "front_default", js: "front_default", typ: "" },
458 | { json: "front_female", js: "front_female", typ: "" },
459 | { json: "front_shiny", js: "front_shiny", typ: "" },
460 | { json: "front_shiny_female", js: "front_shiny_female", typ: "" },
461 | { json: "other", js: "other", typ: u(undefined, r("Other")) },
462 | { json: "versions", js: "versions", typ: u(undefined, r("Versions")) },
463 | { json: "animated", js: "animated", typ: u(undefined, r("Sprites")) },
464 | ],
465 | false
466 | ),
467 | GenerationI: o(
468 | [
469 | { json: "red-blue", js: "red-blue", typ: r("RedBlue") },
470 | { json: "yellow", js: "yellow", typ: r("RedBlue") },
471 | ],
472 | false
473 | ),
474 | RedBlue: o(
475 | [
476 | { json: "back_default", js: "back_default", typ: "" },
477 | { json: "back_gray", js: "back_gray", typ: "" },
478 | { json: "front_default", js: "front_default", typ: "" },
479 | { json: "front_gray", js: "front_gray", typ: "" },
480 | ],
481 | false
482 | ),
483 | GenerationIi: o(
484 | [
485 | { json: "crystal", js: "crystal", typ: r("Crystal") },
486 | { json: "gold", js: "gold", typ: r("Crystal") },
487 | { json: "silver", js: "silver", typ: r("Crystal") },
488 | ],
489 | false
490 | ),
491 | Crystal: o(
492 | [
493 | { json: "back_default", js: "back_default", typ: "" },
494 | { json: "back_shiny", js: "back_shiny", typ: "" },
495 | { json: "front_default", js: "front_default", typ: "" },
496 | { json: "front_shiny", js: "front_shiny", typ: "" },
497 | ],
498 | false
499 | ),
500 | GenerationIii: o(
501 | [
502 | { json: "emerald", js: "emerald", typ: r("Emerald") },
503 | { json: "firered-leafgreen", js: "firered-leafgreen", typ: r("Crystal") },
504 | { json: "ruby-sapphire", js: "ruby-sapphire", typ: r("Crystal") },
505 | ],
506 | false
507 | ),
508 | Emerald: o(
509 | [
510 | { json: "front_default", js: "front_default", typ: "" },
511 | { json: "front_shiny", js: "front_shiny", typ: "" },
512 | ],
513 | false
514 | ),
515 | GenerationVi: o(
516 | [
517 | { json: "front_default", js: "front_default", typ: "" },
518 | { json: "front_female", js: "front_female", typ: "" },
519 | { json: "front_shiny", js: "front_shiny", typ: "" },
520 | { json: "front_shiny_female", js: "front_shiny_female", typ: "" },
521 | ],
522 | false
523 | ),
524 | GenerationVii: o(
525 | [
526 | { json: "icons", js: "icons", typ: r("DreamWorld") },
527 | {
528 | json: "ultra-sun-ultra-moon",
529 | js: "ultra-sun-ultra-moon",
530 | typ: r("GenerationVi"),
531 | },
532 | ],
533 | false
534 | ),
535 | DreamWorld: o(
536 | [
537 | { json: "front_default", js: "front_default", typ: "" },
538 | { json: "front_female", js: "front_female", typ: u(null, "") },
539 | ],
540 | false
541 | ),
542 | GenerationViii: o(
543 | [{ json: "icons", js: "icons", typ: r("DreamWorld") }],
544 | false
545 | ),
546 | Other: o(
547 | [
548 | { json: "dream_world", js: "dream_world", typ: r("DreamWorld") },
549 | {
550 | json: "official-artwork",
551 | js: "official-artwork",
552 | typ: r("OfficialArtwork"),
553 | },
554 | ],
555 | false
556 | ),
557 | OfficialArtwork: o(
558 | [{ json: "front_default", js: "front_default", typ: "" }],
559 | false
560 | ),
561 | Stat: o(
562 | [
563 | { json: "base_stat", js: "base_stat", typ: 0 },
564 | { json: "effort", js: "effort", typ: 0 },
565 | { json: "stat", js: "stat", typ: r("Species") },
566 | ],
567 | false
568 | ),
569 | Type: o(
570 | [
571 | { json: "slot", js: "slot", typ: 0 },
572 | { json: "type", js: "type", typ: r("Species") },
573 | ],
574 | false
575 | ),
576 | };
577 |
--------------------------------------------------------------------------------