chore: improve type safety Tuyau
All checks were successful
Chromatic / chromatic (push) Successful in 2m3s
CI / ci (push) Successful in 4m16s
CI / commitlint (push) Successful in 14s

This commit is contained in:
Théo LUDWIG 2024-08-18 01:31:02 +01:00
parent 4add77856e
commit 20ab889cf8
Signed by: theoludwig
GPG Key ID: ADFE5A563D718F3B
16 changed files with 110 additions and 25 deletions

View File

@ -40,8 +40,7 @@
- [ ] Share VineJS validators between `website` and `api` - [ ] Share VineJS validators between `website` and `api`
- [ ] Implement Wikipedia Game Solver (`website`) - [ ] Implement Wikipedia Game Solver (`website`)
- [x] Init Next.js project - [x] Init Next.js project
- [ ] Try to use <https://www.npmjs.com/package/@tuyau/client> for API calls - [x] Try to use <https://www.npmjs.com/package/@tuyau/client> for API calls
- [ ] Hard code 2 pages to test if it works with `console.log` in the browser
- [ ] Implement a form with inputs, button to submit, and list all pages to go from one to another, or none if it is not possible - [ ] Implement a form with inputs, button to submit, and list all pages to go from one to another, or none if it is not possible
- [ ] Add images, links to the pages + good UI/UX - [ ] Add images, links to the pages + good UI/UX
- [ ] Autocompletion page titles - [ ] Autocompletion page titles

View File

@ -11,9 +11,8 @@
} }
}, },
{ {
"files": ["app/controllers/**/*.ts", "app/middleware/**/*.ts"], "files": ["app/controllers/**/*.ts"],
"rules": { "rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/naming-convention": "off" "@typescript-eslint/naming-convention": "off"
} }
} }

View File

@ -2,15 +2,31 @@ import { healthChecks } from "#start/health.ts"
import { middleware } from "#start/kernel.ts" import { middleware } from "#start/kernel.ts"
import type { HttpContext } from "@adonisjs/core/http" import type { HttpContext } from "@adonisjs/core/http"
import router from "@adonisjs/core/services/router" import router from "@adonisjs/core/services/router"
import type { HealthCheckReport } from "@adonisjs/core/types/health"
export default class get_health_controller { export default class get_health_controller {
public async handle(context: HttpContext) { public async handle(context: HttpContext): Promise<
| {
__response: HealthCheckReport
__status: 200
}
| {
__response: HealthCheckReport
__status: 503
}
> {
const report = await healthChecks.run() const report = await healthChecks.run()
if (report.isHealthy) { if (!report.isHealthy) {
return context.response.ok(report) return context.response.serviceUnavailable(report)
} }
return context.response.serviceUnavailable(report) return context.response.ok(report)
} }
} }
router.get("/health", [get_health_controller]).use(middleware.appKeySecurity()) router
.get("/health", [get_health_controller])
.use(middleware.appKeySecurity())
.openapi({
description: "Ensure that the application is in a healthy state.",
tags: ["health"],
})

View File

@ -1,3 +1,4 @@
import type { ExceptionMessage } from "#app/exceptions/handler.ts"
import Page, { type PageWithInternalLinksRaw } from "#app/models/page.ts" import Page, { type PageWithInternalLinksRaw } from "#app/models/page.ts"
import { throttle } from "#start/limiter.ts" import { throttle } from "#start/limiter.ts"
import type { HttpContext } from "@adonisjs/core/http" import type { HttpContext } from "@adonisjs/core/http"
@ -13,13 +14,22 @@ export const get_wikipedia_page_by_id_validator = vine.compile(
) )
export default class get_wikipedia_page_by_id { export default class get_wikipedia_page_by_id {
public async handle(context: HttpContext): Promise<PageWithInternalLinksRaw> { public async handle(context: HttpContext): Promise<
| {
__response: ExceptionMessage
__status: 404
}
| {
__response: PageWithInternalLinksRaw
__status: 200
}
> {
const payload = await context.request.validateUsing( const payload = await context.request.validateUsing(
get_wikipedia_page_by_id_validator, get_wikipedia_page_by_id_validator,
) )
const page = await Page.findOrFail(payload.params.id) const page = await Page.findOrFail(payload.params.id)
await page.load("internalLinks") await page.load("internalLinks")
return page return context.response.ok(page)
} }
} }

View File

@ -25,14 +25,17 @@ export const get_wikipedia_pages_validator = vine.compile(
) )
export default class get_wikipedia_pages { export default class get_wikipedia_pages {
public async handle(context: HttpContext): Promise<PageRaw[]> { public async handle(context: HttpContext): Promise<{
__response: PageRaw[]
__status: 200
}> {
const payload = await context.request.validateUsing( const payload = await context.request.validateUsing(
get_wikipedia_pages_validator, get_wikipedia_pages_validator,
) )
const pages = await Page.query() const pages = await Page.query()
.whereLike("title", `${payload.title}%`) .whereLike("title", `${payload.title}%`)
.limit(payload.limit) .limit(payload.limit)
return pages return context.response.ok(pages)
} }
} }

