Skip to content

Commit

Permalink
infer types, check and share to client for end-to-end type safety
Browse files Browse the repository at this point in the history
  • Loading branch information
SantiagoJavierRubio committed Nov 29, 2023
1 parent 67742f0 commit 07e0162
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 71 deletions.
3 changes: 2 additions & 1 deletion apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"postcss": "^8.4.31",
"tailwindcss": "^3.3.5",
"typescript": "^5.2.2",
"vite": "^5.0.0"
"vite": "^5.0.0",
"types": "*"
}
}
2 changes: 2 additions & 0 deletions apps/client/src/api/getCommits.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import axios from 'axios'
import type {} from 'types'

export default function getCommits() {
return axios.get('http://localhost:3000/commits').then(res => res.data)
}
7 changes: 3 additions & 4 deletions apps/client/src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import getCommits from '@/api/getCommits'
import { useQuery } from '@tanstack/react-query'
import { CommitList } from 'types'

export default function Home() {
const { isLoading, error, data, isFetching } = useQuery({
const commitQuery = useQuery<CommitList>({
queryKey: ['commits'],
queryFn: getCommits
})

// eslint-disable-next-line no-console
console.log(isLoading, error, data, isFetching)
return (
<div>Home</div>
<div>Home {commitQuery.isSuccess ? commitQuery.data[0].url : ''}</div>
)
}
1 change: 1 addition & 0 deletions apps/server/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default () => ({
NODE_ENV: process.env.NODE_ENV || 'development',
githubToken: process.env.GITHUB_TOKEN,
repo: {
owner: process.env.REPO_OWNER ?? 'SantiagoJavierRubio',
Expand Down
12 changes: 9 additions & 3 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@anatine/zod-openapi": "^2.2.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.1.16",
"octokit": "^3.1.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
"zod": "^3.22.4"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand Down Expand Up @@ -58,8 +59,13 @@
"json",
"ts"
],
"modulePaths": ["<rootDir>"],
"moduleDirectories": ["node_modules", "src"],
"modulePaths": [
"<rootDir>"
],
"moduleDirectories": [
"node_modules",
"src"
],
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
Expand Down
35 changes: 35 additions & 0 deletions apps/server/src/commits/commit.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { z } from 'zod'
import { generateSchema } from '@anatine/zod-openapi'

export const basicCommitSchema = z.object({
sha: z.string(),
node_id: z.string(),
commit: z.object({
author: z.object({
name: z.string(),
email: z.string(),
date: z.string()
}),
message: z.string(),
tree: z.object({
sha: z.string(),
url: z.string()
})
}),
url: z.string(),
html_url: z.string(),
parents: z.object({
sha: z.string(),
url: z.string()
}).array(),
author: z.object({
avatar_url: z.string(),
html_url: z.string()
})
})

export const commitList = z.array(basicCommitSchema)

export type CommitList = z.infer<typeof commitList>

export const commitApiSchema = generateSchema(basicCommitSchema)
7 changes: 6 additions & 1 deletion apps/server/src/commits/commits.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Controller, Get, Param } from '@nestjs/common'
import { ApiTags, ApiParam } from '@nestjs/swagger'
import { ApiTags, ApiParam, ApiOkResponse } from '@nestjs/swagger'
import { CommitsService } from './commits.service'
import { commitApiSchema } from './commit.entity'

@ApiTags('commits')
@Controller('commits')
export class CommitsController {
constructor(private readonly service: CommitsService) {}

@ApiOkResponse({
schema: commitApiSchema,
isArray: true
})
@Get()
async getAll() {
return await this.service.getAllCommits()
Expand Down
8 changes: 6 additions & 2 deletions apps/server/src/commits/commits.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common'
import { HttpException, Injectable, InternalServerErrorException } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { OctokitClient } from 'src/octokit/octokitClient'
import { commitList } from './commit.entity'

@Injectable()
export class CommitsService {
Expand All @@ -11,7 +12,10 @@ export class CommitsService {
owner: this.configService.get<string>('repo.owner'),
repo: this.configService.get<string>('repo.name')
})
return response.data
if (response.status !== 200) throw new HttpException('Github client error', response.status)
const parsed = commitList.safeParse(response.data)
if (parsed.success) return parsed.data
else throw new InternalServerErrorException('Github response did not match parameters')
}

async getById(ref: string) {
Expand Down
66 changes: 7 additions & 59 deletions apps/server/test/commits/__mocks__/commits.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,78 +8,26 @@ export const commitListMock =
email: '[email protected]',
date: '2023-11-29T13:27:48Z'
},
committer: {
name: 'GitHub',
email: '[email protected]',
date: '2023-11-29T13:27:48Z'
},
message: 'Merge pull request #2 from SantiagoJavierRubio/dev-client\n\nClient cleanup & setup',
tree: {
sha: 'f91a62c5a9498af36d8b66d1ab26d8d6c43482f7',
url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/git/trees/f91a62c5a9498af36d8b66d1ab26d8d6c43482f7'
},
url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/git/commits/bd75cf8acc664414df895ed5d623c3426b167321',
comment_count: 0,
verification: {
verified: true,
reason: 'valid',
signature: '-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJlZzxUCRBK7hj4Ov3rIwAATzgIAHw91oJl2TzvR6UGl+mJvjlW\nApSrRmoNMdlYApdIitgWa6NzSMd/X4gkAdXtdmX8MlMemorpz3MO+BxAoirEb9oX\nflh3+y/gelIrDy72hlE2PZjyzcCAPxeF4APa1tu3cqeVlkAWlYsUv+mgpzlc/2s6\n9auj6xYIo13qunedVSV0Gby2qneQWf15xb4AaDxb8GTwHWi3U3Dgag7QhBSP4AgJ\nTePyjYKYs85x6ACtgW86e8o7K3Mpzg1ZFrCm61hbC77Mnj9h7F4HtZXIlzuHh4j0\ndngW2ecXDC/cJI+V2yFJUIO3o0RTEXdiyBX46scwVo3XqKlopkwvk91tcZE8bgs=\n=Fhc8\n-----END PGP SIGNATURE-----\n',
payload: 'tree f91a62c5a9498af36d8b66d1ab26d8d6c43482f7\nparent 5336e9079a916c32f41e04523bd5b9b08de9e55d\nparent aee9d11a05bcda6f30ba202eda0d5532e699ea92\nauthor Santiago Javier Rubio <[email protected]> 1701264468 -0300\ncommitter GitHub <[email protected]> 1701264468 -0300\n\nMerge pull request #2 from SantiagoJavierRubio/dev-client\n\nClient cleanup & setup'
}
},
url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/commits/bd75cf8acc664414df895ed5d623c3426b167321',
html_url: 'https://github.com/SantiagoJavierRubio/git-commit-history/commit/bd75cf8acc664414df895ed5d623c3426b167321',
comments_url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/commits/bd75cf8acc664414df895ed5d623c3426b167321/comments',
author: {
login: 'SantiagoJavierRubio',
id: 62577814,
node_id: 'MDQ6VXNlcjYyNTc3ODE0',
avatar_url: 'https://avatars.githubusercontent.com/u/62577814?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/SantiagoJavierRubio',
html_url: 'https://github.com/SantiagoJavierRubio',
followers_url: 'https://api.github.com/users/SantiagoJavierRubio/followers',
following_url: 'https://api.github.com/users/SantiagoJavierRubio/following{/other_user}',
gists_url: 'https://api.github.com/users/SantiagoJavierRubio/gists{/gist_id}',
starred_url: 'https://api.github.com/users/SantiagoJavierRubio/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/SantiagoJavierRubio/subscriptions',
organizations_url: 'https://api.github.com/users/SantiagoJavierRubio/orgs',
repos_url: 'https://api.github.com/users/SantiagoJavierRubio/repos',
events_url: 'https://api.github.com/users/SantiagoJavierRubio/events{/privacy}',
received_events_url: 'https://api.github.com/users/SantiagoJavierRubio/received_events',
type: 'User',
site_admin: false
},
committer: {
login: 'web-flow',
id: 19864447,
node_id: 'MDQ6VXNlcjE5ODY0NDQ3',
avatar_url: 'https://avatars.githubusercontent.com/u/19864447?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/web-flow',
html_url: 'https://github.com/web-flow',
followers_url: 'https://api.github.com/users/web-flow/followers',
following_url: 'https://api.github.com/users/web-flow/following{/other_user}',
gists_url: 'https://api.github.com/users/web-flow/gists{/gist_id}',
starred_url: 'https://api.github.com/users/web-flow/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/web-flow/subscriptions',
organizations_url: 'https://api.github.com/users/web-flow/orgs',
repos_url: 'https://api.github.com/users/web-flow/repos',
events_url: 'https://api.github.com/users/web-flow/events{/privacy}',
received_events_url: 'https://api.github.com/users/web-flow/received_events',
type: 'User',
site_admin: false
},
parents: [
{
sha: '5336e9079a916c32f41e04523bd5b9b08de9e55d',
url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/commits/5336e9079a916c32f41e04523bd5b9b08de9e55d',
html_url: 'https://github.com/SantiagoJavierRubio/git-commit-history/commit/5336e9079a916c32f41e04523bd5b9b08de9e55d'
url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/commits/5336e9079a916c32f41e04523bd5b9b08de9e55d'
},
{
sha: 'aee9d11a05bcda6f30ba202eda0d5532e699ea92',
url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/commits/aee9d11a05bcda6f30ba202eda0d5532e699ea92',
html_url: 'https://github.com/SantiagoJavierRubio/git-commit-history/commit/aee9d11a05bcda6f30ba202eda0d5532e699ea92'
url: 'https://api.github.com/repos/SantiagoJavierRubio/git-commit-history/commits/aee9d11a05bcda6f30ba202eda0d5532e699ea92'
}
]
],
author: {
avatar_url: 'https://avatars.githubusercontent.com/u/62577814?v=4',
html_url: 'https://github.com/SantiagoJavierRubio'
}
}]
1 change: 1 addition & 0 deletions packages/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/server.types'
8 changes: 8 additions & 0 deletions packages/types/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "types",
"version": "1.0.0",
"main": "./index.ts",
"types": "./index.ts",
"license": "MIT",
"private": true
}
1 change: 1 addition & 0 deletions packages/types/src/server.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { CommitList } from '../../../apps/server/src/commits/commit.entity'
19 changes: 18 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"

