Skip to content

Commit

Permalink
Merge pull request #74 from ty-ras/issue/73-add-runtypes-support
Browse files Browse the repository at this point in the history
Issue/73 add runtypes support
  • Loading branch information
stazz authored Aug 11, 2023
2 parents e587346 + 6ed2a6d commit 0d2086d
Show file tree
Hide file tree
Showing 50 changed files with 13,310 additions and 26 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ jobs:
- frontend-fetch-zod
- extras-backend-zod
- extras-frontend-zod
- backend-node-runtypes-openapi
- frontend-fetch-runtypes
- extras-backend-runtypes
- extras-frontend-runtypes
runs-on: ubuntu-latest
name: Build and test ${{ matrix.dir }}
steps:
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ This repository contains the following aggregator libraries for [`zod`](https://
- [`@ty-ras/frontend-fetch-zod`](./frontend-fetch-zod) for frontend clients using Fetch API to send HTTP requests and `zod` as data validation library,
- [`@ty-ras-extras/backend-zod](./extras-backend-zod) for most typically used extra functionality (caching, configuration, resource pool, main invocation, SQL) in backend, and
- [`@ty-ras-extras/frontend-zod](./extras-frontend-zod) for most typically used extra functionality (caching, configuration) in frontend.

# Data validation using `runtypes`
This repository contains the following aggregator libraries for [`runtypes`](https://github.com/pelotom/runtypes) framework:
- [`@ty-ras/backend-node-runtypes-openapi`](./backend-node-runtypes-openapi) for backends using Node HTTP(S) 1/2 server, `runtypes` as data validation library, and OpenAPI as metadata format,
- [`@ty-ras/frontend-fetch-runtypes`](./frontend-fetch-runtypes) for frontend clients using Fetch API to send HTTP requests and `runtypes` as data validation library,
- [`@ty-ras-extras/backend-runtypes](./extras-backend-runtypes) for most typically used extra functionality (caching, configuration, resource pool, main invocation, SQL) in backend, and
- [`@ty-ras-extras/frontend-runtypes](./extras-frontend-zod) for most typically used extra functionality (caching, configuration) in frontend.
3 changes: 2 additions & 1 deletion backend-node-io-ts-openapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import * as tyrasDataBE from "@ty-ras/data-backend";
This library exports all members of the following libraries:
- [`@ty-ras/protocol`](https://npmjs.com/package/@ty-ras/protocol),
- [`@ty-ras/endpoint`](https://npmjs.com/package/@ty-ras/endpoint),
- [`@ty-ras/endpoint-prefix`](https://npmjs.com/package/@ty-ras/endpoint-prefix),
- [`@ty-ras/endpoint-spec`](https://npmjs.com/package/@ty-ras/endpoint-spec),
- [`@ty-ras/server`](https://npmjs.com/package/@ty-ras/server),
- [`@ty-ras/server-node`](https://npmjs.com/package/@ty-ras/server-node),
- [`@ty-ras/state`](https://npmjs.com/package/@ty-ras/state),
- [`@ty-ras/state-io-ts`](https://npmjs.com/package/@ty-ras/state-io-ts),
- [`@ty-ras/data`](https://npmjs.com/package/@ty-ras/data),
- [`@ty-ras/data-io-ts`](https://npmjs.com/package/@ty-ras/data-io-ts),
- [`@ty-ras/data-backend`](https://npmjs.com/package/@ty-ras/data-backend),
Expand Down
4 changes: 2 additions & 2 deletions backend-node-io-ts-openapi/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ty-ras/backend-node-io-ts-openapi",
"version": "2.0.1",
"version": "2.0.2",
"author": {
"name": "Stanislav Muhametsin",
"email": "[email protected]",
Expand Down Expand Up @@ -48,7 +48,7 @@
"@ty-ras/data-backend-io-ts": "^2.0.2",
"@ty-ras/endpoint-spec": "^2.0.0",
"@ty-ras/metadata-jsonschema-io-ts": "^2.0.1",
"@ty-ras/metadata-openapi": "^2.0.1",
"@ty-ras/metadata-openapi": "^2.0.2",
"@ty-ras/state-io-ts": "^2.0.1",
"fp-ts": "^2.13.1",
"io-ts": "^2.2.19",
Expand Down
3 changes: 0 additions & 3 deletions backend-node-io-ts-openapi/src/__test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ test("Verify that using server works", async (c) => {
400: {
description: "If URL path parameters fail validation.",
},
401: {
description: "If authentication failed.",
},
},
},
},
Expand Down
8 changes: 4 additions & 4 deletions backend-node-io-ts-openapi/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,10 @@
dependencies:
"@ty-ras/data" "^2.1.0"

"@ty-ras/metadata-openapi@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@ty-ras/metadata-openapi/-/metadata-openapi-2.0.1.tgz#99266c17ed57674aa6034c217230c40cb42369e9"
integrity sha512-31Zc5zas1irYB2hvGeM1umqzG02ExkxCgNnkqThd1iyKcsai4FDo+1EXIpwxsPjhxXN1yYWNxpb6Gf+RdZ34wg==
"@ty-ras/metadata-openapi@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@ty-ras/metadata-openapi/-/metadata-openapi-2.0.2.tgz#0c0214b775cb8b727b82c04fe9b94aeafb6bc8d4"
integrity sha512-DMxOT1oMYwSdILZic7cTtGZYHd301kxzUbFdlJKuJ6/YyiUEXrG20cscYY7RcX+dbgRGWnyv3Ib9JfYrEHA/4A==
dependencies:
"@ty-ras/endpoint" "^2.0.0"
"@ty-ras/metadata" "^2.0.0"
Expand Down
30 changes: 30 additions & 0 deletions backend-node-runtypes-openapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# TyRAS Library Bundle - Backend Running Node HTTP(S) 1/2 Server, `runtypes` as Data Validation Library, OpenAPI as Metadata Format

This library bundles various `@ty-ras/*` libraries into one.
The purpose is to enable writing
```ts
import * as tyras from "@ty-ras/backend-node-runtypes-openapi";
```
instead of
```ts
import * as tyrasData from "@ty-ras/data";
import * as tyrasDataBE from "@ty-ras/data-backend";
...etc
```

This library exports all members of the following libraries:
- [`@ty-ras/protocol`](https://npmjs.com/package/@ty-ras/protocol),
- [`@ty-ras/endpoint`](https://npmjs.com/package/@ty-ras/endpoint),
- [`@ty-ras/endpoint-spec`](https://npmjs.com/package/@ty-ras/endpoint-spec),
- [`@ty-ras/server`](https://npmjs.com/package/@ty-ras/server),
- [`@ty-ras/server-node`](https://npmjs.com/package/@ty-ras/server-node),
- [`@ty-ras/state`](https://npmjs.com/package/@ty-ras/state),
- [`@ty-ras/state-runtypes`](https://npmjs.com/package/@ty-ras/state-runtypes),
- [`@ty-ras/data`](https://npmjs.com/package/@ty-ras/data),
- [`@ty-ras/data-runtypes`](https://npmjs.com/package/@ty-ras/data-runtypes),
- [`@ty-ras/data-backend`](https://npmjs.com/package/@ty-ras/data-backend),
- [`@ty-ras/data-backend-runtypes`](https://npmjs.com/package/@ty-ras/data-backend-runtypes),
- [`@ty-ras/metadata`](https://npmjs.com/package/@ty-ras/metadata),
- [`@ty-ras/metadata-openapi`](https://npmjs.com/package/@ty-ras/metadata-openapi),
- [`@ty-ras/metadata-jsonschema`](https://npmjs.com/package/@ty-ras/metadata-jsonschema`), and
- [`@ty-ras/metadata-jsonschema-runtypes`](https://npmjs.com/package/@ty-ras/metadata-jsonschema-runtypes).
103 changes: 103 additions & 0 deletions backend-node-runtypes-openapi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"name": "@ty-ras/backend-node-runtypes-openapi",
"version": "2.0.0",
"author": {
"name": "Stanislav Muhametsin",
"email": "[email protected]",
"url": "https://github.com/stazz"
},
"license": "MIT",
"description": "Bundle of TyRAS libraries suitable to use in backends which run on Node HTTP(S) 1 or 2 server, use `runtypes` as data validation library, and OpenAPI as metadata about the endpoints.",
"keywords": [
"backend",
"http",
"node",
"server",
"data",
"validation",
"runtypes",
"openapi",
"metadata",
"runtime"
],
"repository": {
"type": "git",
"url": "https://github.com/ty-ras/packaging"
},
"files": [
"./src",
"./dist-ts",
"./dist-esm",
"./dist-cjs",
"README.md",
"LICENSE.txt"
],
"type": "module",
"main": "./dist-cjs/index.js",
"module": "./dist-esm/index.js",
"types": "./dist-ts/index.d.ts",
"exports": {
".": {
"types": "./dist-ts/index.d.ts",
"import": "./dist-esm/index.js",
"require": "./dist-cjs/index.js"
}
},
"dependencies": {
"@ty-ras/server-node": "^2.0.0",
"@ty-ras/data-backend-runtypes": "^2.0.0",
"@ty-ras/endpoint-spec": "^2.0.0",
"@ty-ras/metadata-jsonschema-runtypes": "^2.0.0",
"@ty-ras/metadata-openapi": "^2.0.2",
"@ty-ras/state-runtypes": "^2.0.0",
"openapi-types": "^12.0.2",
"raw-body": "^2.5.1",
"runtypes": "^6.7.0"
},
"devDependencies": {
"@ava/get-port": "2.0.0",
"@babel/core": "7.22.10",
"@babel/eslint-parser": "7.22.10",
"@types/node": "18.16.3",
"@typescript-eslint/eslint-plugin": "6.3.0",
"@typescript-eslint/parser": "6.3.0",
"ava": "5.3.1",
"c8": "8.0.1",
"eslint": "8.47.0",
"eslint-plugin-jsdoc": "46.4.6",
"eslint-plugin-path-import-extension": "0.9.0",
"eslint-plugin-type-only-import": "0.9.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-sonarjs": "0.20.0",
"madge": "6.1.0",
"prettier": "3.0.1",
"raw-body": "^2.5.1",
"ts-node": "10.9.1",
"typescript": "5.1.6",
"undici": "5.23.0"
},
"scripts": {
"build:run": "yarn run lint && yarn run tsc",
"build:ci": "yarn run clear-build-artifacts && yarn run compile-d-ts-files && yarn run tsc --outDir ./dist-esm && yarn run tsc --module CommonJS --outDir ./dist-cjs && yarn run remove-empty-js-files && yarn run generate-stub-package-json-for-cjs && yarn run format-output-files",
"clear-build-artifacts": "rm -rf dist dist-ts dist-cjs dist-esm build",
"compile-d-ts-files": "yarn run tsc --removeComments false --emitDeclarationOnly --declaration --declarationDir ./dist-ts && yarn run tsc:plain --project tsconfig.out.json",
"format-output-files": "yarn run format-output-files-ts && yarn run format-output-files-js",
"format-output-files-ts": "eslint --no-eslintrc --config '.eslintrc.out-ts.cjs' --fix --fix-type layout './dist-ts/**/*.ts'",
"format-output-files-js": "eslint --no-eslintrc --config '.eslintrc.out.cjs' --fix 'dist-cjs/**/*js' 'dist-esm/**/*js'",
"generate-stub-package-json-for-cjs": "../scripts/generate-stub-package-json.cjs",
"lint": "yarn run lint:eslint && yarn run lint:circular",
"lint:circular": "madge --circular --no-color --no-spinner --extensions ts --warning ./src",
"lint:eslint": "eslint ./src --ext .ts,.tsx",
"remove-empty-js-files": "../scripts/remove-empty-js-files.cjs",
"tsc": "tsc --project tsconfig.build.json",
"tsc:plain": "tsc",
"test:coverage": "c8 --temp-directory /tmp ava",
"test:run": "c8 --temp-directory /tmp --reporter text ava"
},
"resolutions": {
"detective-typescript": "11.1.0",
"dependency-tree": "10.0.9",
"precinct": "11.0.5"
}
}
20 changes: 20 additions & 0 deletions backend-node-runtypes-openapi/src/__test__/api/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @file This file contains code exposing the TyRAS application builder.
*/

import * as tyras from "../..";

const app = tyras.newBuilder({
// Max limit for request bodies is 10MB by default.
limit: "10mb",
});

/**
* This is the application builder to use to define OpenAPI-enabled endpoints.
*/
export default app;

/**
* This type is the base type for all state specifications used by endpoints.
*/
export type StateSpecBase = tyras.StateSpecBaseOfAppBuilder<typeof app>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @file This file contains the implementation for greeting endpoint.
*/

import * as tyras from "../../..";
import { greeting } from "../protocol";
import app, { type StateSpecBase } from "../app";

const urlPath = app.url`${tyras.urlParameter(
"target",
greeting.data.greetingTarget,
)}`({
// OpenAPI-specific information about all endpoints behind this URL pattern.
openapi: {
pathItem: {
description: "Endpoint(s) related to greeting.",
},
url: {
target: {
description: "The target to greet.",
},
},
},
});

const stateSpec = {
// We don't really use authentication-related properties in the endpoint.
// This is just to demonstrate how to specify that "this endpoint works for both authenticated and unauthenticated requests".
// For truly unauthenticated endpoints, simply remove this property altogether so that stateSpec is simply an empty object.
userId: false,
} as const satisfies StateSpecBase;

/**
* This class implements the greeting endpoint(s).
*/
export default class GreetingEndpoint {
/**
* Implementation for {@link greeting.GetGreeting} endpoint.
* @param param0 The endpoint input.
* @param param0.url Privately deconstructed variable.
* @param param0.url.target Privately deconstructed variable.
* @returns The greeting.
* @see greeting.GetGreeting
*/
@urlPath<greeting.GetGreeting>({
// OpenAPI -specific information about this endpoint
openapi: {
operation: { description: "Get the greeting for given target." },
responseBody: {
description: "The returned greeting.",
mediaTypes: { "application/json": { example: "Hello, world!" } },
},
},
})({
method: "GET",
responseBody: tyras.responseBody(greeting.data.greeting),
state: stateSpec,
})
getGreeting({
url: { target },
}: tyras.GetMethodArgs<
greeting.GetGreeting,
typeof urlPath,
typeof stateSpec
>) {
return `Hello, ${target}!`;
}
}
29 changes: 29 additions & 0 deletions backend-node-runtypes-openapi/src/__test__/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @file This file exposes the built TyRAS AppEndpoints, including OpenAPI endpoint, to be served via HTTP server.
*/

import * as tyras from "../../";
import app from "./app";
import Greeting from "./endpoints/greeting";

/**
* These endpoints compose the whole of REST API: the actual endpoints + endpoint to serve OpenAPI document about the actual endpoints.
*/
export default tyras.endpointsWithOpenAPI(
app,
app.createEndpoints(
{
// OpenAPI-specific information about the REST API.
openapi: {
title: "The example API",
version: "1.0.0",
},
},
{
"/api/": {
// The final URL of greeting endpoint will be "/api/greet/{target}".
"greet/": new Greeting(),
},
},
),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @file This file contains the validators which will be used to validate the HTTP-protocol related inputs and outputs when calling the endpoints.
*/

import * as data from "@ty-ras/data-runtypes"; // We don't import from main package in order for this code to be copypasteable into project shared by both BE and FE.
import * as t from "runtypes";

export const greetingTarget = t.String;
export const greeting = t.String;

/**
* The target of the protocol which does the greeting.
*/
export type GreetingTarget = data.ProtocolTypeOf<typeof greetingTarget>;

/**
* The greeting text.
*/
export type GreetingResult = data.ProtocolTypeOf<typeof greeting>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @file This file contains TyRAS protocol type definitions for endpoint doing a greeting.
*/

import type * as data from "./data";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type * as protocol from "@ty-ras/protocol"; // Imported only for JSDoc.

/**
* This is TyRAS protocol type definition for endpoint which performs a greeting.
*/
export interface GetGreeting {
/**
* The HTTP method for this endpoint: `GET`.
* @see protocol.ProtocolSpecCore.method
*/
method: "GET";

/**
* The URL parameters for this endpoint.
* Only one:
* - `target`: The target of the greeting.
* @see protocol.ProtocolSpecURL.url
* @see data.GreetingTarget
*/
url: {
target: data.GreetingTarget;
};
/**
* The response body for this endpoint.
* @see protocol.ProtocolSpecCore.responseBody
*/
responseBody: data.GreetingResult;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @file This file exports all the things related to greeting protocol(s).
*/

export * as data from "./data";
export type * from "./endpoints.types";
Loading

0 comments on commit 0d2086d

Please sign in to comment.