View File

@ -2,6 +2,10 @@ import type { HttpContext } from "@adonisjs/core/http"
import { ExceptionHandler } from "@adonisjs/core/http" import { ExceptionHandler } from "@adonisjs/core/http"
import app from "@adonisjs/core/services/app" import app from "@adonisjs/core/services/app"
export interface ExceptionMessage {
message: string
}
export default class HttpExceptionHandler extends ExceptionHandler { export default class HttpExceptionHandler extends ExceptionHandler {
/** /**
* In debug mode, the exception handler will display verbose errors with pretty printed stack traces. * In debug mode, the exception handler will display verbose errors with pretty printed stack traces.

View File

@ -1,9 +1,13 @@
import type { ExceptionMessage } from "#app/exceptions/handler.ts"
import { APP_KEY, APP_KEY_HEADER_NAME } from "#config/app.ts" import { APP_KEY, APP_KEY_HEADER_NAME } from "#config/app.ts"
import type { HttpContext } from "@adonisjs/core/http" import type { HttpContext } from "@adonisjs/core/http"
import type { NextFn } from "@adonisjs/core/types/http" import type { NextFn } from "@adonisjs/core/types/http"
export default class AppKeySecurityMiddleware { export default class AppKeySecurityMiddleware {
public async handle(context: HttpContext, next: NextFn) { public async handle(
context: HttpContext,
next: NextFn,
): Promise<unknown | ExceptionMessage> {
if (context.request.header(APP_KEY_HEADER_NAME) === APP_KEY) { if (context.request.header(APP_KEY_HEADER_NAME) === APP_KEY) {
return next() return next()
} }

View File

@ -17,7 +17,7 @@ export default class AuthMiddleware {
options: { options: {
guards?: Array<keyof Authenticators> guards?: Array<keyof Authenticators>
} = {}, } = {},
) { ): Promise<void> {
await context.auth.authenticateUsing(options.guards, { await context.auth.authenticateUsing(options.guards, {
loginRoute: this.redirectTo, loginRoute: this.redirectTo,
}) })

View File

@ -9,7 +9,7 @@ import type { NextFn } from "@adonisjs/core/types/http"
* - And bind "Logger" class to the "context.logger" object. * - And bind "Logger" class to the "context.logger" object.
*/ */
export default class ContainerBindingsMiddleware { export default class ContainerBindingsMiddleware {
public async handle(context: HttpContext, next: NextFn) { public async handle(context: HttpContext, next: NextFn): Promise<void> {
context.containerResolver.bindValue(HttpContext, context) context.containerResolver.bindValue(HttpContext, context)
context.containerResolver.bindValue(Logger, context.logger) context.containerResolver.bindValue(Logger, context.logger)

View File

@ -5,7 +5,7 @@ import type { NextFn } from "@adonisjs/core/types/http"
* Updating the "Accept" header to always accept "application/json" response from the server. This will force the internals of the framework like validator errors or auth errors to return a JSON response. * Updating the "Accept" header to always accept "application/json" response from the server. This will force the internals of the framework like validator errors or auth errors to return a JSON response.
*/ */
export default class ForceJsonResponseMiddleware { export default class ForceJsonResponseMiddleware {
public async handle({ request }: HttpContext, next: NextFn) { public async handle({ request }: HttpContext, next: NextFn): Promise<void> {
const headers = request.headers() const headers = request.headers()
headers.accept = "application/json" headers.accept = "application/json"

View File

@ -12,7 +12,7 @@ const tuyauConfig = defineConfig({
title: "Wikipedia Game Solver API", title: "Wikipedia Game Solver API",
version: VERSION, version: VERSION,
}, },
tags: [{ name: "users" }, { name: "wikipedia" }], tags: [{ name: "health" }, { name: "users" }, { name: "wikipedia" }],
servers: [{ url: env.get("API_URL") }], servers: [{ url: env.get("API_URL") }],
}, },
}, },

View File

@ -34,7 +34,9 @@
}, },
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"@tuyau/core@0.1.4": "patches/@tuyau__core@0.1.4.patch" "@tuyau/core@0.1.4": "patches/@tuyau__core@0.1.4.patch",
"@tuyau/openapi@0.2.0": "patches/@tuyau__openapi@0.2.0.patch",
"@tuyau/client@0.1.2": "patches/@tuyau__client@0.1.2.patch"
} }
} }
} }

View File

