Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new package ts-endpoint-swagger #145

Draft
wants to merge 1 commit into
base: daily
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ lib
.vscode
.idea
dist
coverage

.yarn/*
!.yarn/cache
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
9 changes: 9 additions & 0 deletions .yarn/plugins/@yarnpkg/plugin-typescript.cjs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ plugins:
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"

yarnPath: .yarn/releases/yarn-3.5.0.cjs
Empty file.
Empty file.
1 change: 0 additions & 1 deletion examples/react-express-example/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ const App: React.FC = () => {
apiClient
.getUser({ Params: { id: ID } })()
.then((response) => {
console.log(111, response);

setResponse(O.some(response));
setLoading(false);
Expand Down
2 changes: 0 additions & 2 deletions jest.config.base.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module.exports = {
verbose: true,
preset: 'ts-jest',
transform: {
'^.+\\.ts?$': [
Expand All @@ -12,5 +11,4 @@ module.exports = {
},
roots: ['<rootDir>/src'],
testMatch: ['**/+(*.)*(spec).ts'],
verbose: true,
};
9 changes: 9 additions & 0 deletions jest.typetests-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { transform, testMatch, globals, preset } = require('./jest.typetests-config.base');

module.exports = {
globals,
preset,
transform,
testMatch,
projects: ['<rootDir>/packages/*/jest.typetests-config.js'],
};
17 changes: 12 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,28 @@
"jest": "^29.5.0",
"lerna": "^4.0.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
},
"scripts": {
"ts-endpoint": "yarn workspace packages/ts-endpoint",
"website": "yarn workspace @ts-endpoint/docs",
"ts-endpoint": "yarn workspace ts-endpoint",
"ts-io-error": "yarn workspace ts-io-error",
"website": "yarn workspace ts-endpoint-docs",
"swagger": "yarn workspace ts-endpoint-swagger",
"express": "yarn workspace ts-endpoint-express",
"prepare": "husky install",
"build": "lerna run build",
"clean": "yarn workspaces foreach -v run clean",
"build": "yarn workspaces foreach -v run build",
"test:types": "jest --config jest.typetests-config.js",
"test:spec": "jest --config jest.config.js",
"test": "yarn test:types && yarn test:spec",
"bootstrap": "yarn && lerna bootstrap",
"bootstrap-ci": "yarn install --frozen-lockfile && lerna bootstrap",
"release": "lerna publish --yes --conventional-graduate --force-publish --skip-npm",
"pre-release": "lerna publish --yes --conventional-prerelease --npm-tag beta --skip-npm",
"npm-publish": "lerna run npm-publish",
"postversion": "lerna run postversion",
"npm-publish-beta": "lerna run npm-publish-beta",
"test": "lerna run test"
"npm-publish-beta": "lerna run npm-publish-beta"
},
"dependencies": {
"fp-ts": "^2.13.1",
Expand Down
9 changes: 4 additions & 5 deletions packages/ts-endpoint-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
"access": "public"
},
"scripts": {
"clean": "rm -rf ./lib",
"npm-publish-beta": "if [ -f lib/package.json ] ; then npm publish ./lib --tag beta; else echo echo \"no need to publish\"; fi",
"npm-publish": "if [ -f lib/package.json ] ; then npm publish ./lib; else echo echo \"no need to publish\"; fi",
"postversion": "cp package.json lib && cp -r lib lib2 && mv lib2 lib/lib",
"build": "rm -rf ./lib && tsc",
"build": "tsc -b",
"type-test": "jest --config=jest.typetests-config.js",
"runtime-test": "jest --config=jest.config.js",
"test": "yarn runtime-test && yarn type-test"
Expand All @@ -28,11 +29,9 @@
"jest-environment-jsdom": "^29.5.0"
},
"peerDependencies": {
"ts-endpoint": "^2.0.0-alpha.63"
"ts-endpoint": "^2.0.0"
},
"dependencies": {
"fp-ts": "^2.13.1",
"ts-endpoint": "^2.0.0",
"ts-io-error": "^2.0.0"
"fp-ts": "^2.13.1"
}
}
36 changes: 18 additions & 18 deletions packages/ts-endpoint-browser/src/__typetests__/fetch.typespec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const options: StaticHTTPClientConfig = {
};

