chore: improve type safety Tuyau
This commit is contained in:
parent
4add77856e
commit
20ab889cf8
3
TODO.md
3
TODO.md
@ -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
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"],
|
||||||
|
})
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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") }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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: {
|
||||||
|
19
patches/@tuyau__client@0.1.2.patch
Normal file
19
patches/@tuyau__client@0.1.2.patch
Normal 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: {
|
13
patches/@tuyau__openapi@0.2.0.patch
Normal file
13
patches/@tuyau__openapi@0.2.0.patch
Normal 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] = {
|
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@ -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
|
||||||
|
Reference in New Issue
Block a user