@ -39,10 +39,20 @@ export const WikipediaClient: React.FC = () => {
const data = await api.wikipedia const data = await api.wikipedia
.pages({ id: fromPageWikipediaLinks.pageId }) .pages({ id: fromPageWikipediaLinks.pageId })
// TODO: any .$get({})
.$get({} as any)
.unwrap() .unwrap()
console.log(data) console.log(data)
const data2 = await api.wikipedia.pages
.$get({
query: {
title: "Node.j",
limit: 5,
},
})
.unwrap()
console.log(data2)
// const deepInternalLinks = await getDeepWikipediaPageInternalLinks({ // const deepInternalLinks = await getDeepWikipediaPageInternalLinks({
// locale: localeWikipedia, // locale: localeWikipedia,
// data: { // data: {

View File

@ -0,0 +1,19 @@
diff --git a/build/index.d.ts b/build/index.d.ts
index fd28b2fd3f91f0312e933189df5b635eeb692281..445d47f6f18a981bba7c0e784a03972bdf99c880 100644
--- a/build/index.d.ts
+++ b/build/index.d.ts
@@ -83,11 +83,11 @@ type TuyauRpcClient<in out Route extends Record<string, any>> = {
response: infer Res extends Record<number, unknown>;
request: infer Request;
} ? K extends '$get' | '$head' ? unknown extends Request ? (options?: TuyauQueryOptions & {
- query?: Request;
+ query?: Omit<Request, 'cookies' | 'headers' | 'params'>;
}) => ResponseOrUnwrap<Res> : {} extends Request ? (options?: TuyauQueryOptions & {
- query?: Request;
+ query?: Omit<Request, 'cookies' | 'headers' | 'params'>;
}) => ResponseOrUnwrap<Res> : (options: TuyauQueryOptions & {
- query: Request;
+ query?: Omit<Request, 'cookies' | 'headers' | 'params'>;
}) => ResponseOrUnwrap<Res> : {} extends Request ? (body?: Request | null, options?: TuyauQueryOptions) => ResponseOrUnwrap<Res> : (body: Request, options?: TuyauQueryOptions) => ResponseOrUnwrap<Res> : K extends '$url' ? () => string : CreateParams<Route[K]>;
};
type CreateParams<Route extends Record<string, any>> = Extract<keyof Route, `:${string}`> extends infer Path extends string ? IsNever<Path> extends true ? Prettify<TuyauRpcClient<Route>> : ((params: {

View File

@ -0,0 +1,13 @@
diff --git a/build/chunk-DFECKFQT.js b/build/chunk-DFECKFQT.js
index 0740bf03f8c9de643f09fdcf02c43e40eb5efa55..ff348509b0e77b0b98e6e6db57c3355e3e0914f7 100644
--- a/build/chunk-DFECKFQT.js
+++ b/build/chunk-DFECKFQT.js
@@ -266,7 +266,7 @@ var OpenApiGenerator = class {
for (const prop of type.getProperties()) {
const propName = prop.getName();
const type2 = prop.getValueDeclaration()?.getType();
- if (!type2)
+ if (!type2 || (mode === 'request' && ['cookies', 'headers', 'params'].includes(propName)))
continue;
if (type2.isArray()) {
properties2[propName] = {

View File

@ -245,9 +245,15 @@ catalogs:
version: 2.0.5 version: 2.0.5
patchedDependencies: patchedDependencies:
'@tuyau/client@0.1.2':
hash: fb6mal6i73tvzidaazfbwsp5ya
path: patches/@tuyau__client@0.1.2.patch
'@tuyau/core@0.1.4': '@tuyau/core@0.1.4':
hash: zzabo2h6qjt52qr7ntgx4dwdpa hash: zzabo2h6qjt52qr7ntgx4dwdpa
path: patches/@tuyau__core@0.1.4.patch path: patches/@tuyau__core@0.1.4.patch
'@tuyau/openapi@0.2.0':
hash: i6nyw7jxy3i3iokbuvzj62ysse
path: patches/@tuyau__openapi@0.2.0.patch
importers: importers:
@ -315,7 +321,7 @@ importers:
version: 0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)) version: 0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0))
'@tuyau/openapi': '@tuyau/openapi':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.2.0(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0))) version: 0.2.0(patch_hash=i6nyw7jxy3i3iokbuvzj62ysse)(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)))
'@tuyau/utils': '@tuyau/utils':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.0.4 version: 0.0.4
@ -598,7 +604,7 @@ importers:
dependencies: dependencies:
'@tuyau/client': '@tuyau/client':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.1.2 version: 0.1.2(patch_hash=fb6mal6i73tvzidaazfbwsp5ya)
devDependencies: devDependencies:
'@repo/api': '@repo/api':
specifier: 'workspace:' specifier: 'workspace:'
@ -12578,7 +12584,7 @@ snapshots:
mkdirp: 3.0.1 mkdirp: 3.0.1
path-browserify: 1.0.1 path-browserify: 1.0.1
'@tuyau/client@0.1.2': '@tuyau/client@0.1.2(patch_hash=fb6mal6i73tvzidaazfbwsp5ya)':
dependencies: dependencies:
'@poppinss/matchit': 3.1.2 '@poppinss/matchit': 3.1.2
ky: 1.6.0 ky: 1.6.0
@ -12589,7 +12595,7 @@ snapshots:
'@adonisjs/core': 6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0) '@adonisjs/core': 6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)
ts-morph: 22.0.0 ts-morph: 22.0.0
'@tuyau/openapi@0.2.0(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)))': '@tuyau/openapi@0.2.0(patch_hash=i6nyw7jxy3i3iokbuvzj62ysse)(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)))':
dependencies: dependencies:
'@tuyau/core': 0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)) '@tuyau/core': 0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0))
defu: 6.1.4 defu: 6.1.4