"@anatine/zod-openapi@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@anatine/zod-openapi/-/zod-openapi-2.2.1.tgz#1a45903b47d396a0a2c890413694dd91b523f479"
integrity sha512-tYWsCc82II3XMR8lJgg8+AHgCbfsKpgzDmcceLW1SRpqiueqYVGW5AE33W4LJTl+WJ2/mET0DWD17biFO7k/Qg==
dependencies:
ts-deepmerge "^6.0.3"

"@angular-devkit/[email protected]":
version "16.2.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-16.2.8.tgz#db74f3063e7fd573be7dafd022e8dc10e43140c0"
Expand Down Expand Up @@ -6074,7 +6081,7 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"

[email protected], rxjs@^7.5.5, rxjs@^7.8.1:
[email protected], rxjs@^7.5.5:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
Expand Down Expand Up @@ -6644,6 +6651,11 @@ ts-api-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331"
integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==

ts-deepmerge@^6.0.3:
version "6.2.0"
resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-6.2.0.tgz#77554381a4884d66cab799470bc2620a1c9d84e8"
integrity sha512-2qxI/FZVDPbzh63GwWIZYE7daWKtwXZYuyc8YNq0iTmMUwn4mL0jRLsp6hfFlgbdRSR4x2ppe+E86FnvEpN7Nw==

ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
Expand Down Expand Up @@ -7193,3 +7205,8 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

zod@^3.22.4:
version "3.22.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==

0 comments on commit 07e0162

Please sign in to comment.