Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"cSpell.words": ["fets"]
}
4 changes: 2 additions & 2 deletions e2e/shared-scripts/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { OutputValue, Stack } from '@pulumi/pulumi/automation';

export type DeploymentConfiguration<TProgramOutput = any> = {
name: string;
prerequisites?: (stack: Stack) => Promise<void>;
config?: (stack: Stack) => Promise<void>;
prerequisites?: ((stack: Stack) => Promise<void>) | undefined;
config?: ((stack: Stack) => Promise<void>) | undefined;
program: () => Promise<{
[K in keyof TProgramOutput]: Output<TProgramOutput[K]> | TProgramOutput[K];
}>;
Expand Down
2 changes: 1 addition & 1 deletion e2e/shared-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRouter, Response, useErrorHandling } from 'fets';
import { z } from 'zod';

export function createTestServerAdapter<TServerContext = {}>(base?: string) {
export function createTestServerAdapter<TServerContext = {}>(base?: string | undefined) {
return createRouter<TServerContext, {}>({
base,
plugins: [useErrorHandling()],
Expand Down
1 change: 1 addition & 0 deletions examples/nextjs-example/src/pages/api/[...slug].ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default createRouter({
openAPI: {
endpoint: '/api/openapi.json',
},
plugins: [],
})
.route({
method: 'GET',
Expand Down
3 changes: 2 additions & 1 deletion examples/todolist/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export const router = createRouter({
schemas: {
Todo: TodoSchema,
},
} as const,
},
},
plugins: [],
})
.route({
description: 'Get all todos',
Expand Down
2 changes: 1 addition & 1 deletion examples/zod-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"fets": "0.4.9",
"ts-node": "10.9.1",
"ts-node-dev": "2.0.0",
"typescript": "^5.0.0",
"typescript": "5.1.6",
"zod": "3.21.4"
}
}
6 changes: 6 additions & 0 deletions examples/zod-example/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ export const router = createRouter()
},
});