const endpoints = {
prova: Endpoint({
dummy: Endpoint({
Input: {
Query: t.type({ color: t.string }),
Params: t.type({ id: t.string }),
Expand All @@ -23,20 +23,20 @@ const endpoints = {
getPath: ({ id }) => `users/${id}/crayons`,
Output: t.type({ crayons: t.array(t.string) }),
}),
provaNoParam: Endpoint({
dummyNoParam: Endpoint({
Input: {
Query: t.type({ color: t.string }),
},
Method: 'GET',
getPath: () => `users/crayons`,
Output: t.type({ crayons: t.array(t.string) }),
}),
provaWithoutInput: Endpoint({
dummyWithoutInput: Endpoint({
Method: 'GET',
getPath: () => `users`,
Output: t.type({ noInput: t.array(t.string) }),
}),
provaWithError: Endpoint({
dummyWithError: Endpoint({
Input: {
Query: t.type({ color: t.string }),
Params: t.type({ id: t.string }),
Expand All @@ -56,62 +56,62 @@ const fetchClient = GetFetchHTTPClient(options, endpoints, {
});

// @dts-jest:pass:snap should allow empty calls when input is not defined
fetchClient.provaWithoutInput();
fetchClient.dummyWithoutInput();

// @dts-jest:fail:snap should not allow calls with Body when input is not defined
fetchClient.provaWithoutInput({
fetchClient.dummyWithoutInput({
Body: 1,
});

// @dts-jest:fail:snap should not allow calls with Query when input is not defined
fetchClient.provaWithoutInput({
fetchClient.dummyWithoutInput({
Query: 1,
});

// @dts-jest:fail:snap should not allow calls with Params when input is not defined
fetchClient.provaWithoutInput({
fetchClient.dummyWithoutInput({
Params: 1,
});

// @dts-jest:fail:snap should not allow calls with Header when input is not defined
fetchClient.provaWithoutInput({
fetchClient.dummyWithoutInput({
Headers: 1,
});

// @dts-jest:fail:snap should not allow empty calls
fetchClient.prova();
fetchClient.dummy();

// @dts-jest:fail:snap should not allow empty-object as input
fetchClient.prova({});
fetchClient.dummy({});

// @dts-jest:fail:snap should not allow calls not specifing Query
fetchClient.prova({
fetchClient.dummy({
Params: { id: '123' },
});

fetchClient.prova({
fetchClient.dummy({
// @dts-jest:fail:snap should not allow calls specifing Params not declared in the endpoint
Params: { id: '123', foo: 'baz' },
Query: { color: 'marrone' },
});

fetchClient.prova({
fetchClient.dummy({
Params: { id: '123' },
Query: { color: 'marrone' },
// @dts-jest:fail:snap should not allow to add Body when not declared in the endpoint
Body: { foo: 'baz' },
});

// @dts-jest:fail:snap InferFetchResult should return the type of the Endpoint.Output
const provaResultWrong: InferFetchResult<typeof fetchClient.prova> = right({});
const dummyResultWrong: InferFetchResult<typeof fetchClient.dummy> = right({});

// @dts-jest:pass:snap InferFetchResult should return the type of the Endpoint.Output
const provaResultCorrect: InferFetchResult<typeof fetchClient.prova> = right({
const dummyResultCorrect: InferFetchResult<typeof fetchClient.dummy> = right({
crayons: ['brown'],
});

const provaWithError = pipe(
fetchClient.provaWithError({
pipe(
fetchClient.dummyWithError({
Params: { id: '123' },
Query: { color: 'marrone' },
}),
Expand Down
1 change: 1 addition & 0 deletions packages/ts-endpoint-browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"target": "es5",
"lib": ["dom", "esnext"],
"outDir": "./lib",
"rootDir": "./src",
"esModuleInterop": true
},
"include": ["src"],
Expand Down
12 changes: 6 additions & 6 deletions packages/ts-endpoint-express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ts-endpoint-express",
"version": "2.0.1-alpha.1",
"description": "Adds typesafe endpoints to your express app.",
"main": "index.js",
"main": "lib/index.js",
"repository": "https://github.com/fes300/ts-endpoint/tree/master/packages/ts-endpoint-express",
"bugs": {
"url": "https://github.com/fes300/ts-endpoint/issues?q=is%3Aopen+is%3Aissue+project%3Afes300%2Fts-endpoint%2F4"
Expand All @@ -13,26 +13,26 @@
"access": "public"
},
"scripts": {
"clean": "rm -rf ./lib",
"npm-publish-beta": "if [ -f lib/package.json ] ; then npm publish ./lib --tag beta; else echo echo \"no need to publish\"; fi",
"npm-publish": "if [ -f lib/package.json ] ; then npm publish ./lib; else echo echo \"no need to publish\"; fi",
"postversion": "cp package.json lib && cp -r lib lib2 && mv lib2 lib/lib",
"build": "rm -rf ./lib && tsc",
"build": "tsc -b",
"type-test": "jest --config=jest.typetests-config.js",
"runtime-test": "jest --config=jest.config.js",
"test": "yarn runtime-test --passWithNoTests && yarn type-test"
},
"peerDependencies": {
"express": "^4.17.1",
"ts-endpoint": "^2.0.0-alpha.19"
"ts-endpoint": "^2.0.0",
"ts-io-error": "^2.0.0"
},
"devDependencies": {
"@types/express": "^4.17.15",
"io-ts": "^2.2.20",
"jest": "^29.5.0"
},
"dependencies": {
"fp-ts": "^2.13.1",
"ts-endpoint": "^2.0.0",
"ts-io-error": "^2.0.0"
"fp-ts": "^2.13.1"
}
}
4 changes: 2 additions & 2 deletions packages/ts-endpoint-express/src/Controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { TaskEither } from 'fp-ts/TaskEither';
import { HTTPResponse } from './HTTPResponse';

export type Controller<E, P, H, Q, B, R> = (
args: { params: P; headers: H; query: Q; body: B },
export type Controller<E, A, R> = (
args: A,
req: Express.Request,
res: Express.Response
) => TaskEither<E, HTTPResponse<R>>;
90 changes: 90 additions & 0 deletions packages/ts-endpoint-express/src/RequestDecoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as E from 'fp-ts/Either';
import { sequenceS } from 'fp-ts/lib/Apply';
import { pipe } from 'fp-ts/lib/function';
import * as t from 'io-ts';
import { InferEndpointInstanceParams, MinimalEndpointInstance } from 'ts-endpoint';
import { UndefinedOrRuntime } from 'ts-endpoint/lib/helpers';
import { Codec, runtimeType } from 'ts-io-error/lib/Codec';
import { URIS } from './HKT';

interface DecodeRequestError<E> {
type: 'error';
error: E;
}

export interface DecodeRequestSuccess<E extends MinimalEndpointInstance> {
type: 'success';
result: {
params: UndefinedOrRuntime<InferEndpointInstanceParams<E>['params']>;
query: UndefinedOrRuntime<InferEndpointInstanceParams<E>['query']>;
headers: UndefinedOrRuntime<InferEndpointInstanceParams<E>['query']>;
body: InferEndpointInstanceParams<E>['body'] extends undefined
? undefined
: InferEndpointInstanceParams<E>['body'] extends Codec<any, any, any>
? runtimeType<InferEndpointInstanceParams<E>['body']>
: undefined;
};
}

type DecodeRequestResult<EI extends MinimalEndpointInstance, E = any> =
| DecodeRequestError<E>
| DecodeRequestSuccess<EI>;

export const decodeHeaders =
<EI extends MinimalEndpointInstance>(e: EI) =>
(headers: any): E.Either<t.Errors, DecodeRequestSuccess<EI>['result']['headers']> => {
return pipe(headers, (e.Input?.Headers ?? t.unknown).decode);
};

export const decodeParams =
<EI extends MinimalEndpointInstance>(e: EI) =>
(params: any): E.Either<t.Errors, DecodeRequestSuccess<EI>['result']['params']> => {
return pipe(params, (e.Input?.Params ?? t.unknown).decode);
};

export const decodeQuery =
<EI extends MinimalEndpointInstance>(e: EI) =>
(query: any): E.Either<t.Errors, DecodeRequestSuccess<EI>['result']['query']> => {
return pipe(query, (e.Input?.Query ?? t.unknown).decode);
};

export const decodeBody =
<EI extends MinimalEndpointInstance>(e: EI) =>
(body: any): E.Either<t.Errors, DecodeRequestSuccess<EI>['result']['body']> => {
return pipe(body, (e.Input?.Body ?? t.unknown).decode);
};

const decodeRequest =
<EI extends MinimalEndpointInstance>(e: EI) =>
(req: any): E.Either<t.Errors, DecodeRequestSuccess<EI>['result']> => {
return pipe(
sequenceS(E.Applicative)({
headers: decodeHeaders(e)(req.headers),
query: decodeQuery(e)(req.query),
params: decodeParams(e)(req.params),
body: decodeBody(e)(req.body),
})
);
};

const foldToError = <EI extends MinimalEndpointInstance, ER extends URIS = 'IOError'>(
result: E.Either<t.Errors, DecodeRequestSuccess<EI>['result']>,
f: (err: any) => ER
): DecodeRequestResult<EI, ER> => {
return pipe(
result,
E.fold(
(e): DecodeRequestResult<EI, ER> => ({
type: 'error',
error: f(e),
}),
(result): DecodeRequestSuccess<EI> => ({ type: 'success', result })
)
);
};

const RequestDecoder = <EI extends MinimalEndpointInstance>(e: EI) => {
return { decode: decodeRequest(e), foldToError };
};

export default RequestDecoder;
Loading