// TODO: Type 'IncomingMessage' is not assignable to type 'NodeRequest' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
// Types of property 'url' are incompatible.
// Type 'string | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
createServer(router).listen(3000, () => {
console.log('SwaggerUI is served at http://localhost:3000/docs');
});
1 change: 1 addition & 0 deletions examples/zod-example/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "esnext",
"moduleResolution": "node",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"lint-staged": "13.2.3",
"patch-package": "8.0.0",
"prettier": "3.0.0",
"prettier-plugin-tailwindcss": "^0.4.0",
"prettier-plugin-tailwindcss": "0.4.1",
"ts-jest": "29.1.1",
"typescript": "5.1.6"
},
Expand Down
11 changes: 6 additions & 5 deletions packages/fets/src/createRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ const HTTP_METHODS: HTTPMethod[] = [
const EMPTY_OBJECT = {};
const EMPTY_MATCH = { pathname: { groups: {} } } as URLPatternResult;

export function createRouterBase(
export function createRouterBase<TServerContext>(
{
fetchAPI: givenFetchAPI,
base: basePath = '/',
plugins = [],
swaggerUI,
}: RouterOptions<any, any> = {},
}: RouterOptions<TServerContext, any>,
openAPIDocument: OpenAPIDocument,
): RouterBaseObject<any, any, any> {
const fetchAPI = {
Expand Down Expand Up @@ -242,19 +242,20 @@ export function createRouterBase(

export function createRouter<
TServerContext,
TComponents extends RouterComponentsBase = {},
TComponents extends RouterComponentsBase,
TRouterSDK extends RouterSDK<string, TypedRequest, TypedResponse> = {
[TKey: string]: never;
},
>(
options: RouterOptions<TServerContext, TComponents> = {},
options?: RouterOptions<TServerContext, TComponents> | undefined,
): Router<TServerContext, TComponents, TRouterSDK> {
const {
openAPI: { endpoint: oasEndpoint = '/openapi.json', ...openAPIDocument } = {},
swaggerUI: { endpoint: swaggerUIEndpoint = '/docs', ...swaggerUIOpts } = {},
plugins: userPlugins = [],
base = '/',
} = options;
} = options || {};

openAPIDocument.openapi = openAPIDocument.openapi || '3.0.1';
const oasInfo = (openAPIDocument.info ||= {} as OpenAPIInfo);
oasInfo.title ||= 'feTS API';
Expand Down
32 changes: 27 additions & 5 deletions packages/fets/src/plugins/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import addFormats from 'ajv-formats';
import jsonSerializerFactory from '@ardatan/fast-json-stringify';
import { URL } from '@whatwg-node/fetch';
import { Response } from '../Response.js';
import { StatusCode } from '../typed-fetch.js';
import {
import type { StatusCode } from '../typed-fetch.js';
import type {
JSONSerializer,
PromiseOrValue,
RouterComponentsBase,
Expand All @@ -18,9 +18,9 @@ import { getHeadersObj } from './utils.js';
type ValidateRequestFn = (request: RouterRequest) => PromiseOrValue<ErrorObject[]>;

export function useAjv({
components = {},
components = { schemas: {} },
}: {
components?: RouterComponentsBase;
components?: RouterComponentsBase | undefined;
} = {}): RouterPlugin<any> {
const ajv = new Ajv({
strict: false,
Expand Down Expand Up @@ -73,10 +73,16 @@ export function useAjv({
onRoute({ path, schemas, handlers }) {
const validationMiddlewares = new Map<string, ValidateRequestFn>();
if (schemas?.request?.headers && !isZodSchema(schemas.request.headers)) {
const { headers } = schemas.request;

// TODO: Property '$async' is missing in type '{ components: RouterComponentsBase; type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; pattern?: string | undefined; ... 46 more ...; [$JSONSchema7]?: unique symbol; }' but required in type 'AsyncSchema'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const validateFn = ajv.compile({
...schemas.request.headers,
...headers,
components,
});

validationMiddlewares.set('headers', request => {
const headersObj = getHeadersObj(request.headers);
const isValid = validateFn(headersObj);
Expand All @@ -87,10 +93,14 @@ export function useAjv({
});
}
if (schemas?.request?.params && !isZodSchema(schemas.request.params)) {
// TODO: Property '$async' is missing in type '{ components: RouterComponentsBase; type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; pattern?: string | undefined; ... 46 more ...; [$JSONSchema7]?: unique symbol; }' but required in type 'AsyncSchema'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const validateFn = ajv.compile({
...schemas.request.params,
components,
});

validationMiddlewares.set('params', request => {
const isValid = validateFn(request.params);
if (!isValid) {
Expand All @@ -100,10 +110,14 @@ export function useAjv({
});
}
if (schemas?.request?.query && !isZodSchema(schemas.request.query)) {
// TODO: Property '$async' is missing in type '{ components: RouterComponentsBase; type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; pattern?: string | undefined; ... 46 more ...; [$JSONSchema7]?: unique symbol; }' but required in type 'AsyncSchema'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const validateFn = ajv.compile({
...schemas.request.query,
components,
});

validationMiddlewares.set('query', request => {
const isValid = validateFn(request.query);
if (!isValid) {
Expand All @@ -113,10 +127,14 @@ export function useAjv({
});
}
if (schemas?.request?.json && !isZodSchema(schemas.request.json)) {
// TODO: Property '$async' is missing in type '{ components: RouterComponentsBase; type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; pattern?: string | undefined; ... 46 more ...; [$JSONSchema7]?: unique symbol; }' but required in type 'AsyncSchema'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const validateFn = ajv.compile({
...schemas.request.json,
components,
});

validationMiddlewares.set('json', async request => {
const contentType = request.headers.get('content-type');
if (contentType?.includes('json')) {
Expand All @@ -134,10 +152,14 @@ export function useAjv({
});
}
if (schemas?.request?.formData && !isZodSchema(schemas.request.formData)) {
// TODO: Property '$async' is missing in type '{ components: RouterComponentsBase; type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; pattern?: string | undefined; ... 46 more ...; [$JSONSchema7]?: unique symbol; }' but required in type 'AsyncSchema'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const validateFn = ajv.compile({
...schemas.request.formData,
components,
});

validationMiddlewares.set('formData', async request => {
const contentType = request.headers.get('content-type');
if (
Expand Down
Loading