From 9b44f8b3dd7f906f2c23af4ab8baeb8529016154 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Thu, 4 Jul 2024 11:44:34 -0400 Subject: [PATCH 01/13] added tests to validator, added exports for core, created stubs for express/hyper-express --- packages/core/cache/index.ts | 4 + .../cache/interfaces/ttlCache.interface.ts | 2 +- packages/core/cache/redisTtlCache.ts | 2 +- ...CacheRecord.ts => ttlCacheRecord.types.ts} | 0 packages/core/controllers/index.ts | 1 + packages/core/database/index.ts | 1 + packages/core/entityMapper/index.ts | 2 + packages/core/http/index.ts | 3 + packages/core/http/middlewares/index.ts | 2 + packages/core/http/types/index.ts | 2 + packages/core/index.ts | 12 +- packages/core/services/index.ts | 1 + packages/express/package.json | 19 ++ packages/hyper-express/package.json | 19 ++ packages/validator/index.ts | 4 +- packages/validator/interfaces/index.ts | 1 + packages/validator/package-lock.json | 50 ++--- packages/validator/package.json | 3 +- .../validator/tests/typebox/equality.test.ts | 144 +++++++++++++- .../tests/typebox/largeSchema.test.ts | 180 +++++++++--------- packages/validator/tests/zod/equality.test.ts | 150 ++++++++++++++- .../validator/tests/zod/largeSchema.test.ts | 178 ++++++++--------- packages/validator/typebox/index.ts | 20 +- packages/validator/zod/index.ts | 3 +- 24 files changed, 575 insertions(+), 228 deletions(-) create mode 100644 packages/core/cache/index.ts rename packages/core/cache/types/{ttlCacheRecord.ts => ttlCacheRecord.types.ts} (100%) create mode 100644 packages/core/controllers/index.ts create mode 100644 packages/core/database/index.ts create mode 100644 packages/core/entityMapper/index.ts create mode 100644 packages/core/http/index.ts create mode 100644 packages/core/http/middlewares/index.ts create mode 100644 packages/core/http/types/index.ts create mode 100644 packages/core/services/index.ts create mode 100644 packages/express/package.json create mode 100644 packages/hyper-express/package.json create mode 100644 packages/validator/interfaces/index.ts diff --git a/packages/core/cache/index.ts b/packages/core/cache/index.ts new file mode 100644 index 000000000..fe988cf54 --- /dev/null +++ b/packages/core/cache/index.ts @@ -0,0 +1,4 @@ +export * from './interfaces/ttlCache.interface'; +export * from './redisTtlCache'; +export * from './types/ttlCacheRecord.types'; + diff --git a/packages/core/cache/interfaces/ttlCache.interface.ts b/packages/core/cache/interfaces/ttlCache.interface.ts index 014c5ffda..0d354727d 100644 --- a/packages/core/cache/interfaces/ttlCache.interface.ts +++ b/packages/core/cache/interfaces/ttlCache.interface.ts @@ -1,4 +1,4 @@ -import { TtlCacheRecord } from "../types/ttlCacheRecord"; +import { TtlCacheRecord } from "../types/ttlCacheRecord.types"; /** * Interface representing a TTL (Time-To-Live) cache. diff --git a/packages/core/cache/redisTtlCache.ts b/packages/core/cache/redisTtlCache.ts index 920442a5e..a00bd39f2 100644 --- a/packages/core/cache/redisTtlCache.ts +++ b/packages/core/cache/redisTtlCache.ts @@ -1,6 +1,6 @@ import { createClient } from 'redis'; import { TtlCache } from './interfaces/ttlCache.interface'; -import { TtlCacheRecord } from './types/ttlCacheRecord'; +import { TtlCacheRecord } from './types/ttlCacheRecord.types'; /** * Class representing a Redis-based TTL (Time-To-Live) cache. diff --git a/packages/core/cache/types/ttlCacheRecord.ts b/packages/core/cache/types/ttlCacheRecord.types.ts similarity index 100% rename from packages/core/cache/types/ttlCacheRecord.ts rename to packages/core/cache/types/ttlCacheRecord.types.ts diff --git a/packages/core/controllers/index.ts b/packages/core/controllers/index.ts new file mode 100644 index 000000000..2b2bd5741 --- /dev/null +++ b/packages/core/controllers/index.ts @@ -0,0 +1 @@ +export * from './interfaces/controller.interface'; diff --git a/packages/core/database/index.ts b/packages/core/database/index.ts new file mode 100644 index 000000000..8e40384ba --- /dev/null +++ b/packages/core/database/index.ts @@ -0,0 +1 @@ +export * from './mikro/models/entities/base.entity'; diff --git a/packages/core/entityMapper/index.ts b/packages/core/entityMapper/index.ts new file mode 100644 index 000000000..7a92086c9 --- /dev/null +++ b/packages/core/entityMapper/index.ts @@ -0,0 +1,2 @@ +export * from './models/requestEntityMapper.model'; +export * from './models/responseEntityMapper.model'; diff --git a/packages/core/http/index.ts b/packages/core/http/index.ts new file mode 100644 index 000000000..5a00e5455 --- /dev/null +++ b/packages/core/http/index.ts @@ -0,0 +1,3 @@ +export * from './middlewares'; +export * from './types'; + diff --git a/packages/core/http/middlewares/index.ts b/packages/core/http/middlewares/index.ts new file mode 100644 index 000000000..0eb980275 --- /dev/null +++ b/packages/core/http/middlewares/index.ts @@ -0,0 +1,2 @@ +export * from './request.middleware'; +export * from './response.middleware'; diff --git a/packages/core/http/types/index.ts b/packages/core/http/types/index.ts new file mode 100644 index 000000000..f4a9b30cc --- /dev/null +++ b/packages/core/http/types/index.ts @@ -0,0 +1,2 @@ +export * from './api.types'; +export * from './primitive.types'; diff --git a/packages/core/index.ts b/packages/core/index.ts index 2f1f613c8..d40174e54 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -1,5 +1,7 @@ -// export * from './cache'; -// export * from './controllers'; -// export * from './database'; -// export * from './entityMapper'; -// export * from './services'; +export * from './cache'; +export * from './controllers'; +export * from './database'; +export * from './entityMapper'; +export * from './http'; +export * from './services'; + diff --git a/packages/core/services/index.ts b/packages/core/services/index.ts new file mode 100644 index 000000000..d7b26c652 --- /dev/null +++ b/packages/core/services/index.ts @@ -0,0 +1 @@ +export * from './interfaces/baseService'; diff --git a/packages/express/package.json b/packages/express/package.json new file mode 100644 index 000000000..1bea2c2b2 --- /dev/null +++ b/packages/express/package.json @@ -0,0 +1,19 @@ +{ + "name": "@forklaunch/express", + "version": "0.1.0", + "description": "Forklaunch framework for express.", + "main": "index.js", + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/forklaunch/forklaunch-js.git" + }, + "author": "Rohin Bhargava", + "license": "MIT", + "bugs": { + "url": "https://github.com/forklaunch/forklaunch-js/issues" + }, + "homepage": "https://github.com/forklaunch/forklaunch-js#readme" +} diff --git a/packages/hyper-express/package.json b/packages/hyper-express/package.json new file mode 100644 index 000000000..ea63cbc4b --- /dev/null +++ b/packages/hyper-express/package.json @@ -0,0 +1,19 @@ +{ + "name": "@forklaunch/hyper-express", + "version": "0.1.0", + "description": "Forklaunch framework for hyper-express.", + "main": "index.js", + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/forklaunch/forklaunch-js.git" + }, + "author": "Rohin Bhargava", + "license": "MIT", + "bugs": { + "url": "https://github.com/forklaunch/forklaunch-js/issues" + }, + "homepage": "https://github.com/forklaunch/forklaunch-js#readme" +} diff --git a/packages/validator/index.ts b/packages/validator/index.ts index 22995206e..fab3468cf 100644 --- a/packages/validator/index.ts +++ b/packages/validator/index.ts @@ -118,4 +118,6 @@ export type ValidSchemaObject = SchemaObject | IdiomaticSchema>, SV extends AnySchemaValidator> = SchemaPrettify, SV>; \ No newline at end of file +export type Schema | IdiomaticSchema>, SV extends AnySchemaValidator> = SchemaPrettify, SV>; + +export * from "./interfaces"; diff --git a/packages/validator/interfaces/index.ts b/packages/validator/interfaces/index.ts new file mode 100644 index 000000000..087626458 --- /dev/null +++ b/packages/validator/interfaces/index.ts @@ -0,0 +1 @@ +export * from './schemaValidator.interfaces'; diff --git a/packages/validator/package-lock.json b/packages/validator/package-lock.json index f4e24653c..113eef776 100644 --- a/packages/validator/package-lock.json +++ b/packages/validator/package-lock.json @@ -1,12 +1,12 @@ { "name": "@forklaunch/validator", - "version": "0.1.13", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@forklaunch/validator", - "version": "0.1.13", + "version": "0.2.2", "license": "MIT", "dependencies": { "@anatine/zod-openapi": "^2.2.6", @@ -16,6 +16,7 @@ }, "devDependencies": { "@eslint/js": "^9.6.0", + "@types/jest": "^29.5.12", "globals": "^15.7.0", "openapi3-ts": "^4.3.3", "ts-jest": "^29.1.5", @@ -54,7 +55,6 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, - "peer": true, "dependencies": { "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" @@ -263,7 +263,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -297,7 +296,6 @@ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", @@ -313,7 +311,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -326,7 +323,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -341,7 +337,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -350,15 +345,13 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "peer": true, "engines": { "node": ">=0.8.0" } @@ -368,7 +361,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -378,7 +370,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -958,7 +949,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "peer": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -1367,6 +1357,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/node": { "version": "20.14.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", @@ -1380,8 +1380,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/yargs": { "version": "17.0.32", @@ -2121,7 +2120,6 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -2203,7 +2201,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -2531,7 +2528,6 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "peer": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -3295,7 +3291,6 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -3359,7 +3354,6 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -3409,7 +3403,6 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -3425,7 +3418,6 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -3735,8 +3727,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/js-yaml": { "version": "3.14.1", @@ -4227,8 +4218,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true, - "peer": true + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -4280,7 +4270,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -4295,7 +4284,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -4368,8 +4356,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/require-directory": { "version": "2.1.1", @@ -4571,7 +4558,6 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, diff --git a/packages/validator/package.json b/packages/validator/package.json index fed287dcd..d24176b61 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@forklaunch/validator", - "version": "0.2.1", + "version": "0.2.2", "description": "Schema validator for ForkLaunch components.", "files": [ "dist" @@ -33,6 +33,7 @@ }, "devDependencies": { "@eslint/js": "^9.6.0", + "@types/jest": "^29.5.12", "globals": "^15.7.0", "openapi3-ts": "^4.3.3", "ts-jest": "^29.1.5", diff --git a/packages/validator/tests/typebox/equality.test.ts b/packages/validator/tests/typebox/equality.test.ts index 213a29ce1..971913ee5 100644 --- a/packages/validator/tests/typebox/equality.test.ts +++ b/packages/validator/tests/typebox/equality.test.ts @@ -1,5 +1,7 @@ +import { TObject, Type } from "@sinclair/typebox" import { Schema } from "../../index" -import { TypeboxSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, schemify, string, symbol, union } from "../../typebox/index" +import { TypeboxSchemaValidator, array, bigint, boolean, date, empty, never, number, openapi, optional, schemify, string, symbol, union, validate } from "../../typebox/index" +import { UnboxedTObjectSchema } from "../../typebox/types/typebox.schema.types" const one = array({ name: { @@ -119,4 +121,142 @@ type ShortExpected = { non: number; } assert, ShortExpected>>(); -assert, Schema>>(); \ No newline at end of file +assert, Schema>>(); + +describe('Typebox Equality Tests', () => { + let schema: UnboxedTObjectSchema + let schemified: TObject + let expectedSchema: TObject + + beforeAll(() => { + schema = { + hello: { + world: string + }, + foo: { + bar: number + } + } + schemified = schemify(schema); + expectedSchema = Type.Object({ + hello: Type.Object({ + world: Type.String() + }), + foo: Type.Object({ + bar: Type.Number() + }) + }); + }); + + test('Schema Equality', async () => { + expect(schemified).toEqual(expectedSchema); + + expect(schemified).toEqual(schemify({ + hello: { + world: string + }, + foo: { + bar: number + } + })); + expect(schemified).toEqual(schemify({ + hello: schemify({ + world: string + }), + foo: { + bar: number + } + })); + expect(schemified).toEqual(schemify({ + hello: { + world: string + }, + foo: schemify({ + bar: number + }) + })); + expect(schemified).toEqual(schemify({ + hello: schemify({ + world: string + }), + foo: schemify({ + bar: number + }) + })); + }); + + test('Optional Schema Equality', async () => { + const unboxSchemified = optional(schema); + const boxSchemified = optional(schemified); + + const schemifiedExpected = Type.Optional(expectedSchema); + expect(unboxSchemified).toEqual(schemifiedExpected); + expect(boxSchemified).toEqual(schemifiedExpected); + }); + + test('Array Schema Equality', async () => { + const unboxSchemified = array(schema); + const boxSchemified = array(schemified); + + const schemifiedExpected = Type.Array(expectedSchema) + expect(unboxSchemified).toEqual(schemifiedExpected); + expect(boxSchemified).toEqual(schemifiedExpected); + + }); + + test('Union Schema Equality', async () => { + const unboxSchemified = union([schema, { + test: string + }]); + const unboxSchemified2 = union([schema, schemify({ + test: string + })]); + const boxSchemified1 = union([schemified, schemify({ + test: string + })]); + const boxSchemified2 = union([schemified, { + test: string + }]); + + const schemifiedExpected = Type.Union([expectedSchema, Type.Object({ + test: Type.String() + })]); + + expect(unboxSchemified).toEqual(schemifiedExpected); + expect(unboxSchemified2).toEqual(schemifiedExpected); + }); + + test('Literal Schema Equality', async () => { + const schemified = schemify({ + hello: 'world' + }); + expect(schemified).toEqual(Type.Object({ + hello: Type.Literal('world') + })); + }); + + test('Validate Schema', async () => { + expect(validate(schemified, { + hello: { + world: 'world' + }, + foo: { + bar: 42 + } + })).toBe(true); + expect(validate(schemified, { + hello: { + world: 55 + }, + foo: { + bar: 42 + } + })).toBe(false); + }); + + test('OpenAPI Conversion', async () => { + const schemified = schemify(schema); + const openApi = openapi(schemified); + expect(openApi).toEqual(schemified); + }); +}) \ No newline at end of file diff --git a/packages/validator/tests/typebox/largeSchema.test.ts b/packages/validator/tests/typebox/largeSchema.test.ts index 8655705e8..c353c3095 100644 --- a/packages/validator/tests/typebox/largeSchema.test.ts +++ b/packages/validator/tests/typebox/largeSchema.test.ts @@ -1,9 +1,9 @@ -import { Schema } from "../../index" -import { TypeboxSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, string, symbol, union } from "../../typebox/index" +import { Schema } from "../../index"; +import { TypeboxSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, string, symbol, union } from "../../typebox/index"; -const deepOne = { - s: { - s: { +describe("Typebox Large Schema Tests", () => { + it("Deep Union", () => { + const deepOne = { s: { s: { s: { @@ -11,12 +11,16 @@ const deepOne = { s: { s: { s: { - s:{ + s: { s: { - s: { + s:{ s: { s: { - b: "number" as const + s: { + s: { + b: "number" as const + } + } } } } @@ -30,27 +34,27 @@ const deepOne = { } } } - } -} - - -const deepTwo = { - k: { - o: number, - s: { - s: { + + + const deepTwo = { + k: { + o: number, s: { s: { s: { s: { s: { s: { - s:{ + s: { s: { - s: { + s:{ s: { s: { - b: string + s: { + s: { + b: string + } + } } } } @@ -64,78 +68,80 @@ const deepTwo = { } } } - } -} - -const deepUnion = union([deepOne, deepTwo]) -type DeepUnionSchema = Schema + + const deepUnion = union([deepOne, deepTwo]) + type DeepUnionSchema = Schema + }); -const realistic = array({ - level1: { - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: { - a: optional(string), - b: number, - c: { - d: string, - e: number, - f: { - g: string, - h: number, - i: { - j: string, - k: number, - l: { - m: boolean, - n: array(string), - o: optional(union([string, number])), - p: { - q: string, - r: number + it("Realistic Schema", () => { + const realistic = array({ + level1: { + name: { + j: union([string, number, date, boolean, bigint, empty, symbol, never]), + t: optional(union([array({ + y: array(number) + }), string])), + m: { + a: optional(string), + b: number, + c: { + d: string, + e: number, + f: { + g: string, + h: number, + i: { + j: string, + k: number, + l: { + m: boolean, + n: array(string), + o: optional(union([string, number])), + p: { + q: string, + r: number + } + } } } } } + }, + additionalField1: { + a: union([string, boolean, bigint, empty]), + b: optional(array(number)), + c: { + d: string, + e: number, + f: { + g: string, + h: number + } + } + }, + additionalField2: { + x: string, + y: union([string, array(boolean)]), + z: { + a: string, + b: number + } } - } - }, - additionalField1: { - a: union([string, boolean, bigint, empty]), - b: optional(array(number)), - c: { - d: string, - e: number, - f: { - g: string, - h: number + }, + code: { + 200: { + j: string + }, + 404: { + k: string } + }, + flag: { + a: true as const, + b: false as const } - }, - additionalField2: { - x: string, - y: union([string, array(boolean)]), - z: { - a: string, - b: number - } - } - }, - code: { - 200: { - j: string - }, - 404: { - k: string - } - }, - flag: { - a: true as const, - b: false as const - } -}); - -type RealisticSchema = Schema \ No newline at end of file + }); + + type RealisticSchema = Schema + }); +}) \ No newline at end of file diff --git a/packages/validator/tests/zod/equality.test.ts b/packages/validator/tests/zod/equality.test.ts index 2a71f5a97..787f78ad1 100644 --- a/packages/validator/tests/zod/equality.test.ts +++ b/packages/validator/tests/zod/equality.test.ts @@ -1,5 +1,9 @@ +import { generateSchema } from "@anatine/zod-openapi" +import { ZodObject, z } from "zod" import { Schema } from "../../index" -import { ZodSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, schemify, string, symbol, union } from "../../zod" +import { UnboxedObjectSchema } from "../../types/schema.types" +import { ZodSchemaValidator, array, bigint, boolean, date, empty, never, number, openapi, optional, schemify, string, symbol, union, validate } from "../../zod" +import { ZodCatchall, ZodObjectShape } from "../../zod/types/zod.schema.types" const one = array({ name: { @@ -118,4 +122,146 @@ type ShortExpected = { non: number; } assert, ShortExpected>>(); -assert, Schema>>(); \ No newline at end of file +assert, Schema>>(); + +const compareSchemas = (schema1: ZodCatchall, schema2: ZodCatchall) => { + return JSON.stringify(schema1) === JSON.stringify(schema2); + }; + +describe('Zod Equality Tests', () => { + let schema: UnboxedObjectSchema + let schemified: ZodObject + let expectedSchema: ZodObject + + beforeAll(() => { + schema = { + hello: { + world: string + }, + foo: { + bar: number + } + } + schemified = schemify(schema); + expectedSchema = z.object({ + hello: z.object({ + world: z.string() + }), + foo: z.object({ + bar: z.number() + }) + }); + }); + + test('Schema Equality', async () => { + expect(compareSchemas(schemified, expectedSchema)).toBe(true); + + expect(compareSchemas(schemified, schemify({ + hello: { + world: string + }, + foo: { + bar: number + } + }))).toBe(true); + expect(compareSchemas(schemified, schemify({ + hello: schemify({ + world: string + }), + foo: { + bar: number + } + }))).toBe(true); + expect(compareSchemas(schemified, schemify({ + hello: { + world: string + }, + foo: schemify({ + bar: number + }) + }))).toBe(true); + expect(compareSchemas(schemified, schemify({ + hello: schemify({ + world: string + }), + foo: schemify({ + bar: number + }) + }))).toBe(true); + }); + + test('Optional Schema Equality', async () => { + const unboxSchemified = optional(schema); + const boxSchemified = optional(schemified); + + const schemifiedExpected = z.optional(expectedSchema); + expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); + expect(compareSchemas(boxSchemified, schemifiedExpected)).toBe(true); + }); + + test('Array Schema Equality', async () => { + const unboxSchemified = array(schema); + const boxSchemified = array(schemified); + + const schemifiedExpected = z.array(expectedSchema) + expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); + expect(compareSchemas(boxSchemified, schemifiedExpected)).toBe(true); + + }); + + test('Union Schema Equality', async () => { + const unboxSchemified = union([schema, { + test: string + }]); + const unboxSchemified2 = union([schema, schemify({ + test: string + })]); + const boxSchemified1 = union([schemified, schemify({ + test: string + })]); + const boxSchemified2 = union([schemified, { + test: string + }]); + + const schemifiedExpected = z.union([expectedSchema, z.object({ + test: z.string() + })]); + + expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); + expect(compareSchemas(unboxSchemified2, schemifiedExpected)).toBe(true); + }); + + test('Literal Schema Equality', async () => { + const schemified = schemify({ + hello: 'world' + }); + expect(compareSchemas(schemified, z.object({ + hello: z.literal('world') + }))).toBe(true); + }); + + test('Validate Schema', async () => { + expect(validate(schemified, { + hello: { + world: 'world' + }, + foo: { + bar: 42 + } + })).toBe(true); + expect(validate(schemified, { + hello: { + world: 55 + }, + foo: { + bar: 42 + } + })).toBe(false); + }); + + test('OpenAPI Conversion', async () => { + const schemified = schemify(schema); + const openApi = openapi(schemified); + expect(openApi).toEqual(generateSchema(schemified)); + }); +}) \ No newline at end of file diff --git a/packages/validator/tests/zod/largeSchema.test.ts b/packages/validator/tests/zod/largeSchema.test.ts index 6de36fecc..6b5e52fab 100644 --- a/packages/validator/tests/zod/largeSchema.test.ts +++ b/packages/validator/tests/zod/largeSchema.test.ts @@ -1,9 +1,9 @@ -import { Schema } from "../../index" -import { ZodSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, string, symbol, union } from "../../zod/index" +import { Schema } from "../../index"; +import { ZodSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, string, symbol, union } from "../../zod/index"; -const deepOne = { - s: { - s: { +describe('Zod Large Schema Tests', () => { + it ('Deep Union', async () => { + const deepOne = { s: { s: { s: { @@ -11,12 +11,16 @@ const deepOne = { s: { s: { s: { - s:{ + s: { s: { - s: { + s:{ s: { s: { - b: "number" as const + s: { + s: { + b: "number" as const + } + } } } } @@ -30,27 +34,27 @@ const deepOne = { } } } - } -} - - -const deepTwo = { - k: { - o: number, - s: { - s: { + + + const deepTwo = { + k: { + o: number, s: { s: { s: { s: { s: { s: { - s:{ + s: { s: { - s: { + s:{ s: { s: { - b: string + s: { + s: { + b: string + } + } } } } @@ -64,78 +68,80 @@ const deepTwo = { } } } - } -} - -const deepUnion = union([deepOne, deepTwo]) -type DeepUnionSchema = Schema + + const deepUnion = union([deepOne, deepTwo]) + type DeepUnionSchema = Schema + }); -const realistic = array({ - level1: { - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: { - a: optional(string), - b: number, - c: { - d: string, - e: number, - f: { - g: string, - h: number, - i: { - j: string, - k: number, - l: { - m: boolean, - n: array(string), - o: optional(union([string, number])), - p: { - q: string, - r: number + it('Realistic Schema', async () => { + const realistic = array({ + level1: { + name: { + j: union([string, number, date, boolean, bigint, empty, symbol, never]), + t: optional(union([array({ + y: array(number) + }), string])), + m: { + a: optional(string), + b: number, + c: { + d: string, + e: number, + f: { + g: string, + h: number, + i: { + j: string, + k: number, + l: { + m: boolean, + n: array(string), + o: optional(union([string, number])), + p: { + q: string, + r: number + } + } } } } } + }, + additionalField1: { + a: union([string, boolean, bigint, empty]), + b: optional(array(number)), + c: { + d: string, + e: number, + f: { + g: string, + h: number + } + } + }, + additionalField2: { + x: string, + y: union([string, array(boolean)]), + z: { + a: string, + b: number + } } - } - }, - additionalField1: { - a: union([string, boolean, bigint, empty]), - b: optional(array(number)), - c: { - d: string, - e: number, - f: { - g: string, - h: number + }, + code: { + 200: { + j: string + }, + 404: { + k: string } + }, + flag: { + a: true as const, + b: false as const } - }, - additionalField2: { - x: string, - y: union([string, array(boolean)]), - z: { - a: string, - b: number - } - } - }, - code: { - 200: { - j: string - }, - 404: { - k: string - } - }, - flag: { - a: true as const, - b: false as const - } -}); + }); -type RealisticSchema = Schema \ No newline at end of file + type RealisticSchema = Schema + }); +}); \ No newline at end of file diff --git a/packages/validator/typebox/index.ts b/packages/validator/typebox/index.ts index 1f5ec2a7e..bfa50d873 100644 --- a/packages/validator/typebox/index.ts +++ b/packages/validator/typebox/index.ts @@ -51,8 +51,8 @@ export class TypeboxSchemaValidator implements SchemaValidator< if (typeof schema[key] === 'object' && Kind in (schema[key] as TSchema)) { newSchema[key] = schema[key] as TSchema; } else { - const scheme = this.schemify(schema[key]); - newSchema[key] = scheme; + const schemified = this.schemify(schema[key]); + newSchema[key] = schemified; } }); @@ -68,8 +68,8 @@ export class TypeboxSchemaValidator implements SchemaValidator< if (Kind in (schema as TSchema)) { return Type.Optional(schema as TSchema) as TOptional>; } - const scheme = this.schemify(schema); - return Type.Optional(scheme) as TOptional>; + const schemified = this.schemify(schema); + return Type.Optional(schemified) as TOptional>; } /** @@ -81,8 +81,8 @@ export class TypeboxSchemaValidator implements SchemaValidator< if (Kind in (schema as TSchema)) { return Type.Array(schema as TSchema) as TArray>; } - const scheme = this.schemify(schema); - return Type.Array(scheme) as TArray>; + const schemified = this.schemify(schema); + return Type.Array(schemified) as TArray>; } /** @@ -119,8 +119,12 @@ export class TypeboxSchemaValidator implements SchemaValidator< * @param {unknown} value - The value to validate. * @returns {boolean} True if valid, otherwise false. */ - validate(schema: T, value: unknown): boolean { - return Value.Check(schema, value); + validate(schema: T, value: unknown): boolean { + if (Kind in (schema as TSchema)) { + return Value.Check(schema as TSchema, value); + } + const schemified = this.schemify(schema); + return Value.Check(schemified, value); } /** diff --git a/packages/validator/zod/index.ts b/packages/validator/zod/index.ts index 7708dcc70..5801f1f30 100644 --- a/packages/validator/zod/index.ts +++ b/packages/validator/zod/index.ts @@ -118,8 +118,7 @@ export class ZodSchemaValidator implements SchemaValidator< * @returns {boolean} True if valid, otherwise false. */ validate(schema: T, value: unknown): boolean { - schema.parse(value); - return true; + return schema.safeParse(value).success; } /** From 73b6ee1c4df4af446f142375066a95dfeae08228 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 14:10:11 -0500 Subject: [PATCH 02/13] redis tests and schema validator mock, slight refactor --- packages/core/cache/redisTtlCache.ts | 8 +- packages/core/package-lock.json | 961 +++++++++++++++++- packages/core/package.json | 3 +- .../{dto.test.ts => entityMapper.test.ts} | 0 packages/core/tests/http.middleware.test.ts | 10 + packages/core/tests/redisTtlCache.test.ts | 55 +- packages/validator/index.ts | 7 +- .../interfaces/schemaValidator.interfaces.ts | 31 +- .../validator/tests/mockSchemaValidator.ts | 38 + packages/validator/typebox/index.ts | 15 +- packages/validator/zod/index.ts | 10 +- 11 files changed, 1072 insertions(+), 66 deletions(-) rename packages/core/tests/{dto.test.ts => entityMapper.test.ts} (100%) create mode 100644 packages/core/tests/http.middleware.test.ts create mode 100644 packages/validator/tests/mockSchemaValidator.ts diff --git a/packages/core/cache/redisTtlCache.ts b/packages/core/cache/redisTtlCache.ts index a00bd39f2..ad4863da1 100644 --- a/packages/core/cache/redisTtlCache.ts +++ b/packages/core/cache/redisTtlCache.ts @@ -1,4 +1,4 @@ -import { createClient } from 'redis'; +import { RedisClientOptions, createClient } from 'redis'; import { TtlCache } from './interfaces/ttlCache.interface'; import { TtlCacheRecord } from './types/ttlCacheRecord.types'; @@ -14,12 +14,10 @@ export class RedisTtlCache implements TtlCache { * * @param {number} ttlMilliseconds - The default TTL in milliseconds. */ - constructor(private ttlMilliseconds: number) { + constructor(private ttlMilliseconds: number, hostingOptions?: RedisClientOptions) { // Connects to localhost:6379 by default // url usage: redis[s]://[[username][:password]@][host][:port][/db-number] - this.client = createClient({ - url: process.env.REDIS_URL - }); + this.client = createClient(hostingOptions); this.client.on('error', (err) => console.log('Redis Client Error', err)); this.client.on('connect', () => { console.log('\x1b[32m%s\x1b[0m', 'Successfully Connected to Redis'); // Green text diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index e2eb81cea..f54b5950d 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.1", "license": "MIT", "dependencies": { - "@forklaunch/validator": "^0.2.1", + "@forklaunch/validator": "^0.2.2", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", @@ -23,6 +23,7 @@ "@types/redis": "^4.0.11", "@types/uuid": "^10.0.0", "globals": "^15.8.0", + "testcontainers": "^10.10.1", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typedoc": "^0.26.3", @@ -649,6 +650,12 @@ "node": ">=6.9.0" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "dev": true + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -790,9 +797,9 @@ "integrity": "sha512-ThzqAO97Hk5PZYjtDyokoQFG7Ktq5Kjbyr3zRP4LslzOxe+wMPcbrm3wiQDabV2liQR/BZYXYi5m3RkmxlmaeA==" }, "node_modules/@forklaunch/validator": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.1.tgz", - "integrity": "sha512-62/xh2qBh4XZSbsTZiUAEcXR8pg9djfDFOQYHqLG/ew3Jds8OO71Nliq5c0o2HzhCVHTxys9/9ZcQacr6ga8XA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.2.tgz", + "integrity": "sha512-oKMw4J9QyJS9Z6T24maE6He+f57zLeCJaB/IDD/rBzUjo3xqnRFpwNwgiRk9zcJwc+Ke2EW6AzyotbltHAtvEA==", "dependencies": { "@anatine/zod-openapi": "^2.2.6", "@forklaunch/common": "^0.1.2", @@ -1416,6 +1423,27 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.29", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.29.tgz", + "integrity": "sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==", + "dev": true, + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1485,6 +1513,33 @@ "redis": "*" } }, + "node_modules/@types/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2-streams": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz", + "integrity": "sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", + "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1736,6 +1791,75 @@ "node": ">= 8" } }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1760,6 +1884,33 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -1879,12 +2030,97 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1956,6 +2192,39 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1963,6 +2232,25 @@ "dev": true, "peer": true }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2030,6 +2318,12 @@ "node": ">=10" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2111,12 +2405,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/convert-source-map": { "version": "2.0.0", @@ -2125,6 +2433,52 @@ "dev": true, "peer": true }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2261,6 +2615,59 @@ "node": ">=8" } }, + "node_modules/docker-compose": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", + "dev": true, + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/docker-modem": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", + "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.11.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", + "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", + "dev": true, + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^3.0.0", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode/node_modules/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2312,6 +2719,15 @@ "dev": true, "peer": true }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2705,6 +3121,12 @@ "dev": true, "peer": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -2811,6 +3233,12 @@ "dev": true, "peer": true }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -2828,8 +3256,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -2894,6 +3321,18 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -2913,7 +3352,6 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3021,6 +3459,26 @@ "node": ">=10.17.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3092,7 +3550,6 @@ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "peer": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3102,8 +3559,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/is-arrayish": { "version": "0.2.1", @@ -3198,6 +3654,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3992,6 +4454,48 @@ "node": ">=6" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -4045,6 +4549,30 @@ "node": ">=8" } }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -4058,6 +4586,12 @@ "dev": true, "peer": true }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4205,7 +4739,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4213,18 +4746,63 @@ "node": "*" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "dev": true, + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4244,7 +4822,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4267,7 +4844,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "peer": true, "dependencies": { "wrappy": "1" } @@ -4417,7 +4993,6 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4523,6 +5098,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -4537,6 +5118,43 @@ "node": ">= 6" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/properties-reader": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/properties-reader/-/properties-reader-2.3.0.tgz", + "integrity": "sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/properties?sponsor=1" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4592,12 +5210,62 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/redis": { "version": "4.6.14", "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.14.tgz", @@ -4677,6 +5345,15 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4725,6 +5402,32 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4771,8 +5474,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/sisteransi": { "version": "1.0.5", @@ -4810,6 +5512,12 @@ "source-map": "^0.6.0" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4817,6 +5525,44 @@ "dev": true, "peer": true }, + "node_modules/ssh-remote-port-forward": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", + "integrity": "sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==", + "dev": true, + "dependencies": { + "@types/ssh2": "^0.5.48", + "ssh2": "^1.4.0" + } + }, + "node_modules/ssh-remote-port-forward/node_modules/@types/ssh2": { + "version": "0.5.52", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", + "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2-streams": "*" + } + }, + "node_modules/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.9", + "nan": "^2.18.0" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -4829,6 +5575,29 @@ "node": ">=10" } }, + "node_modules/streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -4929,6 +5698,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -4944,6 +5754,38 @@ "node": ">=8" } }, + "node_modules/testcontainers": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.10.1.tgz", + "integrity": "sha512-bw86BLq2/ljJ/gLg3PBsyhYOoDBkyo87/MnpWLavYTAyWR7feGFnAA87qRLq2SSCOSFLHsgCA8+u7Eg+opSmTQ==", + "dev": true, + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@types/dockerode": "^3.3.29", + "archiver": "^5.3.2", + "async-lock": "^1.4.1", + "byline": "^5.0.0", + "debug": "^4.3.5", + "docker-compose": "^0.24.8", + "dockerode": "^3.3.5", + "get-port": "^5.1.1", + "node-fetch": "^2.7.0", + "proper-lockfile": "^4.1.2", + "properties-reader": "^2.3.0", + "ssh-remote-port-forward": "^1.0.4", + "tar-fs": "^3.0.6", + "tmp": "^0.2.3" + } + }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4951,6 +5793,15 @@ "dev": true, "peer": true }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -4979,6 +5830,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5101,6 +5958,12 @@ } } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5393,6 +6256,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -5436,6 +6305,22 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5484,8 +6369,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -5577,6 +6461,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index e7e89108c..fdd0f8eb0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,7 +21,7 @@ }, "homepage": "https://github.com/forklaunch/forklaunch-js#readme", "dependencies": { - "@forklaunch/validator": "^0.2.1", + "@forklaunch/validator": "^0.2.2", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", @@ -35,6 +35,7 @@ "@types/redis": "^4.0.11", "@types/uuid": "^10.0.0", "globals": "^15.8.0", + "testcontainers": "^10.10.1", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typedoc": "^0.26.3", diff --git a/packages/core/tests/dto.test.ts b/packages/core/tests/entityMapper.test.ts similarity index 100% rename from packages/core/tests/dto.test.ts rename to packages/core/tests/entityMapper.test.ts diff --git a/packages/core/tests/http.middleware.test.ts b/packages/core/tests/http.middleware.test.ts new file mode 100644 index 000000000..20676ec0c --- /dev/null +++ b/packages/core/tests/http.middleware.test.ts @@ -0,0 +1,10 @@ +// import { HttpContractDetails } from "../http"; + +// // describe('Http Middleware Tests', () => { +// // let contractDetails: HttpContractDetails +// // beforeAll(() => { +// // contractDetails = { + +// // } +// // ]); +// // }); \ No newline at end of file diff --git a/packages/core/tests/redisTtlCache.test.ts b/packages/core/tests/redisTtlCache.test.ts index 37ed22554..0f96b604c 100644 --- a/packages/core/tests/redisTtlCache.test.ts +++ b/packages/core/tests/redisTtlCache.test.ts @@ -1,42 +1,63 @@ +import { GenericContainer, StartedTestContainer } from 'testcontainers'; import { RedisTtlCache } from '../cache/redisTtlCache'; describe('RedisTtlCache', () => { + let container: StartedTestContainer; let cache: RedisTtlCache; + let key: string; + let value: unknown; + let ttlMilliseconds: number; + + beforeAll(async () => { + container = await new GenericContainer("redis") + .withExposedPorts(6379) + .start(); + + cache = new RedisTtlCache(5000, { + url: `redis://${container.getHost()}:${container.getMappedPort(6379)}` + }); + + key = 'testKey'; + value = { data: 'testValue' }; + ttlMilliseconds = 1000; + }, 30000); - beforeAll(() => { - // Mock the Redis client - // Override the RedisTtlCache's client with the mock client - cache = new RedisTtlCache(5000); - }); afterAll(async () => { - // Ensure the Redis client is disconnected after tests complete await cache.disconnect(); + await container.stop(); }); - test('putRecord and readRecord', async () => { - const key = 'testKey'; - const value = { data: 'testValue' }; - const ttlMilliseconds = 10000; // 10 seconds - + it('PutRecord', async () => { await cache.putRecord({ key, value, ttlMilliseconds }); + }); + + test('Read Record', async () => { const storedValue = await cache.readRecord(key); - expect(storedValue).toEqual(value); - }); + expect(storedValue).toEqual({ + key, + ttlMilliseconds, + value + }); + }) - test('peekRecord', async () => { - const key = 'testKey'; + test('Peek Record', async () => { const exists = await cache.peekRecord(key); expect(exists).toBeTruthy(); }); - test('deleteRecord', async () => { - const key = 'testKey'; + test('Delete Record', async () => { await cache.deleteRecord(key); const existsAfterDelete = await cache.peekRecord(key); expect(existsAfterDelete).toBeFalsy(); }); + + test('Check No Record', async () => { + await Promise.resolve(setTimeout(async () => {}, ttlMilliseconds)); + const existsAfterTtl = await cache.peekRecord(key); + expect(existsAfterTtl).toBeFalsy(); + }); }); \ No newline at end of file diff --git a/packages/validator/index.ts b/packages/validator/index.ts index fab3468cf..adf8304ca 100644 --- a/packages/validator/index.ts +++ b/packages/validator/index.ts @@ -6,6 +6,7 @@ */ import { Prettify } from "@forklaunch/common"; +import { SchemaValidator } from "./interfaces/schemaValidator.interfaces"; import { TypeboxSchemaValidator } from "./typebox"; import { TCatchall, TObject, TObjectShape, TOuterArray, TResolve, TSchemaTranslate } from "./typebox/types/typebox.schema.types"; import { IdiomaticSchema } from "./types/schema.types"; @@ -13,10 +14,10 @@ import { ZodSchemaValidator } from "./zod"; import { ZodCatchall, ZodObject, ZodObjectShape, ZodOuterArray, ZodResolve, ZodSchemaTranslate } from "./zod/types/zod.schema.types"; /** - * Interface representing any schema validator. - * Extends the SchemaValidator interface with any schema types. + * Interface representing unknown schema validator. + * Extends the SchemaValidator interface with unknown schema types. */ -export type AnySchemaValidator = TypeboxSchemaValidator | ZodSchemaValidator; +export type AnySchemaValidator = SchemaValidator; /** * Type alias for a schema object shape. diff --git a/packages/validator/interfaces/schemaValidator.interfaces.ts b/packages/validator/interfaces/schemaValidator.interfaces.ts index aeaffafe1..3e318d8e4 100644 --- a/packages/validator/interfaces/schemaValidator.interfaces.ts +++ b/packages/validator/interfaces/schemaValidator.interfaces.ts @@ -5,14 +5,19 @@ import { LiteralSchema } from "../types/schema.types"; * Interface representing a schema validator. * * @template UnionContainer - The type for union schemas. - * @template IdiomaticSchema - The type for idiomatic schemas. + * @template IdiomaticSchema - The type for idiomatic schemas. * @template Catchall - The catch-all type for all schemas. */ export interface SchemaValidator< - UnionContainer = unknown, - IdiomaticSchema = unknown, - Catchall = unknown + SchematicFunction = (schema: T) => unknown, + OptionalFunction =(schema: T) => unknown, + ArrayFunction = (schema: T) => unknown, + UnionFunction = (schemas: T[]) => unknown, + LiteralFunction = (schema: T) => unknown, + ValidationFunction = (schema: T) => unknown, + OpenAPIFunction = (schema: T) => SchemaObject > { + /** * Validator for string type. */ @@ -70,7 +75,7 @@ export interface SchemaValidator< * @param {T} schema - The schema to schemify. * @returns {unknown} - The schemified form of the schema. */ - schemify(schema: T): unknown; + schemify: SchematicFunction; /** * Converts a schema into an optional schema. @@ -79,7 +84,7 @@ export interface SchemaValidator< * @param {T} schema - The schema to make optional. * @returns {unknown} - The optional form of the schema. */ - optional(schema: T): unknown; + optional: OptionalFunction; /** * Converts a schema into an array schema. @@ -88,7 +93,7 @@ export interface SchemaValidator< * @param {T} schema - The schema to convert into an array. * @returns {unknown} - The array form of the schema. */ - array(schema: T): unknown; + array: ArrayFunction; /** * Converts multiple schemas into a union schema. @@ -97,7 +102,9 @@ export interface SchemaValidator< * @param {T} schemas - The schemas to unionize. * @returns {unknown} - The union form of the schemas. */ - union(schemas: T): unknown; + // union(schemas: T): unknown; + union: UnionFunction; + /** * Creates a literal schema from a value. @@ -106,7 +113,7 @@ export interface SchemaValidator< * @param {T} value - The literal value. * @returns {unknown} - The literal schema. */ - literal(value: T): unknown; + literal: LiteralFunction; /** * Validates a value against a schema. @@ -116,7 +123,7 @@ export interface SchemaValidator< * @param {unknown} value - The value to validate. * @returns {boolean} - Whether the value is valid according to the schema. */ - validate(schema: T, value: unknown): boolean; + validate: ValidationFunction; /** * Converts a schema into an OpenAPI schema object. @@ -125,5 +132,5 @@ export interface SchemaValidator< * @param {T} schema - The schema to convert. * @returns {SchemaObject} - The OpenAPI schema object. */ - openapi(schema: T): SchemaObject; -} + openapi: OpenAPIFunction; +} \ No newline at end of file diff --git a/packages/validator/tests/mockSchemaValidator.ts b/packages/validator/tests/mockSchemaValidator.ts new file mode 100644 index 000000000..8e028a321 --- /dev/null +++ b/packages/validator/tests/mockSchemaValidator.ts @@ -0,0 +1,38 @@ +import { SchemaValidator } from "../index"; +import { LiteralSchema } from "../types/schema.types"; + + +export class MockSchemaValidator implements SchemaValidator { + string = 'string'; + number = 'number'; + bigint = 'bigint'; + boolean = 'boolean'; + date = 'date'; + symbol = 'symbol'; + empty = 'empty'; + any = 'any'; + unknown = 'unknown'; + never = 'never'; + + schemify(schema: T) { + return schema; + }; + optional(schema: T) { + return 'optional ' + schema; + }; + array(schema: T) { + return 'array ' + schema; + } + union(schemas: T[]) { + return schemas.join(' | '); + } + literal(schema: T) { + return 'literal ' + schema; + }; + validate(schema: T) { + return true; + }; + openapi(schema: T) { + return {}; + } +} \ No newline at end of file diff --git a/packages/validator/typebox/index.ts b/packages/validator/typebox/index.ts index bfa50d873..defc82ae4 100644 --- a/packages/validator/typebox/index.ts +++ b/packages/validator/typebox/index.ts @@ -9,17 +9,24 @@ import { Kind, TArray, TLiteral, TOptional, TSchema, TUnion, Type } from '@sincl import { Value } from '@sinclair/typebox/value'; import { SchemaObject } from 'openapi3-ts/oas31'; import { SchemaValidator } from '../interfaces/schemaValidator.interfaces'; -import { LiteralSchema } from '../types/schema.types'; +import { IdiomaticSchema, LiteralSchema } from '../types/schema.types'; import { TIdiomaticSchema, TObjectShape, TResolve, TUnionContainer, UnionTResolve } from './types/typebox.schema.types'; +type U = TIdiomaticSchema extends IdiomaticSchema ? true : false; +type M = TUnionContainer extends Array> ? true : false; + /** * Class representing a TypeBox schema definition. * @implements {SchemaValidator} */ export class TypeboxSchemaValidator implements SchemaValidator< - TUnionContainer, - TIdiomaticSchema, - TSchema + (schema: T) => TResolve, + (schema: T) => TOptional>, + (schema: T) => TArray>, + (schemas: T) => TUnion>, + (value: T) => TLiteral, + (schema: T, value: unknown) => boolean, + (schema: T) => SchemaObject > { string = Type.String(); number = Type.Number(); diff --git a/packages/validator/zod/index.ts b/packages/validator/zod/index.ts index 5801f1f30..aedf635f4 100644 --- a/packages/validator/zod/index.ts +++ b/packages/validator/zod/index.ts @@ -17,9 +17,13 @@ import { UnionZodResolve, ZodCatchall, ZodIdiomaticSchema, ZodResolve, ZodUnionC * @implements {SchemaValidator} */ export class ZodSchemaValidator implements SchemaValidator< - ZodUnionContainer, - ZodIdiomaticSchema, - ZodCatchall + (schema: T) => ZodResolve, + (schema: T) => ZodOptional>, + (schema: T) => ZodArray>, + (schemas: T) => ZodUnion>, + (value: T) => ZodLiteral>, + (schema: T, value: unknown) => boolean, + (schema: T) => SchemaObject > { string = z.string(); number = z.number(); From d3ef8fb0fe8f51a4d7432846c5a90299ddf86eff Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 14:15:15 -0500 Subject: [PATCH 03/13] package updates --- packages/core/package.json | 2 +- packages/validator/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index fdd0f8eb0..6edc2fb41 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,7 +21,7 @@ }, "homepage": "https://github.com/forklaunch/forklaunch-js#readme", "dependencies": { - "@forklaunch/validator": "^0.2.2", + "@forklaunch/validator": "^0.2.3", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", diff --git a/packages/validator/package.json b/packages/validator/package.json index d24176b61..c8e30129c 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@forklaunch/validator", - "version": "0.2.2", + "version": "0.2.3", "description": "Schema validator for ForkLaunch components.", "files": [ "dist" From 76ce0f30eadbd044262c93074b9fb173bd244eee Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 14:20:34 -0500 Subject: [PATCH 04/13] add export mock schema validator --- packages/core/package-lock.json | 8 ++++---- packages/core/tests/http.middleware.test.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index f54b5950d..6060bc58c 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.1", "license": "MIT", "dependencies": { - "@forklaunch/validator": "^0.2.2", + "@forklaunch/validator": "^0.2.3", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", @@ -797,9 +797,9 @@ "integrity": "sha512-ThzqAO97Hk5PZYjtDyokoQFG7Ktq5Kjbyr3zRP4LslzOxe+wMPcbrm3wiQDabV2liQR/BZYXYi5m3RkmxlmaeA==" }, "node_modules/@forklaunch/validator": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.2.tgz", - "integrity": "sha512-oKMw4J9QyJS9Z6T24maE6He+f57zLeCJaB/IDD/rBzUjo3xqnRFpwNwgiRk9zcJwc+Ke2EW6AzyotbltHAtvEA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.3.tgz", + "integrity": "sha512-I70WWOArt9aT1tKWzCxPIejCfIT3pZm3T94z7aUT0zOuUPVBXLmTsAx9DIUB0pTmBljE8GTPigRXT2TZa3Zozw==", "dependencies": { "@anatine/zod-openapi": "^2.2.6", "@forklaunch/common": "^0.1.2", diff --git a/packages/core/tests/http.middleware.test.ts b/packages/core/tests/http.middleware.test.ts index 20676ec0c..80d99e01a 100644 --- a/packages/core/tests/http.middleware.test.ts +++ b/packages/core/tests/http.middleware.test.ts @@ -1,10 +1,10 @@ -// import { HttpContractDetails } from "../http"; +import { HttpContractDetails } from "../http"; -// // describe('Http Middleware Tests', () => { -// // let contractDetails: HttpContractDetails -// // beforeAll(() => { -// // contractDetails = { +describe('Http Middleware Tests', () => { + let contractDetails: HttpContractDetails + beforeAll(() => { + contractDetails = { -// // } -// // ]); -// // }); \ No newline at end of file + } + ]); +}); \ No newline at end of file From 7eb9c2cd0f50b71378db9d77e81221cf59524698 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 14:24:11 -0500 Subject: [PATCH 05/13] typing mismatch --- packages/core/package-lock.json | 8 ++++---- packages/core/package.json | 2 +- .../validator/interfaces/schemaValidator.interfaces.ts | 2 +- packages/validator/package.json | 6 +++++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 6060bc58c..b47f511d4 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.1", "license": "MIT", "dependencies": { - "@forklaunch/validator": "^0.2.3", + "@forklaunch/validator": "^0.2.4", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", @@ -797,9 +797,9 @@ "integrity": "sha512-ThzqAO97Hk5PZYjtDyokoQFG7Ktq5Kjbyr3zRP4LslzOxe+wMPcbrm3wiQDabV2liQR/BZYXYi5m3RkmxlmaeA==" }, "node_modules/@forklaunch/validator": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.3.tgz", - "integrity": "sha512-I70WWOArt9aT1tKWzCxPIejCfIT3pZm3T94z7aUT0zOuUPVBXLmTsAx9DIUB0pTmBljE8GTPigRXT2TZa3Zozw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.4.tgz", + "integrity": "sha512-BtUW5DTpxMXQ8HgptWIiFge0q7Dv85vDqQqH1CefAVAQbojb8gVTiPkznATKpD0QDFxb1vaYRo65UHz2axAsKQ==", "dependencies": { "@anatine/zod-openapi": "^2.2.6", "@forklaunch/common": "^0.1.2", diff --git a/packages/core/package.json b/packages/core/package.json index 6edc2fb41..34efcfaa0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,7 +21,7 @@ }, "homepage": "https://github.com/forklaunch/forklaunch-js#readme", "dependencies": { - "@forklaunch/validator": "^0.2.3", + "@forklaunch/validator": "^0.2.4", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", diff --git a/packages/validator/interfaces/schemaValidator.interfaces.ts b/packages/validator/interfaces/schemaValidator.interfaces.ts index 3e318d8e4..b45fedd3e 100644 --- a/packages/validator/interfaces/schemaValidator.interfaces.ts +++ b/packages/validator/interfaces/schemaValidator.interfaces.ts @@ -14,7 +14,7 @@ export interface SchemaValidator< ArrayFunction = (schema: T) => unknown, UnionFunction = (schemas: T[]) => unknown, LiteralFunction = (schema: T) => unknown, - ValidationFunction = (schema: T) => unknown, + ValidationFunction = (schema: T, value: unknown) => boolean, OpenAPIFunction = (schema: T) => SchemaObject > { diff --git a/packages/validator/package.json b/packages/validator/package.json index c8e30129c..eff03024b 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@forklaunch/validator", - "version": "0.2.3", + "version": "0.2.5", "description": "Schema validator for ForkLaunch components.", "files": [ "dist" @@ -68,6 +68,10 @@ "./zod/types": { "types": "./dist/zod/types/zod.schema.types.d.ts", "default": "./dist/zod/types/zod.schema.types.js" + }, + "./tests/mockSchemaValidator": { + "types": "./dist/tests/mockSchemaValidator.d.ts", + "default": "./dist/tests/mockSchemaValidator.js" } } } From 63b1f3d5de40bd5b3ee9c7f617eb7eb86bbd1abe Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 19:02:55 -0500 Subject: [PATCH 06/13] huge, impactful refactor for SchemaValidators --- packages/validator/index.ts | 125 +------------ packages/validator/package-lock.json | 20 ++- packages/validator/package.json | 1 + .../validator/tests/mockSchemaValidator.ts | 5 +- .../tests/typebox/largeSchema.test.ts | 6 +- packages/validator/tests/zod/equality.test.ts | 2 +- .../validator/tests/zod/largeSchema.test.ts | 6 +- packages/validator/typebox/index.ts | 14 +- packages/validator/types/index.ts | 1 + packages/validator/types/schema.types.ts | 168 +++++++++++++++++- packages/validator/zod/index.ts | 9 +- .../validator/zod/types/zod.schema.types.ts | 5 +- 12 files changed, 213 insertions(+), 149 deletions(-) create mode 100644 packages/validator/types/index.ts diff --git a/packages/validator/index.ts b/packages/validator/index.ts index adf8304ca..eea524d65 100644 --- a/packages/validator/index.ts +++ b/packages/validator/index.ts @@ -1,124 +1 @@ -/** - * This module provides type definitions and utilities for working with schemas using Zod and TypeBox. - * It includes type mappings and transformations for schema objects, arrays, and resolutions. - * - * @module SchemaTypes - */ - -import { Prettify } from "@forklaunch/common"; -import { SchemaValidator } from "./interfaces/schemaValidator.interfaces"; -import { TypeboxSchemaValidator } from "./typebox"; -import { TCatchall, TObject, TObjectShape, TOuterArray, TResolve, TSchemaTranslate } from "./typebox/types/typebox.schema.types"; -import { IdiomaticSchema } from "./types/schema.types"; -import { ZodSchemaValidator } from "./zod"; -import { ZodCatchall, ZodObject, ZodObjectShape, ZodOuterArray, ZodResolve, ZodSchemaTranslate } from "./zod/types/zod.schema.types"; - -/** - * Interface representing unknown schema validator. - * Extends the SchemaValidator interface with unknown schema types. - */ -export type AnySchemaValidator = SchemaValidator; - -/** - * Type alias for a schema object shape. - * Resolves to ZodObjectShape for Zod schemas and TObjectShape for TypeBox schemas. - * - * @template SV - SchemaValidator type. - */ -type SchemaObjectShape = ( - SV extends ZodSchemaValidator ? ZodObjectShape : - SV extends TypeboxSchemaValidator ? TObjectShape : - never -); - -/** - * Type alias for a schema object. - * Resolves to ZodObject for Zod schemas and TObject for TypeBox schemas. - * - * @template T - Schema object shape. - * @template SV - SchemaValidator type. - */ -type SchemaObject, SV extends AnySchemaValidator> = ( - SV extends ZodSchemaValidator ? ZodObject : - SV extends TypeboxSchemaValidator ? TObject : - never -); - -/** - * Type alias for a schema outer array. - * Resolves to ZodOuterArray for Zod schemas and TOuterArray for TypeBox schemas. - * - * @template T - Schema object. - * @template SV - SchemaValidator type. - */ -type SchemaOuterArray, SV>, SV extends AnySchemaValidator> = ( - SV extends ZodSchemaValidator ? ZodOuterArray : - SV extends TypeboxSchemaValidator ? TOuterArray : - never -); - -/** - * Type alias for resolving a schema. - * Resolves to ZodResolve for Zod schemas and TResolve for TypeBox schemas. - * - * @template T - Schema type. - * @template SV - SchemaValidator type. - */ -type SchemaResolve = ( - SV extends ZodSchemaValidator ? ZodResolve : - SV extends TypeboxSchemaValidator ? TResolve : - never -); - -/** - * Type alias for translating a schema. - * Resolves to ZodSchemaTranslate for Zod schemas and TSchemaTranslate for TypeBox schemas. - * - * @template T - Schema type. - * @template SV - SchemaValidator type. - */ -type SchemaTranslate = ( - SV extends ZodSchemaValidator ? ZodSchemaTranslate : - SV extends TypeboxSchemaValidator ? TSchemaTranslate : - never -); - -/** - * Type alias for prettifying a schema translation. - * Uses the Prettify utility from @forklaunch/common. - * - * @template T - Schema type. - * @template SV - SchemaValidator type. - */ -type SchemaPrettify = Prettify>; - -/** - * Type alias for a schema catchall type. - * Resolves to ZodCatchall for Zod schemas and TCatchall for TypeBox schemas. - * - * @template SV - SchemaValidator type. - */ -export type SchemaCatchall = ( - SV extends ZodSchemaValidator ? ZodCatchall : - SV extends TypeboxSchemaValidator ? TCatchall : - never -); - -/** - * Type alias for a valid schema object. - * Can be a schema object or a schema outer array. - * - * @template SV - SchemaValidator type. - */ -export type ValidSchemaObject = SchemaObject, SV> | SchemaOuterArray, SV>, SV>; - -/** - * Type alias for a schema. - * Applies prettification to the resolved schema. - * - * @template T - Valid schema object or idiomatic schema. - * @template SV - SchemaValidator type. - */ -export type Schema | IdiomaticSchema>, SV extends AnySchemaValidator> = SchemaPrettify, SV>; - -export * from "./interfaces"; +export * from "./types"; diff --git a/packages/validator/package-lock.json b/packages/validator/package-lock.json index 113eef776..6428e6c6f 100644 --- a/packages/validator/package-lock.json +++ b/packages/validator/package-lock.json @@ -1,12 +1,12 @@ { "name": "@forklaunch/validator", - "version": "0.2.2", + "version": "0.2.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@forklaunch/validator", - "version": "0.2.2", + "version": "0.2.5", "license": "MIT", "dependencies": { "@anatine/zod-openapi": "^2.2.6", @@ -19,6 +19,7 @@ "@types/jest": "^29.5.12", "globals": "^15.7.0", "openapi3-ts": "^4.3.3", + "prettier": "^3.3.2", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typescript-eslint": "^7.15.0" @@ -4265,6 +4266,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/packages/validator/package.json b/packages/validator/package.json index eff03024b..bc105260d 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -36,6 +36,7 @@ "@types/jest": "^29.5.12", "globals": "^15.7.0", "openapi3-ts": "^4.3.3", + "prettier": "^3.3.2", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typescript-eslint": "^7.15.0" diff --git a/packages/validator/tests/mockSchemaValidator.ts b/packages/validator/tests/mockSchemaValidator.ts index 8e028a321..65063edc3 100644 --- a/packages/validator/tests/mockSchemaValidator.ts +++ b/packages/validator/tests/mockSchemaValidator.ts @@ -1,8 +1,11 @@ import { SchemaValidator } from "../index"; import { LiteralSchema } from "../types/schema.types"; - export class MockSchemaValidator implements SchemaValidator { + _Type!: 'Mock'; + _SchemaCatchall!: string; + _ValidSchemaObject!: string; + string = 'string'; number = 'number'; bigint = 'bigint'; diff --git a/packages/validator/tests/typebox/largeSchema.test.ts b/packages/validator/tests/typebox/largeSchema.test.ts index c353c3095..e70dbf472 100644 --- a/packages/validator/tests/typebox/largeSchema.test.ts +++ b/packages/validator/tests/typebox/largeSchema.test.ts @@ -69,8 +69,8 @@ describe("Typebox Large Schema Tests", () => { } } - const deepUnion = union([deepOne, deepTwo]) - type DeepUnionSchema = Schema + const deepUnion = union([deepOne, deepTwo]); + type DeepUnionSchema = Schema; }); it("Realistic Schema", () => { @@ -142,6 +142,6 @@ describe("Typebox Large Schema Tests", () => { } }); - type RealisticSchema = Schema + type RealisticSchema = Schema; }); }) \ No newline at end of file diff --git a/packages/validator/tests/zod/equality.test.ts b/packages/validator/tests/zod/equality.test.ts index 787f78ad1..3cd828135 100644 --- a/packages/validator/tests/zod/equality.test.ts +++ b/packages/validator/tests/zod/equality.test.ts @@ -129,7 +129,7 @@ const compareSchemas = (schema1: ZodCatchall, schema2: ZodCatchall) => { }; describe('Zod Equality Tests', () => { - let schema: UnboxedObjectSchema + let schema: UnboxedObjectSchema let schemified: ZodObject let expectedSchema: ZodObject diff --git a/packages/validator/tests/zod/largeSchema.test.ts b/packages/validator/tests/zod/largeSchema.test.ts index 6b5e52fab..9f3fee2a3 100644 --- a/packages/validator/tests/zod/largeSchema.test.ts +++ b/packages/validator/tests/zod/largeSchema.test.ts @@ -69,8 +69,8 @@ describe('Zod Large Schema Tests', () => { } } - const deepUnion = union([deepOne, deepTwo]) - type DeepUnionSchema = Schema + const deepUnion = union([deepOne, deepTwo]); + type DeepUnionSchema = Schema; }); it('Realistic Schema', async () => { @@ -142,6 +142,6 @@ describe('Zod Large Schema Tests', () => { } }); - type RealisticSchema = Schema + type RealisticSchema = Schema; }); }); \ No newline at end of file diff --git a/packages/validator/typebox/index.ts b/packages/validator/typebox/index.ts index defc82ae4..372424eee 100644 --- a/packages/validator/typebox/index.ts +++ b/packages/validator/typebox/index.ts @@ -5,15 +5,11 @@ * @module TypeboxSchemaValidator */ -import { Kind, TArray, TLiteral, TOptional, TSchema, TUnion, Type } from '@sinclair/typebox'; +import { Kind, TArray, TLiteral, TOptional, TProperties, TSchema, TUnion, Type } from '@sinclair/typebox'; import { Value } from '@sinclair/typebox/value'; import { SchemaObject } from 'openapi3-ts/oas31'; -import { SchemaValidator } from '../interfaces/schemaValidator.interfaces'; -import { IdiomaticSchema, LiteralSchema } from '../types/schema.types'; -import { TIdiomaticSchema, TObjectShape, TResolve, TUnionContainer, UnionTResolve } from './types/typebox.schema.types'; - -type U = TIdiomaticSchema extends IdiomaticSchema ? true : false; -type M = TUnionContainer extends Array> ? true : false; +import { LiteralSchema, SchemaValidator } from '../types/schema.types'; +import { TIdiomaticSchema, TObject, TObjectShape, TResolve, TUnionContainer, UnionTResolve } from './types/typebox.schema.types'; /** * Class representing a TypeBox schema definition. @@ -28,6 +24,10 @@ export class TypeboxSchemaValidator implements SchemaValidator< (schema: T, value: unknown) => boolean, (schema: T) => SchemaObject > { + _Type!: 'TypeBox'; + _SchemaCatchall!: TSchema + _ValidSchemaObject!: TObject | TArray>; + string = Type.String(); number = Type.Number(); bigint = Type.BigInt(); diff --git a/packages/validator/types/index.ts b/packages/validator/types/index.ts new file mode 100644 index 000000000..bda2894eb --- /dev/null +++ b/packages/validator/types/index.ts @@ -0,0 +1 @@ +export * from './schema.types'; diff --git a/packages/validator/types/schema.types.ts b/packages/validator/types/schema.types.ts index d5b298434..87b1de734 100644 --- a/packages/validator/types/schema.types.ts +++ b/packages/validator/types/schema.types.ts @@ -1,10 +1,172 @@ + +import { Prettify } from "@forklaunch/common"; +import { SchemaObject } from "openapi3-ts/oas31"; +import { TResolve, TSchemaTranslate } from "../typebox/types/typebox.schema.types"; +import { ZodResolve, ZodSchemaTranslate } from "../zod/types/zod.schema.types"; + +/** + * Interface representing a schema validator. + * + * @template UnionContainer - The type for union schemas. + * @template IdiomaticSchema - The type for idiomatic schemas. + * @template Catchall - The catch-all type for all schemas. + */ +export interface SchemaValidator< + SchematicFunction = (schema: T) => unknown, + OptionalFunction =(schema: T) => unknown, + ArrayFunction = (schema: T) => unknown, + UnionFunction = (schemas: T[]) => unknown, + LiteralFunction = (schema: T) => unknown, + ValidationFunction = (schema: T, value: unknown) => boolean, + OpenAPIFunction = (schema: T) => SchemaObject +> { + _Type: unknown; + _SchemaCatchall: unknown; + _ValidSchemaObject: unknown; + + /** + * Validator for string type. + */ + string: unknown; + + /** + * Validator for number type. + */ + number: unknown; + + /** + * Validator for bigint type. + */ + bigint: unknown; + + /** + * Validator for boolean type. + */ + boolean: unknown; + + /** + * Validator for date type. + */ + date: unknown; + + /** + * Validator for symbol type. + */ + symbol: unknown; + + /** + * Validator for empty type. + */ + empty: unknown; + + /** + * Validator for any type. + */ + any: unknown; + + /** + * Validator for unknown type. + */ + unknown: unknown; + + /** + * Validator for never type. + */ + never: unknown; + + /** + * Converts a valid schema input into a schemified form. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to schemify. + * @returns {unknown} - The schemified form of the schema. + */ + schemify: SchematicFunction; + + /** + * Converts a schema into an optional schema. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to make optional. + * @returns {unknown} - The optional form of the schema. + */ + optional: OptionalFunction; + + /** + * Converts a schema into an array schema. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to convert into an array. + * @returns {unknown} - The array form of the schema. + */ + array: ArrayFunction; + + /** + * Converts multiple schemas into a union schema. + * + * @template T - The type of the union container. + * @param {T} schemas - The schemas to unionize. + * @returns {unknown} - The union form of the schemas. + */ + // union(schemas: T): unknown; + union: UnionFunction; + + + /** + * Creates a literal schema from a value. + * + * @template T - The type of the literal value. + * @param {T} value - The literal value. + * @returns {unknown} - The literal schema. + */ + literal: LiteralFunction; + + /** + * Validates a value against a schema. + * + * @template T - The type of the catch-all schema. + * @param {T} schema - The schema to validate against. + * @param {unknown} value - The value to validate. + * @returns {boolean} - Whether the value is valid according to the schema. + */ + validate: ValidationFunction; + + /** + * Converts a schema into an OpenAPI schema object. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to convert. + * @returns {SchemaObject} - The OpenAPI schema object. + */ + openapi: OpenAPIFunction; +} + +export type AnySchemaValidator = SchemaValidator; + +interface SchemaResolve { + Zod: ZodResolve, + TypeBox: TResolve +} + +interface SchemaTranslate { + Zod: ZodSchemaTranslate; + TypeBox: TSchemaTranslate; +} + +type SchemaPrettify = SV['_Type'] extends keyof SchemaTranslate ? Prettify[SV['_Type']]> : never; + +export type Schema, SV extends AnySchemaValidator> = SV['_Type'] extends keyof SchemaResolve ? + SchemaPrettify[SV['_Type']], SV> + : never; + + /** * Represents a schema for an unboxed object where each key can have an idiomatic schema. * * @template Catchall - The type to use for catch-all cases in the schema. */ -export type UnboxedObjectSchema = { - [key: KeyTypes]: IdiomaticSchema; +export type UnboxedObjectSchema = { + [key: KeyTypes]: IdiomaticSchema; }; /** @@ -17,7 +179,7 @@ export type LiteralSchema = string | number | boolean; * * @template Catchall - The type to use for catch-all cases in the schema. */ -export type IdiomaticSchema = UnboxedObjectSchema | LiteralSchema | Catchall; +export type IdiomaticSchema = UnboxedObjectSchema | LiteralSchema | SV['_SchemaCatchall']; /** * Increments a number type by one, with support up to 50. diff --git a/packages/validator/zod/index.ts b/packages/validator/zod/index.ts index aedf635f4..a891ea27d 100644 --- a/packages/validator/zod/index.ts +++ b/packages/validator/zod/index.ts @@ -7,9 +7,8 @@ import { generateSchema } from '@anatine/zod-openapi'; import { SchemaObject } from 'openapi3-ts/oas31'; -import { ZodArray, ZodLiteral, ZodOptional, ZodRawShape, ZodType, ZodUnion, z } from "zod"; -import { SchemaValidator } from "../interfaces/schemaValidator.interfaces"; -import { LiteralSchema } from "../types/schema.types"; +import { ZodArray, ZodLiteral, ZodObject, ZodOptional, ZodRawShape, ZodType, ZodUnion, z } from "zod"; +import { LiteralSchema, SchemaValidator } from "../types/schema.types"; import { UnionZodResolve, ZodCatchall, ZodIdiomaticSchema, ZodResolve, ZodUnionContainer } from "./types/zod.schema.types"; /** @@ -25,6 +24,10 @@ export class ZodSchemaValidator implements SchemaValidator< (schema: T, value: unknown) => boolean, (schema: T) => SchemaObject > { + _Type!: 'Zod'; + _SchemaCatchall!: ZodType; + _ValidSchemaObject!: ZodObject | ZodArray>; + string = z.string(); number = z.number(); bigint = z.bigint(); diff --git a/packages/validator/zod/types/zod.schema.types.ts b/packages/validator/zod/types/zod.schema.types.ts index 617d70b45..f46f1fe0a 100644 --- a/packages/validator/zod/types/zod.schema.types.ts +++ b/packages/validator/zod/types/zod.schema.types.ts @@ -1,4 +1,5 @@ import { ZodObject as OriginalZodObject, ZodArray, ZodLiteral, ZodNever, ZodRawShape, ZodType, ZodTypeAny, ZodUnknown, z } from "zod"; +import { ZodSchemaValidator } from ".."; import { IdiomaticSchema, Increment, LiteralSchema, UnboxedObjectSchema } from "../../types/schema.types"; /** @@ -35,12 +36,12 @@ export type ZodSchemaTranslate = T extends ZodCatchall ? z.infer : ZodNeve /** * Represents an unboxed Zod object schema where each key can have an idiomatic schema. */ -export type ZodObjectSchema = UnboxedObjectSchema; +export type ZodObjectSchema = UnboxedObjectSchema; /** * Represents an idiomatic schema for Zod which can be an unboxed object schema or a literal schema. */ -export type ZodIdiomaticSchema = IdiomaticSchema; +export type ZodIdiomaticSchema = IdiomaticSchema; /** * Represents a container for a union of Zod idiomatic schemas. From 19b97f5e6dace01354b9b94bc75f942818ff28e4 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 19:03:21 -0500 Subject: [PATCH 07/13] get rid of interfaces --- packages/validator/interfaces/index.ts | 1 - .../interfaces/schemaValidator.interfaces.ts | 136 ------------------ 2 files changed, 137 deletions(-) delete mode 100644 packages/validator/interfaces/index.ts delete mode 100644 packages/validator/interfaces/schemaValidator.interfaces.ts diff --git a/packages/validator/interfaces/index.ts b/packages/validator/interfaces/index.ts deleted file mode 100644 index 087626458..000000000 --- a/packages/validator/interfaces/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './schemaValidator.interfaces'; diff --git a/packages/validator/interfaces/schemaValidator.interfaces.ts b/packages/validator/interfaces/schemaValidator.interfaces.ts deleted file mode 100644 index b45fedd3e..000000000 --- a/packages/validator/interfaces/schemaValidator.interfaces.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { SchemaObject } from "openapi3-ts/oas31"; -import { LiteralSchema } from "../types/schema.types"; - -/** - * Interface representing a schema validator. - * - * @template UnionContainer - The type for union schemas. - * @template IdiomaticSchema - The type for idiomatic schemas. - * @template Catchall - The catch-all type for all schemas. - */ -export interface SchemaValidator< - SchematicFunction = (schema: T) => unknown, - OptionalFunction =(schema: T) => unknown, - ArrayFunction = (schema: T) => unknown, - UnionFunction = (schemas: T[]) => unknown, - LiteralFunction = (schema: T) => unknown, - ValidationFunction = (schema: T, value: unknown) => boolean, - OpenAPIFunction = (schema: T) => SchemaObject -> { - - /** - * Validator for string type. - */ - string: unknown; - - /** - * Validator for number type. - */ - number: unknown; - - /** - * Validator for bigint type. - */ - bigint: unknown; - - /** - * Validator for boolean type. - */ - boolean: unknown; - - /** - * Validator for date type. - */ - date: unknown; - - /** - * Validator for symbol type. - */ - symbol: unknown; - - /** - * Validator for empty type. - */ - empty: unknown; - - /** - * Validator for any type. - */ - any: unknown; - - /** - * Validator for unknown type. - */ - unknown: unknown; - - /** - * Validator for never type. - */ - never: unknown; - - /** - * Converts a valid schema input into a schemified form. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to schemify. - * @returns {unknown} - The schemified form of the schema. - */ - schemify: SchematicFunction; - - /** - * Converts a schema into an optional schema. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to make optional. - * @returns {unknown} - The optional form of the schema. - */ - optional: OptionalFunction; - - /** - * Converts a schema into an array schema. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to convert into an array. - * @returns {unknown} - The array form of the schema. - */ - array: ArrayFunction; - - /** - * Converts multiple schemas into a union schema. - * - * @template T - The type of the union container. - * @param {T} schemas - The schemas to unionize. - * @returns {unknown} - The union form of the schemas. - */ - // union(schemas: T): unknown; - union: UnionFunction; - - - /** - * Creates a literal schema from a value. - * - * @template T - The type of the literal value. - * @param {T} value - The literal value. - * @returns {unknown} - The literal schema. - */ - literal: LiteralFunction; - - /** - * Validates a value against a schema. - * - * @template T - The type of the catch-all schema. - * @param {T} schema - The schema to validate against. - * @param {unknown} value - The value to validate. - * @returns {boolean} - Whether the value is valid according to the schema. - */ - validate: ValidationFunction; - - /** - * Converts a schema into an OpenAPI schema object. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to convert. - * @returns {SchemaObject} - The OpenAPI schema object. - */ - openapi: OpenAPIFunction; -} \ No newline at end of file From 6d45e12bc55eac5bb57c707d99f8fa2c4adeb6ce Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 19:27:41 -0500 Subject: [PATCH 08/13] prettier added --- packages/validator/index.ts | 2 +- packages/validator/jest.config.ts | 2 +- packages/validator/package-lock.json | 98 +++- packages/validator/package.json | 10 +- .../validator/tests/mockSchemaValidator.ts | 74 +-- .../validator/tests/typebox/equality.test.ts | 506 ++++++++++------- .../tests/typebox/largeSchema.test.ts | 258 +++++---- packages/validator/tests/zod/equality.test.ts | 525 +++++++++++------- .../validator/tests/zod/largeSchema.test.ts | 256 +++++---- packages/validator/typebox/index.ts | 285 +++++----- .../typebox/types/typebox.schema.types.ts | 67 ++- packages/validator/types/schema.types.ts | 464 +++++++++------- packages/validator/zod/index.ts | 283 +++++----- .../validator/zod/types/zod.schema.types.ts | 88 ++- 14 files changed, 1715 insertions(+), 1203 deletions(-) diff --git a/packages/validator/index.ts b/packages/validator/index.ts index eea524d65..fcb073fef 100644 --- a/packages/validator/index.ts +++ b/packages/validator/index.ts @@ -1 +1 @@ -export * from "./types"; +export * from './types'; diff --git a/packages/validator/jest.config.ts b/packages/validator/jest.config.ts index 3d485f92b..e52889d64 100644 --- a/packages/validator/jest.config.ts +++ b/packages/validator/jest.config.ts @@ -7,4 +7,4 @@ const config: Config = { testPathIgnorePatterns: ['dist/', 'node_modules/'] }; -export default config; \ No newline at end of file +export default config; diff --git a/packages/validator/package-lock.json b/packages/validator/package-lock.json index 6428e6c6f..64da23d80 100644 --- a/packages/validator/package-lock.json +++ b/packages/validator/package-lock.json @@ -17,9 +17,10 @@ "devDependencies": { "@eslint/js": "^9.6.0", "@types/jest": "^29.5.12", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "globals": "^15.7.0", "openapi3-ts": "^4.3.3", - "prettier": "^3.3.2", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typescript-eslint": "^7.15.0" @@ -1230,6 +1231,18 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@sinclair/typebox": { "version": "0.32.34", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.32.34.tgz", @@ -2262,6 +2275,48 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -2547,6 +2602,12 @@ "dev": true, "peer": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -4271,6 +4332,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -4281,6 +4343,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -4681,6 +4755,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -4854,6 +4944,12 @@ } } }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/packages/validator/package.json b/packages/validator/package.json index bc105260d..f65ac19e9 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@forklaunch/validator", - "version": "0.2.5", + "version": "0.2.6", "description": "Schema validator for ForkLaunch components.", "files": [ "dist" @@ -13,7 +13,8 @@ "test": "jest", "build": "tsc", "lint": "eslint . -c eslint.config.mjs", - "lint:fix": "eslint . -c eslint.config.mjs --fix" + "lint:fix": "eslint . -c eslint.config.mjs --fix", + "format": "prettier --ignore-path=.prettierignore --config .prettierrc '**/*.ts' --write" }, "repository": { "type": "git", @@ -34,9 +35,10 @@ "devDependencies": { "@eslint/js": "^9.6.0", "@types/jest": "^29.5.12", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "globals": "^15.7.0", "openapi3-ts": "^4.3.3", - "prettier": "^3.3.2", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typescript-eslint": "^7.15.0" @@ -75,4 +77,4 @@ "default": "./dist/tests/mockSchemaValidator.js" } } -} +} \ No newline at end of file diff --git a/packages/validator/tests/mockSchemaValidator.ts b/packages/validator/tests/mockSchemaValidator.ts index 65063edc3..bcac83a64 100644 --- a/packages/validator/tests/mockSchemaValidator.ts +++ b/packages/validator/tests/mockSchemaValidator.ts @@ -1,41 +1,41 @@ -import { SchemaValidator } from "../index"; -import { LiteralSchema } from "../types/schema.types"; +import { SchemaValidator } from '../index'; +import { LiteralSchema } from '../types/schema.types'; export class MockSchemaValidator implements SchemaValidator { - _Type!: 'Mock'; - _SchemaCatchall!: string; - _ValidSchemaObject!: string; + _Type!: 'Mock'; + _SchemaCatchall!: string; + _ValidSchemaObject!: string; - string = 'string'; - number = 'number'; - bigint = 'bigint'; - boolean = 'boolean'; - date = 'date'; - symbol = 'symbol'; - empty = 'empty'; - any = 'any'; - unknown = 'unknown'; - never = 'never'; + string = 'string'; + number = 'number'; + bigint = 'bigint'; + boolean = 'boolean'; + date = 'date'; + symbol = 'symbol'; + empty = 'empty'; + any = 'any'; + unknown = 'unknown'; + never = 'never'; - schemify(schema: T) { - return schema; - }; - optional(schema: T) { - return 'optional ' + schema; - }; - array(schema: T) { - return 'array ' + schema; - } - union(schemas: T[]) { - return schemas.join(' | '); - } - literal(schema: T) { - return 'literal ' + schema; - }; - validate(schema: T) { - return true; - }; - openapi(schema: T) { - return {}; - } -} \ No newline at end of file + schemify(schema: T) { + return schema; + } + optional(schema: T) { + return 'optional ' + schema; + } + array(schema: T) { + return 'array ' + schema; + } + union(schemas: T[]) { + return schemas.join(' | '); + } + literal(schema: T) { + return 'literal ' + schema; + } + validate(schema: T) { + return true; + } + openapi(schema: T) { + return {}; + } +} diff --git a/packages/validator/tests/typebox/equality.test.ts b/packages/validator/tests/typebox/equality.test.ts index 971913ee5..f14931e9a 100644 --- a/packages/validator/tests/typebox/equality.test.ts +++ b/packages/validator/tests/typebox/equality.test.ts @@ -1,99 +1,146 @@ -import { TObject, Type } from "@sinclair/typebox" -import { Schema } from "../../index" -import { TypeboxSchemaValidator, array, bigint, boolean, date, empty, never, number, openapi, optional, schemify, string, symbol, union, validate } from "../../typebox/index" -import { UnboxedTObjectSchema } from "../../typebox/types/typebox.schema.types" +import { TObject, Type } from '@sinclair/typebox'; +import { Schema } from '../../index'; +import { + TypeboxSchemaValidator, + array, + bigint, + boolean, + date, + empty, + never, + number, + openapi, + optional, + schemify, + string, + symbol, + union, + validate +} from '../../typebox/index'; +import { UnboxedTObjectSchema } from '../../typebox/types/typebox.schema.types'; const one = array({ - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: { - a: optional(string), - b: number, - c: { - d: string, - e: number - } - } - }, - 200: { - j: string - }, + name: { + j: union([string, number, date, boolean, bigint, empty, symbol, never]), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), m: { - a: true as const + a: optional(string), + b: number, + c: { + d: string, + e: number + } } -}) + }, + 200: { + j: string + }, + m: { + a: true as const + } +}); const two = array({ - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: schemify({ - a: optional(string), - b: number, - c: { - d: string, - e: number - } - }) - }, - 200: { - j: string - }, - m: { - a: true as const - } -}) -const three = schemify(array(schemify({ - name: schemify({ + name: { + j: union([string, number, date, boolean, bigint, empty, symbol, never]), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), + m: schemify({ + a: optional(string), + b: number, + c: { + d: string, + e: number + } + }) + }, + 200: { + j: string + }, + m: { + a: true as const + } +}); +const three = schemify( + array( + schemify({ + name: schemify({ j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), m: schemify({ - a: optional(string), - b: number, - c: { - d: string, - e: number - } + a: optional(string), + b: number, + c: { + d: string, + e: number + } }) - }), - 200: schemify({ + }), + 200: schemify({ j: string - }), - m: schemify({ + }), + m: schemify({ a: true as const + }) }) -}))) + ) +); export function assert() {} -type Equality = Exclude | Exclude; +type Equality = Exclude | Exclude; type Expected = { - name: { - j: string | number | bigint | boolean | symbol | void | Date | null | undefined, - t?: string | { - y: number[] - }[]| undefined, - m: { - a?: string | undefined, - b: number, - c: { - d: string, - e: number - } - } - }, - 200: { - j: string - }, + name: { + j: + | string + | number + | bigint + | boolean + | symbol + | void + | Date + | null + | undefined; + t?: + | string + | { + y: number[]; + }[] + | undefined; m: { - a: true - } + a?: string | undefined; + b: number; + c: { + d: string; + e: number; + }; + }; + }; + 200: { + j: string; + }; + m: { + a: true; + }; }[]; assert>(); @@ -107,156 +154,191 @@ assert>(); assert>(); const shortOne = { - s: string, - non: number -} + s: string, + non: number +}; const shortTwo = schemify({ - s: string, - non: number -}) + s: string, + non: number +}); type ShortExpected = { - s: string; - non: number; -} -assert, ShortExpected>>(); -assert, Schema>>(); + s: string; + non: number; +}; +assert< + Equality, ShortExpected> +>(); +assert< + Equality< + Schema, + Schema + > +>(); describe('Typebox Equality Tests', () => { - let schema: UnboxedTObjectSchema - let schemified: TObject - let expectedSchema: TObject + let schema: UnboxedTObjectSchema; + let schemified: TObject; + let expectedSchema: TObject; - beforeAll(() => { - schema = { - hello: { - world: string - }, - foo: { - bar: number - } - } - schemified = schemify(schema); - expectedSchema = Type.Object({ - hello: Type.Object({ - world: Type.String() - }), - foo: Type.Object({ - bar: Type.Number() - }) - }); + beforeAll(() => { + schema = { + hello: { + world: string + }, + foo: { + bar: number + } + }; + schemified = schemify(schema); + expectedSchema = Type.Object({ + hello: Type.Object({ + world: Type.String() + }), + foo: Type.Object({ + bar: Type.Number() + }) }); + }); - test('Schema Equality', async () => { - expect(schemified).toEqual(expectedSchema); + test('Schema Equality', async () => { + expect(schemified).toEqual(expectedSchema); - expect(schemified).toEqual(schemify({ - hello: { - world: string - }, - foo: { - bar: number - } - })); - expect(schemified).toEqual(schemify({ - hello: schemify({ - world: string - }), - foo: { - bar: number - } - })); - expect(schemified).toEqual(schemify({ - hello: { - world: string - }, - foo: schemify({ - bar: number - }) - })); - expect(schemified).toEqual(schemify({ - hello: schemify({ - world: string - }), - foo: schemify({ - bar: number - }) - })); - }); + expect(schemified).toEqual( + schemify({ + hello: { + world: string + }, + foo: { + bar: number + } + }) + ); + expect(schemified).toEqual( + schemify({ + hello: schemify({ + world: string + }), + foo: { + bar: number + } + }) + ); + expect(schemified).toEqual( + schemify({ + hello: { + world: string + }, + foo: schemify({ + bar: number + }) + }) + ); + expect(schemified).toEqual( + schemify({ + hello: schemify({ + world: string + }), + foo: schemify({ + bar: number + }) + }) + ); + }); - test('Optional Schema Equality', async () => { - const unboxSchemified = optional(schema); - const boxSchemified = optional(schemified); + test('Optional Schema Equality', async () => { + const unboxSchemified = optional(schema); + const boxSchemified = optional(schemified); - const schemifiedExpected = Type.Optional(expectedSchema); - expect(unboxSchemified).toEqual(schemifiedExpected); - expect(boxSchemified).toEqual(schemifiedExpected); - }); + const schemifiedExpected = Type.Optional(expectedSchema); + expect(unboxSchemified).toEqual(schemifiedExpected); + expect(boxSchemified).toEqual(schemifiedExpected); + }); - test('Array Schema Equality', async () => { - const unboxSchemified = array(schema); - const boxSchemified = array(schemified); + test('Array Schema Equality', async () => { + const unboxSchemified = array(schema); + const boxSchemified = array(schemified); - const schemifiedExpected = Type.Array(expectedSchema) - expect(unboxSchemified).toEqual(schemifiedExpected); - expect(boxSchemified).toEqual(schemifiedExpected); - - }); + const schemifiedExpected = Type.Array(expectedSchema); + expect(unboxSchemified).toEqual(schemifiedExpected); + expect(boxSchemified).toEqual(schemifiedExpected); + }); - test('Union Schema Equality', async () => { - const unboxSchemified = union([schema, { - test: string - }]); - const unboxSchemified2 = union([schema, schemify({ - test: string - })]); - const boxSchemified1 = union([schemified, schemify({ - test: string - })]); - const boxSchemified2 = union([schemified, { - test: string - }]); + test('Union Schema Equality', async () => { + const unboxSchemified = union([ + schema, + { + test: string + } + ]); + const unboxSchemified2 = union([ + schema, + schemify({ + test: string + }) + ]); + const boxSchemified1 = union([ + schemified, + schemify({ + test: string + }) + ]); + const boxSchemified2 = union([ + schemified, + { + test: string + } + ]); - const schemifiedExpected = Type.Union([expectedSchema, Type.Object({ - test: Type.String() - })]); + const schemifiedExpected = Type.Union([ + expectedSchema, + Type.Object({ + test: Type.String() + }) + ]); - expect(unboxSchemified).toEqual(schemifiedExpected); - expect(unboxSchemified2).toEqual(schemifiedExpected); - }); + expect(unboxSchemified).toEqual(schemifiedExpected); + expect(unboxSchemified2).toEqual(schemifiedExpected); + }); - test('Literal Schema Equality', async () => { - const schemified = schemify({ - hello: 'world' - }); - expect(schemified).toEqual(Type.Object({ - hello: Type.Literal('world') - })); + test('Literal Schema Equality', async () => { + const schemified = schemify({ + hello: 'world' }); + expect(schemified).toEqual( + Type.Object({ + hello: Type.Literal('world') + }) + ); + }); - test('Validate Schema', async () => { - expect(validate(schemified, { - hello: { - world: 'world' - }, - foo: { - bar: 42 - } - })).toBe(true); - expect(validate(schemified, { - hello: { - world: 55 - }, - foo: { - bar: 42 - } - })).toBe(false); - }); + test('Validate Schema', async () => { + expect( + validate(schemified, { + hello: { + world: 'world' + }, + foo: { + bar: 42 + } + }) + ).toBe(true); + expect( + validate(schemified, { + hello: { + world: 55 + }, + foo: { + bar: 42 + } + }) + ).toBe(false); + }); - test('OpenAPI Conversion', async () => { - const schemified = schemify(schema); - const openApi = openapi(schemified); - expect(openApi).toEqual(schemified); - }); -}) \ No newline at end of file + test('OpenAPI Conversion', async () => { + const schemified = schemify(schema); + const openApi = openapi(schemified); + expect(openApi).toEqual(schemified); + }); +}); diff --git a/packages/validator/tests/typebox/largeSchema.test.ts b/packages/validator/tests/typebox/largeSchema.test.ts index e70dbf472..0622e9973 100644 --- a/packages/validator/tests/typebox/largeSchema.test.ts +++ b/packages/validator/tests/typebox/largeSchema.test.ts @@ -1,147 +1,173 @@ -import { Schema } from "../../index"; -import { TypeboxSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, string, symbol, union } from "../../typebox/index"; +import { Schema } from '../../index'; +import { + TypeboxSchemaValidator, + array, + bigint, + boolean, + date, + empty, + never, + number, + optional, + string, + symbol, + union +} from '../../typebox/index'; -describe("Typebox Large Schema Tests", () => { - it("Deep Union", () => { - const deepOne = { +describe('Typebox Large Schema Tests', () => { + it('Deep Union', () => { + const deepOne = { + s: { + s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { - s: { - s: { - s: { - s:{ - s: { - s: { - s: { - s: { - b: "number" as const - } - } - } - } - } - } - } - } + b: 'number' as const } + } } + } } + } } + } } + } } + } } - - - const deepTwo = { - k: { - o: number, + } + }; + + const deepTwo = { + k: { + o: number, + s: { + s: { + s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { - s: { - s: { - s: { - s:{ - s: { - s: { - s: { - s: { - b: string - } - } - } - } - } - } - } - } + b: string } + } } + } } + } } + } } + } } + } } - - const deepUnion = union([deepOne, deepTwo]); - type DeepUnionSchema = Schema; - }); + } + }; - it("Realistic Schema", () => { - const realistic = array({ - level1: { - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: { - a: optional(string), - b: number, - c: { - d: string, - e: number, - f: { - g: string, - h: number, - i: { - j: string, - k: number, - l: { - m: boolean, - n: array(string), - o: optional(union([string, number])), - p: { - q: string, - r: number - } - } - } - } - } - } - }, - additionalField1: { - a: union([string, boolean, bigint, empty]), - b: optional(array(number)), - c: { - d: string, - e: number, - f: { - g: string, - h: number - } - } - }, - additionalField2: { - x: string, - y: union([string, array(boolean)]), - z: { - a: string, - b: number + const deepUnion = union([deepOne, deepTwo]); + type DeepUnionSchema = Schema; + }); + + it('Realistic Schema', () => { + const realistic = array({ + level1: { + name: { + j: union([ + string, + number, + date, + boolean, + bigint, + empty, + symbol, + never + ]), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), + m: { + a: optional(string), + b: number, + c: { + d: string, + e: number, + f: { + g: string, + h: number, + i: { + j: string, + k: number, + l: { + m: boolean, + n: array(string), + o: optional(union([string, number])), + p: { + q: string, + r: number } + } } - }, - code: { - 200: { - j: string - }, - 404: { - k: string - } - }, - flag: { - a: true as const, - b: false as const + } + } + } + }, + additionalField1: { + a: union([string, boolean, bigint, empty]), + b: optional(array(number)), + c: { + d: string, + e: number, + f: { + g: string, + h: number } - }); - - type RealisticSchema = Schema; + } + }, + additionalField2: { + x: string, + y: union([string, array(boolean)]), + z: { + a: string, + b: number + } + } + }, + code: { + 200: { + j: string + }, + 404: { + k: string + } + }, + flag: { + a: true as const, + b: false as const + } }); -}) \ No newline at end of file + + type RealisticSchema = Schema; + }); +}); diff --git a/packages/validator/tests/zod/equality.test.ts b/packages/validator/tests/zod/equality.test.ts index 3cd828135..c725a0c8d 100644 --- a/packages/validator/tests/zod/equality.test.ts +++ b/packages/validator/tests/zod/equality.test.ts @@ -1,105 +1,152 @@ -import { generateSchema } from "@anatine/zod-openapi" -import { ZodObject, z } from "zod" -import { Schema } from "../../index" -import { UnboxedObjectSchema } from "../../types/schema.types" -import { ZodSchemaValidator, array, bigint, boolean, date, empty, never, number, openapi, optional, schemify, string, symbol, union, validate } from "../../zod" -import { ZodCatchall, ZodObjectShape } from "../../zod/types/zod.schema.types" +import { generateSchema } from '@anatine/zod-openapi'; +import { ZodObject, z } from 'zod'; +import { Schema } from '../../index'; +import { UnboxedObjectSchema } from '../../types/schema.types'; +import { + ZodSchemaValidator, + array, + bigint, + boolean, + date, + empty, + never, + number, + openapi, + optional, + schemify, + string, + symbol, + union, + validate +} from '../../zod'; +import { ZodCatchall, ZodObjectShape } from '../../zod/types/zod.schema.types'; const one = array({ - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: { - a: optional(string), - b: number, - c: { - d: string, - e: number - } - } - }, - 200: { - j: string - }, + name: { + j: union([string, number, date, boolean, bigint, empty, symbol, never]), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), m: { - a: true as const + a: optional(string), + b: number, + c: { + d: string, + e: number + } } -}) + }, + 200: { + j: string + }, + m: { + a: true as const + } +}); const two = array({ - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: schemify({ - a: optional(string), - b: number, - c: { - d: string, - e: number - } - }) - }, - 200: { - j: string - }, - m: { - a: true as const - } -}) -const three = schemify(array(schemify({ - name: schemify({ + name: { + j: union([string, number, date, boolean, bigint, empty, symbol, never]), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), + m: schemify({ + a: optional(string), + b: number, + c: { + d: string, + e: number + } + }) + }, + 200: { + j: string + }, + m: { + a: true as const + } +}); +const three = schemify( + array( + schemify({ + name: schemify({ j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), m: schemify({ - a: optional(string), - b: number, - c: { - d: string, - e: number - } + a: optional(string), + b: number, + c: { + d: string, + e: number + } }) - }), - 200: schemify({ + }), + 200: schemify({ j: string - }), - m: schemify({ + }), + m: schemify({ a: true as const + }) }) -}))) + ) +); export function assert() {} -type Equality = Exclude | Exclude; +type Equality = Exclude | Exclude; type SchemaOne = Schema; type SchemaTwo = Schema; type SchemaThree = Schema; type Expected = { - name: { - j?: string | number | bigint | boolean | symbol | void | Date | null | undefined, - t?: string | { - y: number[] - }[]| undefined, - m: { - a?: string | undefined, - b: number, - c: { - d: string, - e: number - } - } - }, - 200: { - j: string - }, + name: { + j?: + | string + | number + | bigint + | boolean + | symbol + | void + | Date + | null + | undefined; + t?: + | string + | { + y: number[]; + }[] + | undefined; m: { - a: true - } + a?: string | undefined; + b: number; + c: { + d: string; + e: number; + }; + }; + }; + 200: { + j: string; + }; + m: { + a: true; + }; }[]; assert>(); @@ -108,160 +155,208 @@ assert>(); assert>(); const shortOne = { - s: string, - non: number -} + s: string, + non: number +}; const shortTwo = schemify({ - s: string, - non: number -}) + s: string, + non: number +}); type ShortExpected = { - s: string; - non: number; -} + s: string; + non: number; +}; assert, ShortExpected>>(); -assert, Schema>>(); +assert< + Equality< + Schema, + Schema + > +>(); const compareSchemas = (schema1: ZodCatchall, schema2: ZodCatchall) => { - return JSON.stringify(schema1) === JSON.stringify(schema2); - }; + return JSON.stringify(schema1) === JSON.stringify(schema2); +}; describe('Zod Equality Tests', () => { - let schema: UnboxedObjectSchema - let schemified: ZodObject - let expectedSchema: ZodObject + let schema: UnboxedObjectSchema; + let schemified: ZodObject; + let expectedSchema: ZodObject; - beforeAll(() => { - schema = { - hello: { - world: string - }, - foo: { - bar: number - } - } - schemified = schemify(schema); - expectedSchema = z.object({ - hello: z.object({ - world: z.string() - }), - foo: z.object({ - bar: z.number() - }) - }); + beforeAll(() => { + schema = { + hello: { + world: string + }, + foo: { + bar: number + } + }; + schemified = schemify(schema); + expectedSchema = z.object({ + hello: z.object({ + world: z.string() + }), + foo: z.object({ + bar: z.number() + }) }); + }); - test('Schema Equality', async () => { - expect(compareSchemas(schemified, expectedSchema)).toBe(true); + test('Schema Equality', async () => { + expect(compareSchemas(schemified, expectedSchema)).toBe(true); - expect(compareSchemas(schemified, schemify({ - hello: { - world: string - }, - foo: { - bar: number - } - }))).toBe(true); - expect(compareSchemas(schemified, schemify({ - hello: schemify({ - world: string - }), - foo: { - bar: number - } - }))).toBe(true); - expect(compareSchemas(schemified, schemify({ - hello: { - world: string - }, - foo: schemify({ - bar: number - }) - }))).toBe(true); - expect(compareSchemas(schemified, schemify({ - hello: schemify({ - world: string - }), - foo: schemify({ - bar: number - }) - }))).toBe(true); - }); + expect( + compareSchemas( + schemified, + schemify({ + hello: { + world: string + }, + foo: { + bar: number + } + }) + ) + ).toBe(true); + expect( + compareSchemas( + schemified, + schemify({ + hello: schemify({ + world: string + }), + foo: { + bar: number + } + }) + ) + ).toBe(true); + expect( + compareSchemas( + schemified, + schemify({ + hello: { + world: string + }, + foo: schemify({ + bar: number + }) + }) + ) + ).toBe(true); + expect( + compareSchemas( + schemified, + schemify({ + hello: schemify({ + world: string + }), + foo: schemify({ + bar: number + }) + }) + ) + ).toBe(true); + }); - test('Optional Schema Equality', async () => { - const unboxSchemified = optional(schema); - const boxSchemified = optional(schemified); + test('Optional Schema Equality', async () => { + const unboxSchemified = optional(schema); + const boxSchemified = optional(schemified); - const schemifiedExpected = z.optional(expectedSchema); - expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); - expect(compareSchemas(boxSchemified, schemifiedExpected)).toBe(true); - }); + const schemifiedExpected = z.optional(expectedSchema); + expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); + expect(compareSchemas(boxSchemified, schemifiedExpected)).toBe(true); + }); - test('Array Schema Equality', async () => { - const unboxSchemified = array(schema); - const boxSchemified = array(schemified); + test('Array Schema Equality', async () => { + const unboxSchemified = array(schema); + const boxSchemified = array(schemified); - const schemifiedExpected = z.array(expectedSchema) - expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); - expect(compareSchemas(boxSchemified, schemifiedExpected)).toBe(true); - - }); + const schemifiedExpected = z.array(expectedSchema); + expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); + expect(compareSchemas(boxSchemified, schemifiedExpected)).toBe(true); + }); - test('Union Schema Equality', async () => { - const unboxSchemified = union([schema, { - test: string - }]); - const unboxSchemified2 = union([schema, schemify({ - test: string - })]); - const boxSchemified1 = union([schemified, schemify({ - test: string - })]); - const boxSchemified2 = union([schemified, { - test: string - }]); + test('Union Schema Equality', async () => { + const unboxSchemified = union([ + schema, + { + test: string + } + ]); + const unboxSchemified2 = union([ + schema, + schemify({ + test: string + }) + ]); + const boxSchemified1 = union([ + schemified, + schemify({ + test: string + }) + ]); + const boxSchemified2 = union([ + schemified, + { + test: string + } + ]); - const schemifiedExpected = z.union([expectedSchema, z.object({ - test: z.string() - })]); + const schemifiedExpected = z.union([ + expectedSchema, + z.object({ + test: z.string() + }) + ]); - expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); - expect(compareSchemas(unboxSchemified2, schemifiedExpected)).toBe(true); - }); + expect(compareSchemas(unboxSchemified, schemifiedExpected)).toBe(true); + expect(compareSchemas(unboxSchemified2, schemifiedExpected)).toBe(true); + }); - test('Literal Schema Equality', async () => { - const schemified = schemify({ - hello: 'world' - }); - expect(compareSchemas(schemified, z.object({ - hello: z.literal('world') - }))).toBe(true); + test('Literal Schema Equality', async () => { + const schemified = schemify({ + hello: 'world' }); + expect( + compareSchemas( + schemified, + z.object({ + hello: z.literal('world') + }) + ) + ).toBe(true); + }); - test('Validate Schema', async () => { - expect(validate(schemified, { - hello: { - world: 'world' - }, - foo: { - bar: 42 - } - })).toBe(true); - expect(validate(schemified, { - hello: { - world: 55 - }, - foo: { - bar: 42 - } - })).toBe(false); - }); + test('Validate Schema', async () => { + expect( + validate(schemified, { + hello: { + world: 'world' + }, + foo: { + bar: 42 + } + }) + ).toBe(true); + expect( + validate(schemified, { + hello: { + world: 55 + }, + foo: { + bar: 42 + } + }) + ).toBe(false); + }); - test('OpenAPI Conversion', async () => { - const schemified = schemify(schema); - const openApi = openapi(schemified); - expect(openApi).toEqual(generateSchema(schemified)); - }); -}) \ No newline at end of file + test('OpenAPI Conversion', async () => { + const schemified = schemify(schema); + const openApi = openapi(schemified); + expect(openApi).toEqual(generateSchema(schemified)); + }); +}); diff --git a/packages/validator/tests/zod/largeSchema.test.ts b/packages/validator/tests/zod/largeSchema.test.ts index 9f3fee2a3..38b4ab80f 100644 --- a/packages/validator/tests/zod/largeSchema.test.ts +++ b/packages/validator/tests/zod/largeSchema.test.ts @@ -1,147 +1,173 @@ -import { Schema } from "../../index"; -import { ZodSchemaValidator, array, bigint, boolean, date, empty, never, number, optional, string, symbol, union } from "../../zod/index"; +import { Schema } from '../../index'; +import { + ZodSchemaValidator, + array, + bigint, + boolean, + date, + empty, + never, + number, + optional, + string, + symbol, + union +} from '../../zod/index'; describe('Zod Large Schema Tests', () => { - it ('Deep Union', async () => { - const deepOne = { + it('Deep Union', async () => { + const deepOne = { + s: { + s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { - s: { - s: { - s: { - s:{ - s: { - s: { - s: { - s: { - b: "number" as const - } - } - } - } - } - } - } - } + b: 'number' as const } + } } + } } + } } + } } + } } + } } - - - const deepTwo = { - k: { - o: number, + } + }; + + const deepTwo = { + k: { + o: number, + s: { + s: { + s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { + s: { s: { - s: { - s: { - s: { - s:{ - s: { - s: { - s: { - s: { - b: string - } - } - } - } - } - } - } - } + b: string } + } } + } } + } } + } } + } } + } } - - const deepUnion = union([deepOne, deepTwo]); - type DeepUnionSchema = Schema; - }); + } + }; - it('Realistic Schema', async () => { - const realistic = array({ - level1: { - name: { - j: union([string, number, date, boolean, bigint, empty, symbol, never]), - t: optional(union([array({ - y: array(number) - }), string])), - m: { - a: optional(string), - b: number, - c: { - d: string, - e: number, - f: { - g: string, - h: number, - i: { - j: string, - k: number, - l: { - m: boolean, - n: array(string), - o: optional(union([string, number])), - p: { - q: string, - r: number - } - } - } - } - } - } - }, - additionalField1: { - a: union([string, boolean, bigint, empty]), - b: optional(array(number)), - c: { - d: string, - e: number, - f: { - g: string, - h: number - } - } - }, - additionalField2: { - x: string, - y: union([string, array(boolean)]), - z: { - a: string, - b: number + const deepUnion = union([deepOne, deepTwo]); + type DeepUnionSchema = Schema; + }); + + it('Realistic Schema', async () => { + const realistic = array({ + level1: { + name: { + j: union([ + string, + number, + date, + boolean, + bigint, + empty, + symbol, + never + ]), + t: optional( + union([ + array({ + y: array(number) + }), + string + ]) + ), + m: { + a: optional(string), + b: number, + c: { + d: string, + e: number, + f: { + g: string, + h: number, + i: { + j: string, + k: number, + l: { + m: boolean, + n: array(string), + o: optional(union([string, number])), + p: { + q: string, + r: number } + } } - }, - code: { - 200: { - j: string - }, - 404: { - k: string - } - }, - flag: { - a: true as const, - b: false as const + } } - }); - - type RealisticSchema = Schema; + } + }, + additionalField1: { + a: union([string, boolean, bigint, empty]), + b: optional(array(number)), + c: { + d: string, + e: number, + f: { + g: string, + h: number + } + } + }, + additionalField2: { + x: string, + y: union([string, array(boolean)]), + z: { + a: string, + b: number + } + } + }, + code: { + 200: { + j: string + }, + 404: { + k: string + } + }, + flag: { + a: true as const, + b: false as const + } }); -}); \ No newline at end of file + + type RealisticSchema = Schema; + }); +}); diff --git a/packages/validator/typebox/index.ts b/packages/validator/typebox/index.ts index 372424eee..f09d828b9 100644 --- a/packages/validator/typebox/index.ts +++ b/packages/validator/typebox/index.ts @@ -1,147 +1,173 @@ /** * This module provides a TypeScript-based schema definition using the TypeBox library. * It includes various types, schema creation, validation, and OpenAPI integration. - * + * * @module TypeboxSchemaValidator */ -import { Kind, TArray, TLiteral, TOptional, TProperties, TSchema, TUnion, Type } from '@sinclair/typebox'; +import { + Kind, + TArray, + TLiteral, + TOptional, + TProperties, + TSchema, + TUnion, + Type +} from '@sinclair/typebox'; import { Value } from '@sinclair/typebox/value'; import { SchemaObject } from 'openapi3-ts/oas31'; import { LiteralSchema, SchemaValidator } from '../types/schema.types'; -import { TIdiomaticSchema, TObject, TObjectShape, TResolve, TUnionContainer, UnionTResolve } from './types/typebox.schema.types'; +import { + TIdiomaticSchema, + TObject, + TObjectShape, + TResolve, + TUnionContainer, + UnionTResolve +} from './types/typebox.schema.types'; /** * Class representing a TypeBox schema definition. * @implements {SchemaValidator} */ -export class TypeboxSchemaValidator implements SchemaValidator< - (schema: T) => TResolve, - (schema: T) => TOptional>, - (schema: T) => TArray>, - (schemas: T) => TUnion>, - (value: T) => TLiteral, - (schema: T, value: unknown) => boolean, - (schema: T) => SchemaObject -> { - _Type!: 'TypeBox'; - _SchemaCatchall!: TSchema - _ValidSchemaObject!: TObject | TArray>; - - string = Type.String(); - number = Type.Number(); - bigint = Type.BigInt(); - boolean = Type.Boolean(); - date = Type.Date(); - symbol = Type.Symbol(); - empty = Type.Union([Type.Void(), Type.Null(), Type.Undefined()]); - any = Type.Any(); - unknown = Type.Unknown(); - never = Type.Never(); - - /** - * Convert a schema to a TypeBox schema. - * @param {TIdiomaticSchema} schema - The schema to convert. - * @returns {TResolve} The resolved schema. - */ - schemify(schema: T): TResolve { - if (typeof schema === 'string' || typeof schema === 'number' || typeof schema === 'boolean') { - return Type.Literal(schema) as TResolve; - } - - if (Kind in (schema as TSchema)) { - return schema as TResolve; - } - - const newSchema: TObjectShape = {}; - Object.getOwnPropertyNames(schema).forEach((key) => { - if (typeof schema[key] === 'object' && Kind in (schema[key] as TSchema)) { - newSchema[key] = schema[key] as TSchema; - } else { - const schemified = this.schemify(schema[key]); - newSchema[key] = schemified; - } - }); - - return Type.Object(newSchema) as TResolve; - } +export class TypeboxSchemaValidator + implements + SchemaValidator< + (schema: T) => TResolve, + (schema: T) => TOptional>, + (schema: T) => TArray>, + (schemas: T) => TUnion>, + (value: T) => TLiteral, + (schema: T, value: unknown) => boolean, + (schema: T) => SchemaObject + > +{ + _Type!: 'TypeBox'; + _SchemaCatchall!: TSchema; + _ValidSchemaObject!: TObject | TArray>; - /** - * Make a schema optional. - * @param {TIdiomaticSchema} schema - The schema to make optional. - * @returns {TOptional>} The optional schema. - */ - optional(schema: T): TOptional> { - if (Kind in (schema as TSchema)) { - return Type.Optional(schema as TSchema) as TOptional>; - } - const schemified = this.schemify(schema); - return Type.Optional(schemified) as TOptional>; - } + string = Type.String(); + number = Type.Number(); + bigint = Type.BigInt(); + boolean = Type.Boolean(); + date = Type.Date(); + symbol = Type.Symbol(); + empty = Type.Union([Type.Void(), Type.Null(), Type.Undefined()]); + any = Type.Any(); + unknown = Type.Unknown(); + never = Type.Never(); - /** - * Create an array schema. - * @param {TIdiomaticSchema} schema - The schema to use for array items. - * @returns {TArray>} The array schema. - */ - array(schema: T): TArray> { - if (Kind in (schema as TSchema)) { - return Type.Array(schema as TSchema) as TArray>; - } - const schemified = this.schemify(schema); - return Type.Array(schemified) as TArray>; + /** + * Convert a schema to a TypeBox schema. + * @param {TIdiomaticSchema} schema - The schema to convert. + * @returns {TResolve} The resolved schema. + */ + schemify(schema: T): TResolve { + if ( + typeof schema === 'string' || + typeof schema === 'number' || + typeof schema === 'boolean' + ) { + return Type.Literal(schema) as TResolve; } - /** - * Create a union schema. - * @param {TUnionContainer} schemas - The schemas to union. - * @returns {TUnion>} The union schema. - * - * WARNING: If "empty" or TUndefined is included in the union, the key will still be expected. - * This is a limitation of TypeBox. Consider using "optional" instead. - */ - union(schemas: T): TUnion> { - const unionTypes = schemas.map((schema) => { - if (Kind in (schema as TSchema)) { - return schema as TSchema; - } - return this.schemify(schema); - }); - - return Type.Union(unionTypes) as TUnion>; + if (Kind in (schema as TSchema)) { + return schema as TResolve; } - /** - * Create a literal schema. - * @param {LiteralSchema} value - The literal value. - * @returns {TLiteral} The literal schema. - */ - literal(value: T): TLiteral { - return Type.Literal(value); + const newSchema: TObjectShape = {}; + Object.getOwnPropertyNames(schema).forEach((key) => { + if (typeof schema[key] === 'object' && Kind in (schema[key] as TSchema)) { + newSchema[key] = schema[key] as TSchema; + } else { + const schemified = this.schemify(schema[key]); + newSchema[key] = schemified; + } + }); + + return Type.Object(newSchema) as TResolve; + } + + /** + * Make a schema optional. + * @param {TIdiomaticSchema} schema - The schema to make optional. + * @returns {TOptional>} The optional schema. + */ + optional(schema: T): TOptional> { + if (Kind in (schema as TSchema)) { + return Type.Optional(schema as TSchema) as TOptional>; } + const schemified = this.schemify(schema); + return Type.Optional(schemified) as TOptional>; + } - /** - * Validate a value against a schema. - * @param {TSchema} schema - The schema to validate against. - * @param {unknown} value - The value to validate. - * @returns {boolean} True if valid, otherwise false. - */ - validate(schema: T, value: unknown): boolean { - if (Kind in (schema as TSchema)) { - return Value.Check(schema as TSchema, value); - } - const schemified = this.schemify(schema); - return Value.Check(schemified, value); + /** + * Create an array schema. + * @param {TIdiomaticSchema} schema - The schema to use for array items. + * @returns {TArray>} The array schema. + */ + array(schema: T): TArray> { + if (Kind in (schema as TSchema)) { + return Type.Array(schema as TSchema) as TArray>; } + const schemified = this.schemify(schema); + return Type.Array(schemified) as TArray>; + } - /** - * Convert a schema to an OpenAPI schema object. - * @param {TIdiomaticSchema | TSchema} schema - The schema to convert. - * @returns {SchemaObject} The OpenAPI schema object. - */ - openapi(schema: T): SchemaObject { - return this.schemify(schema); + /** + * Create a union schema. + * @param {TUnionContainer} schemas - The schemas to union. + * @returns {TUnion>} The union schema. + * + * WARNING: If "empty" or TUndefined is included in the union, the key will still be expected. + * This is a limitation of TypeBox. Consider using "optional" instead. + */ + union(schemas: T): TUnion> { + const unionTypes = schemas.map((schema) => { + if (Kind in (schema as TSchema)) { + return schema as TSchema; + } + return this.schemify(schema); + }); + + return Type.Union(unionTypes) as TUnion>; + } + + /** + * Create a literal schema. + * @param {LiteralSchema} value - The literal value. + * @returns {TLiteral} The literal schema. + */ + literal(value: T): TLiteral { + return Type.Literal(value); + } + + /** + * Validate a value against a schema. + * @param {TSchema} schema - The schema to validate against. + * @param {unknown} value - The value to validate. + * @returns {boolean} True if valid, otherwise false. + */ + validate( + schema: T, + value: unknown + ): boolean { + if (Kind in (schema as TSchema)) { + return Value.Check(schema as TSchema, value); } + const schemified = this.schemify(schema); + return Value.Check(schemified, value); + } + + /** + * Convert a schema to an OpenAPI schema object. + * @param {TIdiomaticSchema | TSchema} schema - The schema to convert. + * @returns {SchemaObject} The OpenAPI schema object. + */ + openapi(schema: T): SchemaObject { + return this.schemify(schema); + } } /** @@ -205,34 +231,41 @@ export const never: typeof SchemaValidator.never = SchemaValidator.never; /** * Transforms valid schema into TypeBox schema. */ -export const schemify: typeof SchemaValidator.schemify = SchemaValidator.schemify.bind(SchemaValidator); +export const schemify: typeof SchemaValidator.schemify = + SchemaValidator.schemify.bind(SchemaValidator); /** * Makes a valid schema optional. */ -export const optional: typeof SchemaValidator.optional = SchemaValidator.optional.bind(SchemaValidator); +export const optional: typeof SchemaValidator.optional = + SchemaValidator.optional.bind(SchemaValidator); /** * Defines an array for a valid schema. */ -export const array: typeof SchemaValidator.array = SchemaValidator.array.bind(SchemaValidator); +export const array: typeof SchemaValidator.array = + SchemaValidator.array.bind(SchemaValidator); /** * Defines a union for a valid schema. */ -export const union: typeof SchemaValidator.union = SchemaValidator.union.bind(SchemaValidator); +export const union: typeof SchemaValidator.union = + SchemaValidator.union.bind(SchemaValidator); /** * Defines a literal for a valid schema. */ -export const literal: typeof SchemaValidator.literal = SchemaValidator.literal.bind(SchemaValidator); +export const literal: typeof SchemaValidator.literal = + SchemaValidator.literal.bind(SchemaValidator); /** * Validates a value against a valid schema. */ -export const validate: typeof SchemaValidator.validate = SchemaValidator.validate.bind(SchemaValidator); +export const validate: typeof SchemaValidator.validate = + SchemaValidator.validate.bind(SchemaValidator); /** * Generates an OpenAPI schema object from a valid schema. */ -export const openapi: typeof SchemaValidator.openapi = SchemaValidator.openapi.bind(SchemaValidator); \ No newline at end of file +export const openapi: typeof SchemaValidator.openapi = + SchemaValidator.openapi.bind(SchemaValidator); diff --git a/packages/validator/typebox/types/typebox.schema.types.ts b/packages/validator/typebox/types/typebox.schema.types.ts index 667b19faa..ac8d48b5b 100644 --- a/packages/validator/typebox/types/typebox.schema.types.ts +++ b/packages/validator/typebox/types/typebox.schema.types.ts @@ -1,4 +1,14 @@ -import { TObject as OriginalTObject, Static, TArray, TKind, TLiteral, TNever, TProperties, TSchema, TUnknown } from '@sinclair/typebox'; +import { + TObject as OriginalTObject, + Static, + TArray, + TKind, + TLiteral, + TNever, + TProperties, + TSchema, + TUnknown +} from '@sinclair/typebox'; import { Increment, KeyTypes, LiteralSchema } from '../../types/schema.types'; /** @@ -8,10 +18,11 @@ export type TCatchall = TSchema; /** * Represents an outer array schema type. If the type T is an object shape, it will return an array schema of T. Otherwise, it returns TNever. - * + * * @template T - The type to check and possibly convert to an array schema. */ -export type TOuterArray = T extends TObject ? TArray : TNever; +export type TOuterArray = + T extends TObject ? TArray : TNever; /** * Represents the shape of an object schema. @@ -20,14 +31,14 @@ export type TObjectShape = TProperties; /** * Represents an object schema type. If the type T is an object shape, it will return the original TObject type of T. Otherwise, it returns TNever. - * + * * @template T - The type to check and possibly convert to an object schema. */ export type TObject = T extends TObjectShape ? OriginalTObject : TNever; /** * Translates a schema type T to its static type if T extends TCatchall. Otherwise, it returns TNever. - * + * * @template T - The schema type to translate. */ export type TSchemaTranslate = T extends TCatchall ? Static : TNever; @@ -36,8 +47,8 @@ export type TSchemaTranslate = T extends TCatchall ? Static : TNever; * Represents an unboxed object schema where each key can have an idiomatic schema. */ export type UnboxedTObjectSchema = { - [key: KeyTypes]: TIdiomaticSchema; -} + [key: KeyTypes]: TIdiomaticSchema; +}; /** * Represents an idiomatic schema which can be an unboxed object schema or a literal schema. @@ -51,29 +62,37 @@ export type TUnionContainer = [...TIdiomaticSchema[]]; /** * Resolves a union container to a tuple of resolved idiomatic schemas. - * + * * @template T - The union container to resolve. */ export type UnionTResolve = T extends [ - ...infer A extends TIdiomaticSchema[] -] ? [ - ...{ - [K in keyof A]: TResolve - } -] : []; + ...infer A extends TIdiomaticSchema[] +] + ? [ + ...{ + [K in keyof A]: TResolve; + } + ] + : []; /** * Resolves a schema type T to its resolved type. The depth is limited to 45 to prevent infinite recursion. - * + * * @template T - The schema type to resolve. * @template Depth - The current depth of the resolution. */ -export type TResolve = Depth extends 45 ? TUnknown : - T extends LiteralSchema ? TLiteral : - T extends TObject ? T : - T extends TSchema ? T : - T extends TKind ? T : - T extends UnboxedTObjectSchema ? TObject<{ - [K in keyof T]: TResolve> - }> : - TNever; +export type TResolve = Depth extends 45 + ? TUnknown + : T extends LiteralSchema + ? TLiteral + : T extends TObject + ? T + : T extends TSchema + ? T + : T extends TKind + ? T + : T extends UnboxedTObjectSchema + ? TObject<{ + [K in keyof T]: TResolve>; + }> + : TNever; diff --git a/packages/validator/types/schema.types.ts b/packages/validator/types/schema.types.ts index 87b1de734..275f6ec1f 100644 --- a/packages/validator/types/schema.types.ts +++ b/packages/validator/types/schema.types.ts @@ -1,8 +1,10 @@ - -import { Prettify } from "@forklaunch/common"; -import { SchemaObject } from "openapi3-ts/oas31"; -import { TResolve, TSchemaTranslate } from "../typebox/types/typebox.schema.types"; -import { ZodResolve, ZodSchemaTranslate } from "../zod/types/zod.schema.types"; +import { Prettify } from '@forklaunch/common'; +import { SchemaObject } from 'openapi3-ts/oas31'; +import { + TResolve, + TSchemaTranslate +} from '../typebox/types/typebox.schema.types'; +import { ZodResolve, ZodSchemaTranslate } from '../zod/types/zod.schema.types'; /** * Interface representing a schema validator. @@ -12,161 +14,175 @@ import { ZodResolve, ZodSchemaTranslate } from "../zod/types/zod.schema.types"; * @template Catchall - The catch-all type for all schemas. */ export interface SchemaValidator< - SchematicFunction = (schema: T) => unknown, - OptionalFunction =(schema: T) => unknown, - ArrayFunction = (schema: T) => unknown, - UnionFunction = (schemas: T[]) => unknown, - LiteralFunction = (schema: T) => unknown, - ValidationFunction = (schema: T, value: unknown) => boolean, - OpenAPIFunction = (schema: T) => SchemaObject + SchematicFunction = (schema: T) => unknown, + OptionalFunction = (schema: T) => unknown, + ArrayFunction = (schema: T) => unknown, + UnionFunction = (schemas: T[]) => unknown, + LiteralFunction = (schema: T) => unknown, + ValidationFunction = (schema: T, value: unknown) => boolean, + OpenAPIFunction = (schema: T) => SchemaObject > { - _Type: unknown; - _SchemaCatchall: unknown; - _ValidSchemaObject: unknown; - - /** - * Validator for string type. - */ - string: unknown; - - /** - * Validator for number type. - */ - number: unknown; - - /** - * Validator for bigint type. - */ - bigint: unknown; - - /** - * Validator for boolean type. - */ - boolean: unknown; - - /** - * Validator for date type. - */ - date: unknown; - - /** - * Validator for symbol type. - */ - symbol: unknown; - - /** - * Validator for empty type. - */ - empty: unknown; - - /** - * Validator for any type. - */ - any: unknown; - - /** - * Validator for unknown type. - */ - unknown: unknown; - - /** - * Validator for never type. - */ - never: unknown; - - /** - * Converts a valid schema input into a schemified form. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to schemify. - * @returns {unknown} - The schemified form of the schema. - */ - schemify: SchematicFunction; - - /** - * Converts a schema into an optional schema. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to make optional. - * @returns {unknown} - The optional form of the schema. - */ - optional: OptionalFunction; - - /** - * Converts a schema into an array schema. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to convert into an array. - * @returns {unknown} - The array form of the schema. - */ - array: ArrayFunction; - - /** - * Converts multiple schemas into a union schema. - * - * @template T - The type of the union container. - * @param {T} schemas - The schemas to unionize. - * @returns {unknown} - The union form of the schemas. - */ - // union(schemas: T): unknown; - union: UnionFunction; - - - /** - * Creates a literal schema from a value. - * - * @template T - The type of the literal value. - * @param {T} value - The literal value. - * @returns {unknown} - The literal schema. - */ - literal: LiteralFunction; - - /** - * Validates a value against a schema. - * - * @template T - The type of the catch-all schema. - * @param {T} schema - The schema to validate against. - * @param {unknown} value - The value to validate. - * @returns {boolean} - Whether the value is valid according to the schema. - */ - validate: ValidationFunction; - - /** - * Converts a schema into an OpenAPI schema object. - * - * @template T - The type of the idiomatic schema. - * @param {T} schema - The schema to convert. - * @returns {SchemaObject} - The OpenAPI schema object. - */ - openapi: OpenAPIFunction; + _Type: unknown; + _SchemaCatchall: unknown; + _ValidSchemaObject: unknown; + + /** + * Validator for string type. + */ + string: unknown; + + /** + * Validator for number type. + */ + number: unknown; + + /** + * Validator for bigint type. + */ + bigint: unknown; + + /** + * Validator for boolean type. + */ + boolean: unknown; + + /** + * Validator for date type. + */ + date: unknown; + + /** + * Validator for symbol type. + */ + symbol: unknown; + + /** + * Validator for empty type. + */ + empty: unknown; + + /** + * Validator for any type. + */ + any: unknown; + + /** + * Validator for unknown type. + */ + unknown: unknown; + + /** + * Validator for never type. + */ + never: unknown; + + /** + * Converts a valid schema input into a schemified form. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to schemify. + * @returns {unknown} - The schemified form of the schema. + */ + schemify: SchematicFunction; + + /** + * Converts a schema into an optional schema. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to make optional. + * @returns {unknown} - The optional form of the schema. + */ + optional: OptionalFunction; + + /** + * Converts a schema into an array schema. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to convert into an array. + * @returns {unknown} - The array form of the schema. + */ + array: ArrayFunction; + + /** + * Converts multiple schemas into a union schema. + * + * @template T - The type of the union container. + * @param {T} schemas - The schemas to unionize. + * @returns {unknown} - The union form of the schemas. + */ + // union(schemas: T): unknown; + union: UnionFunction; + + /** + * Creates a literal schema from a value. + * + * @template T - The type of the literal value. + * @param {T} value - The literal value. + * @returns {unknown} - The literal schema. + */ + literal: LiteralFunction; + + /** + * Validates a value against a schema. + * + * @template T - The type of the catch-all schema. + * @param {T} schema - The schema to validate against. + * @param {unknown} value - The value to validate. + * @returns {boolean} - Whether the value is valid according to the schema. + */ + validate: ValidationFunction; + + /** + * Converts a schema into an OpenAPI schema object. + * + * @template T - The type of the idiomatic schema. + * @param {T} schema - The schema to convert. + * @returns {SchemaObject} - The OpenAPI schema object. + */ + openapi: OpenAPIFunction; } -export type AnySchemaValidator = SchemaValidator; +export type AnySchemaValidator = SchemaValidator< + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown +>; interface SchemaResolve { - Zod: ZodResolve, - TypeBox: TResolve + Zod: ZodResolve; + TypeBox: TResolve; } interface SchemaTranslate { - Zod: ZodSchemaTranslate; - TypeBox: TSchemaTranslate; -} - -type SchemaPrettify = SV['_Type'] extends keyof SchemaTranslate ? Prettify[SV['_Type']]> : never; + Zod: ZodSchemaTranslate; + TypeBox: TSchemaTranslate; +} -export type Schema, SV extends AnySchemaValidator> = SV['_Type'] extends keyof SchemaResolve ? - SchemaPrettify[SV['_Type']], SV> +type SchemaPrettify< + T, + SV extends AnySchemaValidator +> = SV['_Type'] extends keyof SchemaTranslate + ? Prettify[SV['_Type']]> : never; +export type Schema< + T extends SV['_ValidSchemaObject'] | IdiomaticSchema, + SV extends AnySchemaValidator +> = SV['_Type'] extends keyof SchemaResolve + ? SchemaPrettify[SV['_Type']], SV> + : never; /** * Represents a schema for an unboxed object where each key can have an idiomatic schema. - * + * * @template Catchall - The type to use for catch-all cases in the schema. */ export type UnboxedObjectSchema = { - [key: KeyTypes]: IdiomaticSchema; + [key: KeyTypes]: IdiomaticSchema; }; /** @@ -176,68 +192,120 @@ export type LiteralSchema = string | number | boolean; /** * Represents an idiomatic schema which can be an unboxed object schema, a literal schema, or a catch-all type. - * + * * @template Catchall - The type to use for catch-all cases in the schema. */ -export type IdiomaticSchema = UnboxedObjectSchema | LiteralSchema | SV['_SchemaCatchall']; +export type IdiomaticSchema = + | UnboxedObjectSchema + | LiteralSchema + | SV['_SchemaCatchall']; /** * Increments a number type by one, with support up to 50. - * + * * @template T - The number type to increment. */ -export type Increment = - T extends 0 ? 1 : - T extends 1 ? 2 : - T extends 2 ? 3 : - T extends 3 ? 4 : - T extends 4 ? 5 : - T extends 5 ? 6 : - T extends 6 ? 7 : - T extends 7 ? 8 : - T extends 8 ? 9 : - T extends 9 ? 10 : - T extends 10 ? 11 : - T extends 11 ? 12 : - T extends 12 ? 13 : - T extends 13 ? 14 : - T extends 14 ? 15 : - T extends 15 ? 16 : - T extends 16 ? 17 : - T extends 17 ? 18 : - T extends 18 ? 19 : - T extends 19 ? 20 : - T extends 20 ? 21 : - T extends 21 ? 22 : - T extends 22 ? 23 : - T extends 23 ? 24 : - T extends 24 ? 25 : - T extends 25 ? 26 : - T extends 26 ? 27 : - T extends 27 ? 28 : - T extends 28 ? 29 : - T extends 29 ? 30 : - T extends 30 ? 31 : - T extends 31 ? 32 : - T extends 32 ? 33 : - T extends 33 ? 34 : - T extends 34 ? 35 : - T extends 35 ? 36 : - T extends 36 ? 37 : - T extends 37 ? 38 : - T extends 38 ? 39 : - T extends 39 ? 40 : - T extends 40 ? 41 : - T extends 41 ? 42 : - T extends 42 ? 43 : - T extends 43 ? 44 : - T extends 44 ? 45 : - T extends 45 ? 46 : - T extends 46 ? 47 : - T extends 47 ? 48 : - T extends 48 ? 49 : - T extends 49 ? 50 : - 50; +export type Increment = T extends 0 + ? 1 + : T extends 1 + ? 2 + : T extends 2 + ? 3 + : T extends 3 + ? 4 + : T extends 4 + ? 5 + : T extends 5 + ? 6 + : T extends 6 + ? 7 + : T extends 7 + ? 8 + : T extends 8 + ? 9 + : T extends 9 + ? 10 + : T extends 10 + ? 11 + : T extends 11 + ? 12 + : T extends 12 + ? 13 + : T extends 13 + ? 14 + : T extends 14 + ? 15 + : T extends 15 + ? 16 + : T extends 16 + ? 17 + : T extends 17 + ? 18 + : T extends 18 + ? 19 + : T extends 19 + ? 20 + : T extends 20 + ? 21 + : T extends 21 + ? 22 + : T extends 22 + ? 23 + : T extends 23 + ? 24 + : T extends 24 + ? 25 + : T extends 25 + ? 26 + : T extends 26 + ? 27 + : T extends 27 + ? 28 + : T extends 28 + ? 29 + : T extends 29 + ? 30 + : T extends 30 + ? 31 + : T extends 31 + ? 32 + : T extends 32 + ? 33 + : T extends 33 + ? 34 + : T extends 34 + ? 35 + : T extends 35 + ? 36 + : T extends 36 + ? 37 + : T extends 37 + ? 38 + : T extends 38 + ? 39 + : T extends 39 + ? 40 + : T extends 40 + ? 41 + : T extends 41 + ? 42 + : T extends 42 + ? 43 + : T extends 43 + ? 44 + : T extends 44 + ? 45 + : T extends 45 + ? 46 + : T extends 46 + ? 47 + : T extends 47 + ? 48 + : T extends 48 + ? 49 + : T extends 49 + ? 50 + : 50; /** * Represents key types that can be used in the schema. diff --git a/packages/validator/zod/index.ts b/packages/validator/zod/index.ts index a891ea27d..9ec67a395 100644 --- a/packages/validator/zod/index.ts +++ b/packages/validator/zod/index.ts @@ -1,141 +1,169 @@ /** * This module provides a Zod-based schema definition. * It includes various types, schema creation, validation, and OpenAPI integration. - * + * * @module ZodSchemaValidator */ import { generateSchema } from '@anatine/zod-openapi'; import { SchemaObject } from 'openapi3-ts/oas31'; -import { ZodArray, ZodLiteral, ZodObject, ZodOptional, ZodRawShape, ZodType, ZodUnion, z } from "zod"; -import { LiteralSchema, SchemaValidator } from "../types/schema.types"; -import { UnionZodResolve, ZodCatchall, ZodIdiomaticSchema, ZodResolve, ZodUnionContainer } from "./types/zod.schema.types"; +import { + ZodArray, + ZodLiteral, + ZodObject, + ZodOptional, + ZodRawShape, + ZodType, + ZodUnion, + z +} from 'zod'; +import { LiteralSchema, SchemaValidator } from '../types/schema.types'; +import { + UnionZodResolve, + ZodCatchall, + ZodIdiomaticSchema, + ZodResolve, + ZodUnionContainer +} from './types/zod.schema.types'; /** * Class representing a Zod schema definition. * @implements {SchemaValidator} */ -export class ZodSchemaValidator implements SchemaValidator< - (schema: T) => ZodResolve, - (schema: T) => ZodOptional>, - (schema: T) => ZodArray>, - (schemas: T) => ZodUnion>, - (value: T) => ZodLiteral>, - (schema: T, value: unknown) => boolean, - (schema: T) => SchemaObject -> { - _Type!: 'Zod'; - _SchemaCatchall!: ZodType; - _ValidSchemaObject!: ZodObject | ZodArray>; - - string = z.string(); - number = z.number(); - bigint = z.bigint(); - boolean = z.boolean(); - date = z.date(); - symbol = z.symbol(); - empty = z.union([z.void(), z.null(), z.undefined()]); - any = z.any(); - unknown = z.unknown(); - never = z.never(); - - /** - * Convert a schema to a Zod schema. - * @param {ZodIdiomaticSchema} schema - The schema to convert. - * @returns {ZodResolve} The resolved schema. - */ - schemify(schema: T): ZodResolve { - if (typeof schema === 'string' || typeof schema === 'number' || typeof schema === 'boolean') { - return z.literal(schema) as ZodResolve; - } - - if (schema instanceof ZodType) { - return schema as ZodResolve; - } - - const newSchema: ZodRawShape = {}; - Object.getOwnPropertyNames(schema).forEach((key) => { - if (schema[key] instanceof ZodType) { - newSchema[key] = schema[key] as unknown as ZodType; - } else { - newSchema[key] = this.schemify(schema[key]); - } - }); - - return z.object(newSchema) as ZodResolve; +export class ZodSchemaValidator + implements + SchemaValidator< + (schema: T) => ZodResolve, + (schema: T) => ZodOptional>, + (schema: T) => ZodArray>, + (schemas: T) => ZodUnion>, + (value: T) => ZodLiteral>, + (schema: T, value: unknown) => boolean, + (schema: T) => SchemaObject + > +{ + _Type!: 'Zod'; + _SchemaCatchall!: ZodType; + _ValidSchemaObject!: + | ZodObject + | ZodArray>; + + string = z.string(); + number = z.number(); + bigint = z.bigint(); + boolean = z.boolean(); + date = z.date(); + symbol = z.symbol(); + empty = z.union([z.void(), z.null(), z.undefined()]); + any = z.any(); + unknown = z.unknown(); + never = z.never(); + + /** + * Convert a schema to a Zod schema. + * @param {ZodIdiomaticSchema} schema - The schema to convert. + * @returns {ZodResolve} The resolved schema. + */ + schemify(schema: T): ZodResolve { + if ( + typeof schema === 'string' || + typeof schema === 'number' || + typeof schema === 'boolean' + ) { + return z.literal(schema) as ZodResolve; } - /** - * Make a schema optional. - * @param {ZodIdiomaticSchema} schema - The schema to make optional. - * @returns {ZodOptional>} The optional schema. - */ - optional(schema: T): ZodOptional> { - if (schema instanceof ZodType) { - return schema.optional() as ZodOptional>; - } - return this.schemify(schema).optional() as ZodOptional>; + if (schema instanceof ZodType) { + return schema as ZodResolve; } - /** - * Create an array schema. - * @param {ZodIdiomaticSchema} schema - The schema to use for array items. - * @returns {ZodArray>} The array schema. - */ - array(schema: T): ZodArray> { - if (schema instanceof ZodType) { - return schema.array() as ZodArray>; - } - return this.schemify(schema).array() as ZodArray>; + const newSchema: ZodRawShape = {}; + Object.getOwnPropertyNames(schema).forEach((key) => { + if (schema[key] instanceof ZodType) { + newSchema[key] = schema[key] as unknown as ZodType; + } else { + newSchema[key] = this.schemify(schema[key]); + } + }); + + return z.object(newSchema) as ZodResolve; + } + + /** + * Make a schema optional. + * @param {ZodIdiomaticSchema} schema - The schema to make optional. + * @returns {ZodOptional>} The optional schema. + */ + optional( + schema: T + ): ZodOptional> { + if (schema instanceof ZodType) { + return schema.optional() as ZodOptional>; } - - /** - * Create a union schema. - * @param {ZodUnionContainer} schemas - The schemas to union. - * @returns {ZodUnion>} The union schema. - */ - union(schemas: T): ZodUnion> { - if (schemas.length < 2) { - throw new Error('Union must have at least two schemas'); - } - - const unionTypes = schemas.map((schema) => { - if (schema instanceof ZodType) { - return schema; - } - return this.schemify(schema); - }); - - return z.union(unionTypes as unknown as [ZodType, ZodType, ...ZodType[]]) as ZodUnion>; + return this.schemify(schema).optional() as ZodOptional>; + } + + /** + * Create an array schema. + * @param {ZodIdiomaticSchema} schema - The schema to use for array items. + * @returns {ZodArray>} The array schema. + */ + array(schema: T): ZodArray> { + if (schema instanceof ZodType) { + return schema.array() as ZodArray>; } - - /** - * Create a literal schema. - * @param {LiteralSchema} value - The literal value. - * @returns {ZodLiteral>} The literal schema. - */ - literal(value: T): ZodLiteral> { - return z.literal(value) as ZodLiteral>; + return this.schemify(schema).array() as ZodArray>; + } + + /** + * Create a union schema. + * @param {ZodUnionContainer} schemas - The schemas to union. + * @returns {ZodUnion>} The union schema. + */ + union(schemas: T): ZodUnion> { + if (schemas.length < 2) { + throw new Error('Union must have at least two schemas'); } - /** - * Validate a value against a schema. - * @param {ZodCatchall} schema - The schema to validate against. - * @param {unknown} value - The value to validate. - * @returns {boolean} True if valid, otherwise false. - */ - validate(schema: T, value: unknown): boolean { - return schema.safeParse(value).success; - } - - /** - * Convert a schema to an OpenAPI schema object. - * @param {ZodIdiomaticSchema} schema - The schema to convert. - * @returns {SchemaObject} The OpenAPI schema object. - */ - openapi(schema: T): SchemaObject { - return generateSchema(this.schemify(schema)); - } + const unionTypes = schemas.map((schema) => { + if (schema instanceof ZodType) { + return schema; + } + return this.schemify(schema); + }); + + return z.union( + unionTypes as unknown as [ZodType, ZodType, ...ZodType[]] + ) as ZodUnion>; + } + + /** + * Create a literal schema. + * @param {LiteralSchema} value - The literal value. + * @returns {ZodLiteral>} The literal schema. + */ + literal(value: T): ZodLiteral> { + return z.literal(value) as ZodLiteral>; + } + + /** + * Validate a value against a schema. + * @param {ZodCatchall} schema - The schema to validate against. + * @param {unknown} value - The value to validate. + * @returns {boolean} True if valid, otherwise false. + */ + validate(schema: T, value: unknown): boolean { + return schema.safeParse(value).success; + } + + /** + * Convert a schema to an OpenAPI schema object. + * @param {ZodIdiomaticSchema} schema - The schema to convert. + * @returns {SchemaObject} The OpenAPI schema object. + */ + openapi(schema: T): SchemaObject { + return generateSchema(this.schemify(schema)); + } } /** @@ -199,34 +227,41 @@ export const never: typeof SchemaValidator.never = SchemaValidator.never; /** * Transforms valid schema into Zod schema. */ -export const schemify: typeof SchemaValidator.schemify = SchemaValidator.schemify.bind(SchemaValidator); +export const schemify: typeof SchemaValidator.schemify = + SchemaValidator.schemify.bind(SchemaValidator); /** * Makes a valid schema optional. */ -export const optional: typeof SchemaValidator.optional = SchemaValidator.optional.bind(SchemaValidator); +export const optional: typeof SchemaValidator.optional = + SchemaValidator.optional.bind(SchemaValidator); /** * Defines an array for a valid schema. */ -export const array: typeof SchemaValidator.array = SchemaValidator.array.bind(SchemaValidator); +export const array: typeof SchemaValidator.array = + SchemaValidator.array.bind(SchemaValidator); /** * Defines a union for a valid schema. */ -export const union: typeof SchemaValidator.union = SchemaValidator.union.bind(SchemaValidator); +export const union: typeof SchemaValidator.union = + SchemaValidator.union.bind(SchemaValidator); /** * Defines a literal for a valid schema. */ -export const literal: typeof SchemaValidator.literal = SchemaValidator.literal.bind(SchemaValidator); +export const literal: typeof SchemaValidator.literal = + SchemaValidator.literal.bind(SchemaValidator); /** * Validates a value against a valid schema. */ -export const validate: typeof SchemaValidator.validate = SchemaValidator.validate.bind(SchemaValidator); +export const validate: typeof SchemaValidator.validate = + SchemaValidator.validate.bind(SchemaValidator); /** * Generates an OpenAPI schema object from a valid schema. */ -export const openapi: typeof SchemaValidator.openapi = SchemaValidator.openapi.bind(SchemaValidator); \ No newline at end of file +export const openapi: typeof SchemaValidator.openapi = + SchemaValidator.openapi.bind(SchemaValidator); diff --git a/packages/validator/zod/types/zod.schema.types.ts b/packages/validator/zod/types/zod.schema.types.ts index f46f1fe0a..1bbabab97 100644 --- a/packages/validator/zod/types/zod.schema.types.ts +++ b/packages/validator/zod/types/zod.schema.types.ts @@ -1,6 +1,21 @@ -import { ZodObject as OriginalZodObject, ZodArray, ZodLiteral, ZodNever, ZodRawShape, ZodType, ZodTypeAny, ZodUnknown, z } from "zod"; -import { ZodSchemaValidator } from ".."; -import { IdiomaticSchema, Increment, LiteralSchema, UnboxedObjectSchema } from "../../types/schema.types"; +import { + ZodObject as OriginalZodObject, + ZodArray, + ZodLiteral, + ZodNever, + ZodRawShape, + ZodType, + ZodTypeAny, + ZodUnknown, + z +} from 'zod'; +import { ZodSchemaValidator } from '..'; +import { + IdiomaticSchema, + Increment, + LiteralSchema, + UnboxedObjectSchema +} from '../../types/schema.types'; /** * Represents a catch-all Zod schema type. @@ -9,10 +24,11 @@ export type ZodCatchall = ZodTypeAny; /** * Represents an outer array schema type for Zod. If the type T is a Zod object, it will return an array schema of T. Otherwise, it returns ZodNever. - * + * * @template T - The type to check and possibly convert to an array schema. */ -export type ZodOuterArray = T extends ZodObject ? ZodArray : ZodNever; +export type ZodOuterArray = + T extends ZodObject ? ZodArray : ZodNever; /** * Represents the shape of a Zod object schema. @@ -21,17 +37,21 @@ export type ZodObjectShape = ZodRawShape; /** * Represents a Zod object schema type. If the type T is a Zod object shape, it will return the original ZodObject type of T. Otherwise, it returns ZodNever. - * + * * @template T - The type to check and possibly convert to a Zod object schema. */ -export type ZodObject = T extends ZodObjectShape ? OriginalZodObject : ZodNever; +export type ZodObject = T extends ZodObjectShape + ? OriginalZodObject + : ZodNever; /** * Translates a Zod schema type T to its static type if T extends ZodCatchall. Otherwise, it returns ZodNever. - * + * * @template T - The Zod schema type to translate. */ -export type ZodSchemaTranslate = T extends ZodCatchall ? z.infer : ZodNever; +export type ZodSchemaTranslate = T extends ZodCatchall + ? z.infer + : ZodNever; /** * Represents an unboxed Zod object schema where each key can have an idiomatic schema. @@ -46,35 +66,45 @@ export type ZodIdiomaticSchema = IdiomaticSchema; /** * Represents a container for a union of Zod idiomatic schemas. */ -export type ZodUnionContainer = readonly [ZodIdiomaticSchema, ZodIdiomaticSchema, ...ZodIdiomaticSchema[]]; +export type ZodUnionContainer = readonly [ + ZodIdiomaticSchema, + ZodIdiomaticSchema, + ...ZodIdiomaticSchema[] +]; /** * Resolves a union container to a tuple of resolved Zod idiomatic schemas. - * + * * @template T - The union container to resolve. */ export type UnionZodResolve = T extends [ - infer A extends ZodIdiomaticSchema, - infer B extends ZodIdiomaticSchema, - ...infer C extends ZodIdiomaticSchema[] -] ? [ - ZodResolve, - ZodResolve, - ...{ - [K in keyof C]: ZodResolve - } -] : [ZodNever, ZodNever]; + infer A extends ZodIdiomaticSchema, + infer B extends ZodIdiomaticSchema, + ...infer C extends ZodIdiomaticSchema[] +] + ? [ + ZodResolve, + ZodResolve, + ...{ + [K in keyof C]: ZodResolve; + } + ] + : [ZodNever, ZodNever]; /** * Resolves a Zod schema type T to its resolved type. The depth is limited to 31 to prevent infinite recursion. - * + * * @template T - The Zod schema type to resolve. * @template Depth - The current depth of the resolution. */ -export type ZodResolve = Depth extends 31 ? ZodUnknown : - T extends LiteralSchema ? ZodLiteral : - T extends ZodType ? T : - T extends ZodObjectSchema ? ZodObject<{ - [K in keyof T]: ZodResolve> - }> : - ZodNever; +export type ZodResolve = Depth extends 31 + ? ZodUnknown + : T extends LiteralSchema + ? ZodLiteral + : T extends ZodType + ? T + : T extends ZodObjectSchema + ? ZodObject<{ + [K in keyof T]: ZodResolve>; + }> + : ZodNever; From b4979b35bbb1d9a95afaea62f84409ee4719c46e Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 19:42:20 -0500 Subject: [PATCH 09/13] refactor and prettier added to core --- .../models/baseEntityMapper.model.ts | 15 ++++----- .../models/requestEntityMapper.model.ts | 6 ++-- .../models/responseEntityMapper.model.ts | 4 +-- .../entityMapper/types/entityMapper.types.ts | 4 +-- .../http/middlewares/request.middleware.ts | 25 +++++++-------- packages/core/http/types/api.types.ts | 15 ++++----- packages/core/http/types/primitive.types.ts | 32 ++++++++++--------- packages/core/package-lock.json | 24 +++++++++++--- packages/core/package.json | 8 +++-- packages/core/tests/http.middleware.test.ts | 15 +++++++-- 10 files changed, 88 insertions(+), 60 deletions(-) diff --git a/packages/core/entityMapper/models/baseEntityMapper.model.ts b/packages/core/entityMapper/models/baseEntityMapper.model.ts index 03399d3b7..ecd6aedc0 100644 --- a/packages/core/entityMapper/models/baseEntityMapper.model.ts +++ b/packages/core/entityMapper/models/baseEntityMapper.model.ts @@ -1,5 +1,4 @@ -import { AnySchemaValidator, Schema } from "@forklaunch/validator"; -import { SchemaValidator } from "@forklaunch/validator/interfaces"; +import { AnySchemaValidator, Schema, SchemaValidator } from "@forklaunch/validator"; import { EntityMapperConstructor } from "../interfaces/entityMapper.interface"; import { EntityMapperSchemaValidatorObject } from "../types/entityMapper.types"; @@ -21,11 +20,11 @@ export function construct(self: EntityMapperCo * @template SV - A type that extends SchemaValidator. */ export abstract class BaseEntityMapper { - /** - * The schema validator exact type. - * @type {SV} - * @protected - */ + /** + * The schema validator exact type. + * @type {SV} + * @protected + */ _SV!: SV; /** @@ -88,7 +87,7 @@ export abstract class BaseEntityMapper { * @param {EntityMapperConstructor} this - The constructor of the T. * @returns {T['schema']} - The schema of the T. */ - static schema>(this: EntityMapperConstructor): T['schema'] { + static schema, SV extends AnySchemaValidator>(this: EntityMapperConstructor): T['schema'] { return construct(this).schema; } } \ No newline at end of file diff --git a/packages/core/entityMapper/models/requestEntityMapper.model.ts b/packages/core/entityMapper/models/requestEntityMapper.model.ts index b5fe5c4e5..c4dde9b35 100644 --- a/packages/core/entityMapper/models/requestEntityMapper.model.ts +++ b/packages/core/entityMapper/models/requestEntityMapper.model.ts @@ -62,7 +62,7 @@ export abstract class RequestEntityMapper>(this: EntityMapperConstructor, schemaValidator: T['_SV'], json: T['_dto']): T { + static fromJson, SV extends AnySchemaValidator, JsonType extends T['_dto']>(this: EntityMapperConstructor, schemaValidator: SV, json: JsonType): T { return construct(this, schemaValidator).fromJson(json); } @@ -76,7 +76,7 @@ export abstract class RequestEntityMapper>(this: EntityMapperConstructor, schemaValidator: T['_SV'], json: T['_dto'], ...additionalArgs: unknown[]): T['_Entity'] { - return construct(this, schemaValidator).fromJson(json as T['_dto']).toEntity(...additionalArgs); + static deserializeJsonToEntity, SV extends AnySchemaValidator, JsonType extends T['_dto']>(this: EntityMapperConstructor, schemaValidator: SV, json: JsonType, ...additionalArgs: unknown[]): T['_Entity'] { + return construct(this, schemaValidator).fromJson(json).toEntity(...additionalArgs); } } diff --git a/packages/core/entityMapper/models/responseEntityMapper.model.ts b/packages/core/entityMapper/models/responseEntityMapper.model.ts index 608f46232..16412492b 100644 --- a/packages/core/entityMapper/models/responseEntityMapper.model.ts +++ b/packages/core/entityMapper/models/responseEntityMapper.model.ts @@ -58,7 +58,7 @@ export abstract class ResponseEntityMapper>(this: EntityMapperConstructor, schemaValidator: T['_SV'], entity: T['_Entity']): T { + static fromEntity, SV extends AnySchemaValidator>(this: EntityMapperConstructor, schemaValidator: SV, entity: T['_Entity']): T { return construct(this, schemaValidator).fromEntity(entity); } @@ -70,7 +70,7 @@ export abstract class ResponseEntityMapper>(this: EntityMapperConstructor, schemaValidator: T['_SV'], entity: T['_Entity']): T['_dto'] { + static serializeEntityToJson, SV extends AnySchemaValidator>(this: EntityMapperConstructor, schemaValidator: SV, entity: T['_Entity']): T['_dto'] { return construct(this, schemaValidator).serializeEntityToJson(entity); } } diff --git a/packages/core/entityMapper/types/entityMapper.types.ts b/packages/core/entityMapper/types/entityMapper.types.ts index 3087ff94b..16de7ce5b 100644 --- a/packages/core/entityMapper/types/entityMapper.types.ts +++ b/packages/core/entityMapper/types/entityMapper.types.ts @@ -1,4 +1,4 @@ -import { AnySchemaValidator, SchemaCatchall, ValidSchemaObject } from "@forklaunch/validator/"; +import { AnySchemaValidator } from "@forklaunch/validator"; import { UnboxedObjectSchema } from "@forklaunch/validator/types"; /** @@ -7,4 +7,4 @@ import { UnboxedObjectSchema } from "@forklaunch/validator/types"; * @template SV - A type that extends SchemaValidator. * @typedef {ValidSchemaObject | UnboxedObjectSchema> & {}} EntityMapperSchemaValidatorObject */ -export type EntityMapperSchemaValidatorObject = ValidSchemaObject | UnboxedObjectSchema>; \ No newline at end of file +export type EntityMapperSchemaValidatorObject = SV['_ValidSchemaObject'] | UnboxedObjectSchema; \ No newline at end of file diff --git a/packages/core/http/middlewares/request.middleware.ts b/packages/core/http/middlewares/request.middleware.ts index 6835145d7..06d60b238 100644 --- a/packages/core/http/middlewares/request.middleware.ts +++ b/packages/core/http/middlewares/request.middleware.ts @@ -1,5 +1,4 @@ -import { AnySchemaValidator } from "@forklaunch/validator"; -import { SchemaValidator } from "@forklaunch/validator/interfaces"; +import { AnySchemaValidator, SchemaValidator } from "@forklaunch/validator"; import * as jose from "jose"; import { v4 } from "uuid"; import { ForklaunchNextFunction, ForklaunchRequest, ForklaunchResponse } from "../types/api.types"; @@ -61,7 +60,7 @@ export function preHandlerParse(schemaValidator: export function parseRequestParams< SV extends AnySchemaValidator, Request extends ForklaunchRequest, - Response extends ForklaunchResponse, + Response extends ForklaunchResponse, NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { const params = req.contractDetails.params; @@ -79,7 +78,7 @@ export function parseRequestParams< export function parseRequestBody< SV extends AnySchemaValidator, Request extends ForklaunchRequest, - Response extends ForklaunchResponse, + Response extends ForklaunchResponse, NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { if (req.headers['content-type'] === 'application/json') { @@ -99,9 +98,9 @@ export function parseRequestBody< export function parseRequestHeaders< SV extends AnySchemaValidator, Request extends ForklaunchRequest, - Response extends ForklaunchResponse, + Response extends ForklaunchResponse, NextFunction extends ForklaunchNextFunction -> (req: Request, res: Response, next?: NextFunction) { +>(req: Request, res: Response, next?: NextFunction) { const headers = req.contractDetails.requestHeaders; if (preHandlerParse(req.schemaValidator, req.headers, headers) === 400) { res.status(400).send("Invalid request headers."); @@ -117,7 +116,7 @@ export function parseRequestHeaders< export function parseRequestQuery< SV extends AnySchemaValidator, Request extends ForklaunchRequest, - Response extends ForklaunchResponse, + Response extends ForklaunchResponse, NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { const query = req.contractDetails.query; @@ -144,7 +143,7 @@ async function checkAuthorizationToken(authorizationMethod?: AuthMethod, authori try { const decodedJwt = await jose.jwtVerify(authorizationString.split(' ')[1], new TextEncoder().encode(process.env.JWT_SECRET || 'your-256-bit-secret')); return decodedJwt.payload.iss; - } catch(error) { + } catch (error) { console.error(error); return [403, "Invalid Authorization token."]; } @@ -164,7 +163,7 @@ function mapPermissions(authorizationType?: AuthMethod, authorizationToken?: str export async function parseRequestAuth< SV extends AnySchemaValidator, Request extends ForklaunchRequest, - Response extends ForklaunchResponse, + Response extends ForklaunchResponse, NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { const auth = req.contractDetails.auth; @@ -178,7 +177,7 @@ export async function parseRequestAuth< } // TODO: Implement role and permission checking - const permissionSlugs = mapPermissions(auth.method, req.headers.authorization); + const permissionSlugs = mapPermissions(auth.method, req.headers.authorization); const roles = mapRoles(auth.method, req.headers.authorization); const permissionErrorMessage = "User does not have sufficient permissions to perform action."; @@ -192,7 +191,7 @@ export async function parseRequestAuth< if (next) { next(new Error(permissionErrorMessage)); } - } + } }); roles.forEach(role => { if (!req.contractDetails.auth?.allowedRoles?.has(role) || req.contractDetails.auth?.forbiddenRoles?.has(role)) { @@ -200,10 +199,10 @@ export async function parseRequestAuth< if (next) { next(new Error(roleErrorMessage)); } - } + } }); } - + // if (next) { // next(); // } diff --git a/packages/core/http/types/api.types.ts b/packages/core/http/types/api.types.ts index bf5bf2349..3d30c7ff8 100644 --- a/packages/core/http/types/api.types.ts +++ b/packages/core/http/types/api.types.ts @@ -1,6 +1,5 @@ import { Prettify } from "@forklaunch/common"; -import { AnySchemaValidator, Schema, SchemaCatchall, ValidSchemaObject } from "@forklaunch/validator"; -import { SchemaValidator } from "@forklaunch/validator/interfaces"; +import { AnySchemaValidator, Schema, SchemaValidator } from "@forklaunch/validator"; import { IdiomaticSchema } from "@forklaunch/validator/types"; import { IncomingHttpHeaders, OutgoingHttpHeader } from "http"; import { ParsedQs } from "qs"; @@ -40,7 +39,7 @@ export interface ForklaunchResponse< bodyData: unknown; statusCode: StatusCode; corked: boolean; - + getHeaders: () => OutgoingHttpHeader; setHeader: (key: string, value: string) => void; status: { @@ -62,9 +61,9 @@ export interface ForklaunchResponse< (body?: ResBody): T; } } -export type MapSchema> | ValidSchemaObject> = Schema extends infer U ? -{ [key: string]: unknown } extends U ? - never : - U : -never; +export type MapSchema | SV['_ValidSchemaObject']> = Schema extends infer U ? + { [key: string]: unknown } extends U ? + never : + U : + never; export type ForklaunchNextFunction = (err?: unknown) => void; \ No newline at end of file diff --git a/packages/core/http/types/primitive.types.ts b/packages/core/http/types/primitive.types.ts index 4e7e5b56a..e2bca41d9 100644 --- a/packages/core/http/types/primitive.types.ts +++ b/packages/core/http/types/primitive.types.ts @@ -1,28 +1,30 @@ -import { AnySchemaValidator, SchemaCatchall, ValidSchemaObject } from "@forklaunch/validator"; +import { AnySchemaValidator } from "@forklaunch/validator"; import { UnboxedObjectSchema } from "@forklaunch/validator/types"; export type ParamsDictionary = { [key: string]: string; }; -export type StringOnlyObject = Omit>, number | symbol>; -export type NumberOnlyObject = Omit>, string | symbol>; +export type StringOnlyObject = Omit, number | symbol>; +export type NumberOnlyObject = Omit, string | symbol>; export type BodyObject = StringOnlyObject & unknown; export type ParamsObject = StringOnlyObject & unknown; export type QueryObject = StringOnlyObject & unknown; export type HeadersObject = StringOnlyObject & unknown; -export type ResponsesObject = NumberOnlyObject & unknown; +export type ResponsesObject = { + [key: number]: SV['_ValidSchemaObject'] | UnboxedObjectSchema | string | SV['string']; +} & unknown; export type Body = BodyObject - | ValidSchemaObject - | SchemaCatchall; + | SV['_ValidSchemaObject'] + | SV['_SchemaCatchall']; export type AuthMethod = 'jwt' | 'session'; export interface PathParamHttpContractDetails< SV extends AnySchemaValidator, - ParamSchemas extends ParamsObject = ParamsObject, - ResponseSchemas extends ResponsesObject = ResponsesObject, + ParamSchemas extends ParamsObject = ParamsObject, + ResponseSchemas extends ResponsesObject = ResponsesObject, QuerySchemas extends QueryObject = QueryObject -> { +> { name: string, summary: string, responses: ResponseSchemas, @@ -41,14 +43,14 @@ export interface PathParamHttpContractDetails< export interface HttpContractDetails< SV extends AnySchemaValidator, - ParamSchemas extends ParamsObject = ParamsObject, - ResponseSchemas extends ResponsesObject = ResponsesObject, - BodySchema extends Body = Body, + ParamSchemas extends ParamsObject = ParamsObject, + ResponseSchemas extends ResponsesObject = ResponsesObject, + BodySchema extends Body = Body, QuerySchemas extends QueryObject = QueryObject > extends PathParamHttpContractDetails { body?: BodySchema, contentType?: - | 'application/json' - | 'multipart/form-data' - | 'application/x-www-form-urlencoded'; + | 'application/json' + | 'multipart/form-data' + | 'application/x-www-form-urlencoded'; } diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index b47f511d4..aa35c78fc 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.1", "license": "MIT", "dependencies": { - "@forklaunch/validator": "^0.2.4", + "@forklaunch/validator": "^0.2.6", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", @@ -23,6 +23,7 @@ "@types/redis": "^4.0.11", "@types/uuid": "^10.0.0", "globals": "^15.8.0", + "prettier": "^3.3.2", "testcontainers": "^10.10.1", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", @@ -797,9 +798,9 @@ "integrity": "sha512-ThzqAO97Hk5PZYjtDyokoQFG7Ktq5Kjbyr3zRP4LslzOxe+wMPcbrm3wiQDabV2liQR/BZYXYi5m3RkmxlmaeA==" }, "node_modules/@forklaunch/validator": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.4.tgz", - "integrity": "sha512-BtUW5DTpxMXQ8HgptWIiFge0q7Dv85vDqQqH1CefAVAQbojb8gVTiPkznATKpD0QDFxb1vaYRo65UHz2axAsKQ==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.6.tgz", + "integrity": "sha512-sfbXZztOgkfVqikOpn7Y5DTvw1XNVlpJNlsbr+y4n6Y3vRi6+Xc3ejgrnYYfALgFfGnhuRMUXJ7xH3k/0Cooag==", "dependencies": { "@anatine/zod-openapi": "^2.2.6", "@forklaunch/common": "^0.1.2", @@ -5072,6 +5073,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index 34efcfaa0..a2056f20f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -8,7 +8,8 @@ "build": "tsc", "docs": "typedoc --out docs *", "lint": "eslint . -c eslint.config.mjs", - "lint:fix": "eslint . -c eslint.config.mjs --fix" + "lint:fix": "eslint . -c eslint.config.mjs --fix", + "format": "prettier --ignore-path=.prettierignore --config .prettierrc '**/*.ts' --write" }, "author": "Rohin Bhargava", "license": "MIT", @@ -21,7 +22,7 @@ }, "homepage": "https://github.com/forklaunch/forklaunch-js#readme", "dependencies": { - "@forklaunch/validator": "^0.2.4", + "@forklaunch/validator": "^0.2.6", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", @@ -35,6 +36,7 @@ "@types/redis": "^4.0.11", "@types/uuid": "^10.0.0", "globals": "^15.8.0", + "prettier": "^3.3.2", "testcontainers": "^10.10.1", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", @@ -45,4 +47,4 @@ "directories": { "test": "tests" } -} +} \ No newline at end of file diff --git a/packages/core/tests/http.middleware.test.ts b/packages/core/tests/http.middleware.test.ts index 80d99e01a..bee551bbe 100644 --- a/packages/core/tests/http.middleware.test.ts +++ b/packages/core/tests/http.middleware.test.ts @@ -1,10 +1,21 @@ +import { MockSchemaValidator } from "@forklaunch/validator/tests/mockSchemaValidator"; import { HttpContractDetails } from "../http"; +declare module '@forklaunch/validator' { +} + describe('Http Middleware Tests', () => { let contractDetails: HttpContractDetails beforeAll(() => { contractDetails = { - + name: 'Test Contract', + summary: 'Test Contract Summary', + responses: { + 200: { + test: 'test' as const + }, + 400: "hello" + }, } - ]); + }); }); \ No newline at end of file From c3329308792f7e7a61d546ca3dc98011e9533997 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 19:43:26 -0500 Subject: [PATCH 10/13] formatted core --- packages/core/.prettierignore | 2 + packages/core/.prettierrc | 6 + packages/core/cache/index.ts | 1 - .../cache/interfaces/ttlCache.interface.ts | 84 ++-- packages/core/cache/redisTtlCache.ts | 31 +- .../core/cache/types/ttlCacheRecord.types.ts | 6 +- .../interfaces/controller.interface.ts | 16 +- .../mikro/models/entities/base.entity.ts | 10 +- .../interfaces/entityMapper.interface.ts | 20 +- .../models/baseEntityMapper.model.ts | 153 ++++---- .../models/requestEntityMapper.model.ts | 163 ++++---- .../models/responseEntityMapper.model.ts | 144 ++++--- .../entityMapper/types/entityMapper.types.ts | 10 +- packages/core/http/index.ts | 1 - .../http/middlewares/request.middleware.ts | 360 +++++++++-------- .../http/middlewares/response.middleware.ts | 88 +++-- packages/core/http/types/api.types.ts | 127 +++--- packages/core/http/types/primitive.types.ts | 98 +++-- packages/core/index.ts | 1 - packages/core/jest.config.ts | 4 +- .../core/services/interfaces/baseService.ts | 16 +- packages/core/tests/entityMapper.test.ts | 367 ++++++++++-------- packages/core/tests/http.middleware.test.ts | 35 +- packages/core/tests/redisTtlCache.test.ts | 97 +++-- packages/validator/.prettierignore | 2 + packages/validator/.prettierrc | 6 + packages/validator/package.json | 1 + 27 files changed, 1039 insertions(+), 810 deletions(-) create mode 100644 packages/core/.prettierignore create mode 100644 packages/core/.prettierrc create mode 100644 packages/validator/.prettierignore create mode 100644 packages/validator/.prettierrc diff --git a/packages/core/.prettierignore b/packages/core/.prettierignore new file mode 100644 index 000000000..04c01ba7b --- /dev/null +++ b/packages/core/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/packages/core/.prettierrc b/packages/core/.prettierrc new file mode 100644 index 000000000..a1bd96fea --- /dev/null +++ b/packages/core/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "trailingComma": "none", + "singleQuote": true, + "printWidth": 80 +} \ No newline at end of file diff --git a/packages/core/cache/index.ts b/packages/core/cache/index.ts index fe988cf54..147ca246d 100644 --- a/packages/core/cache/index.ts +++ b/packages/core/cache/index.ts @@ -1,4 +1,3 @@ export * from './interfaces/ttlCache.interface'; export * from './redisTtlCache'; export * from './types/ttlCacheRecord.types'; - diff --git a/packages/core/cache/interfaces/ttlCache.interface.ts b/packages/core/cache/interfaces/ttlCache.interface.ts index 0d354727d..bd01ddf47 100644 --- a/packages/core/cache/interfaces/ttlCache.interface.ts +++ b/packages/core/cache/interfaces/ttlCache.interface.ts @@ -1,53 +1,53 @@ -import { TtlCacheRecord } from "../types/ttlCacheRecord.types"; +import { TtlCacheRecord } from '../types/ttlCacheRecord.types'; /** * Interface representing a TTL (Time-To-Live) cache. */ export interface TtlCache { - /** - * Puts a record into the cache. - * - * @param {TtlCacheRecord} cacheRecord - The cache record to put into the cache. - * @returns {Promise} - A promise that resolves when the record is put into the cache. - */ - putRecord(cacheRecord: TtlCacheRecord): Promise; + /** + * Puts a record into the cache. + * + * @param {TtlCacheRecord} cacheRecord - The cache record to put into the cache. + * @returns {Promise} - A promise that resolves when the record is put into the cache. + */ + putRecord(cacheRecord: TtlCacheRecord): Promise; - /** - * Deletes a record from the cache. - * - * @param {string} cacheRecordKey - The key of the cache record to delete. - * @returns {Promise} - A promise that resolves when the record is deleted from the cache. - */ - deleteRecord(cacheRecordKey: string): Promise; + /** + * Deletes a record from the cache. + * + * @param {string} cacheRecordKey - The key of the cache record to delete. + * @returns {Promise} - A promise that resolves when the record is deleted from the cache. + */ + deleteRecord(cacheRecordKey: string): Promise; - /** - * Reads a record from the cache. - * - * @param {string} cacheRecordKey - The key of the cache record to read. - * @returns {Promise} - A promise that resolves with the cache record. - */ - readRecord(cacheRecordKey: string): Promise; + /** + * Reads a record from the cache. + * + * @param {string} cacheRecordKey - The key of the cache record to read. + * @returns {Promise} - A promise that resolves with the cache record. + */ + readRecord(cacheRecordKey: string): Promise; - /** - * Peeks at a record in the cache to check if it exists. - * - * @param {string} cacheRecordKey - The key of the cache record to peek at. - * @returns {Promise} - A promise that resolves with a boolean indicating if the record exists. - */ - peekRecord(cacheRecordKey: string): Promise; + /** + * Peeks at a record in the cache to check if it exists. + * + * @param {string} cacheRecordKey - The key of the cache record to peek at. + * @returns {Promise} - A promise that resolves with a boolean indicating if the record exists. + */ + peekRecord(cacheRecordKey: string): Promise; - /** - * Gets the TTL (Time-To-Live) in milliseconds. - * - * @returns {number} - The TTL in milliseconds. - */ - getTtlMilliseconds(): number; + /** + * Gets the TTL (Time-To-Live) in milliseconds. + * + * @returns {number} - The TTL in milliseconds. + */ + getTtlMilliseconds(): number; - /** - * Lists the keys in the cache that match a pattern prefix. - * - * @param {string} pattern_prefix - The pattern prefix to match. - * @returns {Promise} - A promise that resolves with an array of keys matching the pattern prefix. - */ - listKeys(pattern_prefix: string): Promise; + /** + * Lists the keys in the cache that match a pattern prefix. + * + * @param {string} pattern_prefix - The pattern prefix to match. + * @returns {Promise} - A promise that resolves with an array of keys matching the pattern prefix. + */ + listKeys(pattern_prefix: string): Promise; } diff --git a/packages/core/cache/redisTtlCache.ts b/packages/core/cache/redisTtlCache.ts index ad4863da1..f0945ef49 100644 --- a/packages/core/cache/redisTtlCache.ts +++ b/packages/core/cache/redisTtlCache.ts @@ -11,27 +11,34 @@ export class RedisTtlCache implements TtlCache { /** * Creates an instance of RedisTtlCache. - * + * * @param {number} ttlMilliseconds - The default TTL in milliseconds. */ - constructor(private ttlMilliseconds: number, hostingOptions?: RedisClientOptions) { + constructor( + private ttlMilliseconds: number, + hostingOptions?: RedisClientOptions + ) { // Connects to localhost:6379 by default // url usage: redis[s]://[[username][:password]@][host][:port][/db-number] this.client = createClient(hostingOptions); this.client.on('error', (err) => console.log('Redis Client Error', err)); this.client.on('connect', () => { - console.log('\x1b[32m%s\x1b[0m', 'Successfully Connected to Redis'); // Green text + console.log('\x1b[32m%s\x1b[0m', 'Successfully Connected to Redis'); // Green text }); this.client.connect().catch(console.error); } /** * Puts a record into the Redis cache. - * + * * @param {TtlCacheRecord} param0 - The cache record to put into the cache. * @returns {Promise} - A promise that resolves when the record is put into the cache. */ - async putRecord({ key, value, ttlMilliseconds = this.ttlMilliseconds }: TtlCacheRecord): Promise { + async putRecord({ + key, + value, + ttlMilliseconds = this.ttlMilliseconds + }: TtlCacheRecord): Promise { await this.client.set(key, JSON.stringify(value), { PX: ttlMilliseconds }); @@ -39,7 +46,7 @@ export class RedisTtlCache implements TtlCache { /** * Deletes a record from the Redis cache. - * + * * @param {string} cacheRecordKey - The key of the cache record to delete. * @returns {Promise} - A promise that resolves when the record is deleted from the cache. */ @@ -49,7 +56,7 @@ export class RedisTtlCache implements TtlCache { /** * Reads a record from the Redis cache. - * + * * @param {string} cacheRecordKey - The key of the cache record to read. * @returns {Promise} - A promise that resolves with the cache record. * @throws {Error} - Throws an error if the record is not found. @@ -64,12 +71,12 @@ export class RedisTtlCache implements TtlCache { key: cacheRecordKey, value: JSON.parse(value), ttlMilliseconds: ttl * 1000 - }; + }; } /** * Lists the keys in the Redis cache that match a pattern prefix. - * + * * @param {string} pattern_prefix - The pattern prefix to match. * @returns {Promise} - A promise that resolves with an array of keys matching the pattern prefix. */ @@ -80,7 +87,7 @@ export class RedisTtlCache implements TtlCache { /** * Peeks at a record in the Redis cache to check if it exists. - * + * * @param {string} cacheRecordKey - The key of the cache record to peek at. * @returns {Promise} - A promise that resolves with a boolean indicating if the record exists. */ @@ -91,7 +98,7 @@ export class RedisTtlCache implements TtlCache { /** * Disconnects the Redis client. - * + * * @returns {Promise} - A promise that resolves when the client is disconnected. */ async disconnect(): Promise { @@ -100,7 +107,7 @@ export class RedisTtlCache implements TtlCache { /** * Gets the default TTL (Time-To-Live) in milliseconds. - * + * * @returns {number} - The TTL in milliseconds. */ getTtlMilliseconds(): number { diff --git a/packages/core/cache/types/ttlCacheRecord.types.ts b/packages/core/cache/types/ttlCacheRecord.types.ts index 1f0724654..da9cbe1bd 100644 --- a/packages/core/cache/types/ttlCacheRecord.types.ts +++ b/packages/core/cache/types/ttlCacheRecord.types.ts @@ -7,7 +7,7 @@ * @property {number} ttlMilliseconds - The time-to-live of the cache record in milliseconds. */ export type TtlCacheRecord = { - key: string; - value: unknown; - ttlMilliseconds: number; + key: string; + value: unknown; + ttlMilliseconds: number; }; diff --git a/packages/core/controllers/interfaces/controller.interface.ts b/packages/core/controllers/interfaces/controller.interface.ts index 8bb5f2ed2..ab3b8f0c9 100644 --- a/packages/core/controllers/interfaces/controller.interface.ts +++ b/packages/core/controllers/interfaces/controller.interface.ts @@ -1,16 +1,16 @@ /** * Interface representing a controller. - * + * * @interface Controller */ interface Controller { - /** - * The base path for the controller. - * - * @type {string} - * @readonly - */ - readonly basePath: string; + /** + * The base path for the controller. + * + * @type {string} + * @readonly + */ + readonly basePath: string; } export default Controller; diff --git a/packages/core/database/mikro/models/entities/base.entity.ts b/packages/core/database/mikro/models/entities/base.entity.ts index add12fe82..0634444ea 100644 --- a/packages/core/database/mikro/models/entities/base.entity.ts +++ b/packages/core/database/mikro/models/entities/base.entity.ts @@ -1,5 +1,5 @@ import { v4 } from 'uuid'; -import { PrimaryKey, Property } from "@mikro-orm/core"; +import { PrimaryKey, Property } from '@mikro-orm/core'; /** * Abstract class representing a base entity. @@ -7,16 +7,16 @@ import { PrimaryKey, Property } from "@mikro-orm/core"; export abstract class BaseEntity { /** * The unique identifier for the entity. - * + * * @type {string} * @readonly */ - @PrimaryKey({ type: "uuid" }) + @PrimaryKey({ type: 'uuid' }) id: string = v4(); /** * The date when the entity was created. - * + * * @type {Date} */ @Property() @@ -24,7 +24,7 @@ export abstract class BaseEntity { /** * The date when the entity was last updated. - * + * * @type {Date} * @readonly */ diff --git a/packages/core/entityMapper/interfaces/entityMapper.interface.ts b/packages/core/entityMapper/interfaces/entityMapper.interface.ts index 83b5a5c4e..4e79766fb 100644 --- a/packages/core/entityMapper/interfaces/entityMapper.interface.ts +++ b/packages/core/entityMapper/interfaces/entityMapper.interface.ts @@ -1,17 +1,17 @@ -import { AnySchemaValidator } from "@forklaunch/validator"; +import { AnySchemaValidator } from '@forklaunch/validator'; /** * Interface representing a constructor for an entity mapper. - * + * * @template T - The type of the entity mapper. * @interface EntityMapperConstructor */ export interface EntityMapperConstructor { - /** - * Creates a new instance of the entity mapper. - * - * @param {AnySchemaValidator} schemaValidator - The arguments to pass to the constructor. - * @returns {T} - A new instance of the entity mapper. - */ - new (schemaValidator: SV): T; -} \ No newline at end of file + /** + * Creates a new instance of the entity mapper. + * + * @param {AnySchemaValidator} schemaValidator - The arguments to pass to the constructor. + * @returns {T} - A new instance of the entity mapper. + */ + new (schemaValidator: SV): T; +} diff --git a/packages/core/entityMapper/models/baseEntityMapper.model.ts b/packages/core/entityMapper/models/baseEntityMapper.model.ts index ecd6aedc0..ace3d2f2b 100644 --- a/packages/core/entityMapper/models/baseEntityMapper.model.ts +++ b/packages/core/entityMapper/models/baseEntityMapper.model.ts @@ -1,6 +1,10 @@ -import { AnySchemaValidator, Schema, SchemaValidator } from "@forklaunch/validator"; -import { EntityMapperConstructor } from "../interfaces/entityMapper.interface"; -import { EntityMapperSchemaValidatorObject } from "../types/entityMapper.types"; +import { + AnySchemaValidator, + Schema, + SchemaValidator +} from '@forklaunch/validator'; +import { EntityMapperConstructor } from '../interfaces/entityMapper.interface'; +import { EntityMapperSchemaValidatorObject } from '../types/entityMapper.types'; /** * Constructs an instance of a T. @@ -10,8 +14,11 @@ import { EntityMapperSchemaValidatorObject } from "../types/entityMapper.types"; * @param {...any[]} args - The arguments to pass to the constructor. * @returns {T} - An instance of the T. */ -export function construct(self: EntityMapperConstructor, schemaValidator?: SV): T { - return new self(schemaValidator || {} as SV); +export function construct( + self: EntityMapperConstructor, + schemaValidator?: SV +): T { + return new self(schemaValidator || ({} as SV)); } /** @@ -20,74 +27,84 @@ export function construct(self: EntityMapperCo * @template SV - A type that extends SchemaValidator. */ export abstract class BaseEntityMapper { - /** - * The schema validator exact type. - * @type {SV} - * @protected - */ - _SV!: SV; + /** + * The schema validator exact type. + * @type {SV} + * @protected + */ + _SV!: SV; - /** - * The schema validator as a general type. - * @type {SchemaValidator} - * @protected - */ - protected schemaValidator: SchemaValidator; + /** + * The schema validator as a general type. + * @type {SchemaValidator} + * @protected + */ + protected schemaValidator: SchemaValidator; - /** - * The schema definition. - * @type {EntityMapperSchemaValidatorObject} - * @abstract - */ - abstract schema: EntityMapperSchemaValidatorObject; + /** + * The schema definition. + * @type {EntityMapperSchemaValidatorObject} + * @abstract + */ + abstract schema: EntityMapperSchemaValidatorObject; - /** - * The Data Transfer Object (DTO). - * @type {Schema} - * - */ - _dto: Schema = {} as unknown as Schema; + /** + * The Data Transfer Object (DTO). + * @type {Schema} + * + */ + _dto: Schema = {} as unknown as Schema< + this['schema'], + SV + >; - /** - * Creates an instance of BaseEntityMapper. - * - * @param {SV} schemaValidator - The schema provider. - */ - constructor(schemaValidator: SV) { - this.schemaValidator = schemaValidator as unknown as SchemaValidator; - } + /** + * Creates an instance of BaseEntityMapper. + * + * @param {SV} schemaValidator - The schema provider. + */ + constructor(schemaValidator: SV) { + this.schemaValidator = schemaValidator as unknown as SchemaValidator; + } - /** - * Validates and sets the Data Transfer Object (DTO). - * - * @param {this['_dto']} dto - The Data Transfer Object (DTO). - * @throws {Error} - Throws an error if the DTO is invalid. - */ - set dto(_dto: this['_dto']) { - if (!this.schemaValidator.validate(this.schemaValidator.schemify(this.schema), _dto)) { - throw new Error('Invalid DTO'); - } - this._dto = _dto as unknown as Schema; + /** + * Validates and sets the Data Transfer Object (DTO). + * + * @param {this['_dto']} dto - The Data Transfer Object (DTO). + * @throws {Error} - Throws an error if the DTO is invalid. + */ + set dto(_dto: this['_dto']) { + if ( + !this.schemaValidator.validate( + this.schemaValidator.schemify(this.schema), + _dto + ) + ) { + throw new Error('Invalid DTO'); } + this._dto = _dto as unknown as Schema; + } - /** - * Validates and gets the Data Transfer Object (DTO). - * - * @returns {this['_dto']} - The Data Transfer Object (DTO). - * @throws {Error} - Throws an error if the DTO is invalid. - */ - get dto(): this['_dto'] { - return this._dto as unknown as this['_dto']; - } + /** + * Validates and gets the Data Transfer Object (DTO). + * + * @returns {this['_dto']} - The Data Transfer Object (DTO). + * @throws {Error} - Throws an error if the DTO is invalid. + */ + get dto(): this['_dto'] { + return this._dto as unknown as this['_dto']; + } - /** - * Gets the schema of a T. - * - * @template T - A type that extends BaseEntityMapper. - * @param {EntityMapperConstructor} this - The constructor of the T. - * @returns {T['schema']} - The schema of the T. - */ - static schema, SV extends AnySchemaValidator>(this: EntityMapperConstructor): T['schema'] { - return construct(this).schema; - } -} \ No newline at end of file + /** + * Gets the schema of a T. + * + * @template T - A type that extends BaseEntityMapper. + * @param {EntityMapperConstructor} this - The constructor of the T. + * @returns {T['schema']} - The schema of the T. + */ + static schema, SV extends AnySchemaValidator>( + this: EntityMapperConstructor + ): T['schema'] { + return construct(this).schema; + } +} diff --git a/packages/core/entityMapper/models/requestEntityMapper.model.ts b/packages/core/entityMapper/models/requestEntityMapper.model.ts index c4dde9b35..0af8badb0 100644 --- a/packages/core/entityMapper/models/requestEntityMapper.model.ts +++ b/packages/core/entityMapper/models/requestEntityMapper.model.ts @@ -1,8 +1,7 @@ -import { AnySchemaValidator } from "@forklaunch/validator"; -import { BaseEntity } from "../../database/mikro/models/entities/base.entity"; -import { EntityMapperConstructor } from "../interfaces/entityMapper.interface"; -import { BaseEntityMapper, construct } from "./baseEntityMapper.model"; - +import { AnySchemaValidator } from '@forklaunch/validator'; +import { BaseEntity } from '../../database/mikro/models/entities/base.entity'; +import { EntityMapperConstructor } from '../interfaces/entityMapper.interface'; +import { BaseEntityMapper, construct } from './baseEntityMapper.model'; /** * Abstract class representing a request entityMapper. @@ -11,72 +10,102 @@ import { BaseEntityMapper, construct } from "./baseEntityMapper.model"; * @template SV - A type that extends SchemaValidator. * @extends {BaseEntityMapper} */ -export abstract class RequestEntityMapper extends BaseEntityMapper { - /** - * The entity. - * @type {Entity} - * @protected - */ - _Entity!: Entity; +export abstract class RequestEntityMapper< + Entity extends BaseEntity, + SV extends AnySchemaValidator +> extends BaseEntityMapper { + /** + * The entity. + * @type {Entity} + * @protected + */ + _Entity!: Entity; - /** - * Converts the underlying DTO to an entity. - * - * @abstract - * @param {...unknown[]} additionalArgs - Additional arguments. - * @returns {Entity} - The entity. - */ - abstract toEntity(...additionalArgs: unknown[]): Entity; + /** + * Converts the underlying DTO to an entity. + * + * @abstract + * @param {...unknown[]} additionalArgs - Additional arguments. + * @returns {Entity} - The entity. + */ + abstract toEntity(...additionalArgs: unknown[]): Entity; - /** - * Populates the DTO with data from a JSON object. - * - * @param {this['_dto']} json - The JSON object. - * @returns {this} - The instance of the RequestEntityMapper. - */ - fromJson(json: this['_dto']): this { - if (!this.schemaValidator.validate(this.schemaValidator.schemify(this.schema), json)) { - throw new Error('Invalid DTO'); - } - this.dto = json; - return this; + /** + * Populates the DTO with data from a JSON object. + * + * @param {this['_dto']} json - The JSON object. + * @returns {this} - The instance of the RequestEntityMapper. + */ + fromJson(json: this['_dto']): this { + if ( + !this.schemaValidator.validate( + this.schemaValidator.schemify(this.schema), + json + ) + ) { + throw new Error('Invalid DTO'); } + this.dto = json; + return this; + } - /** - * Deserializes a JSON object to an entity. - * - * @param {this['_dto']} json - The JSON object. - * @param {...unknown[]} additionalArgs - Additional arguments. - * @returns {Entity} - The entity. - */ - deserializeJsonToEntity(json: this['_dto'], ...additionalArgs: unknown[]): Entity { - return this.fromJson(json).toEntity(...additionalArgs); - } + /** + * Deserializes a JSON object to an entity. + * + * @param {this['_dto']} json - The JSON object. + * @param {...unknown[]} additionalArgs - Additional arguments. + * @returns {Entity} - The entity. + */ + deserializeJsonToEntity( + json: this['_dto'], + ...additionalArgs: unknown[] + ): Entity { + return this.fromJson(json).toEntity(...additionalArgs); + } - /** - * Creates an instance of a RequestEntityMapper from a JSON object. - * - * @template T - A type that extends RequestEntityMapper. - * @param {EntityMapperConstructor} this - The constructor of the T. - * @param {T['_SV']} schemaValidator - The schema provider. - * @param {T['_dto']} json - The JSON object. - * @returns {T} - An instance of the T. - */ - static fromJson, SV extends AnySchemaValidator, JsonType extends T['_dto']>(this: EntityMapperConstructor, schemaValidator: SV, json: JsonType): T { - return construct(this, schemaValidator).fromJson(json); - } + /** + * Creates an instance of a RequestEntityMapper from a JSON object. + * + * @template T - A type that extends RequestEntityMapper. + * @param {EntityMapperConstructor} this - The constructor of the T. + * @param {T['_SV']} schemaValidator - The schema provider. + * @param {T['_dto']} json - The JSON object. + * @returns {T} - An instance of the T. + */ + static fromJson< + T extends RequestEntityMapper, + SV extends AnySchemaValidator, + JsonType extends T['_dto'] + >( + this: EntityMapperConstructor, + schemaValidator: SV, + json: JsonType + ): T { + return construct(this, schemaValidator).fromJson(json); + } - /** - * Deserializes a JSON object to an entity. - * - * @template T - A type that extends RequestEntityMapper. - * @param {EntityMapperConstructor} this - The constructor of the T. - * @param {T['_SV']} schemaValidator - The schema provider. - * @param {T['_dto']} json - The JSON object. - * @param {...unknown[]} additionalArgs - Additional arguments. - * @returns {T['_Entity']} - The entity. - */ - static deserializeJsonToEntity, SV extends AnySchemaValidator, JsonType extends T['_dto']>(this: EntityMapperConstructor, schemaValidator: SV, json: JsonType, ...additionalArgs: unknown[]): T['_Entity'] { - return construct(this, schemaValidator).fromJson(json).toEntity(...additionalArgs); - } + /** + * Deserializes a JSON object to an entity. + * + * @template T - A type that extends RequestEntityMapper. + * @param {EntityMapperConstructor} this - The constructor of the T. + * @param {T['_SV']} schemaValidator - The schema provider. + * @param {T['_dto']} json - The JSON object. + * @param {...unknown[]} additionalArgs - Additional arguments. + * @returns {T['_Entity']} - The entity. + */ + static deserializeJsonToEntity< + T extends RequestEntityMapper, + SV extends AnySchemaValidator, + JsonType extends T['_dto'] + >( + this: EntityMapperConstructor, + schemaValidator: SV, + json: JsonType, + ...additionalArgs: unknown[] + ): T['_Entity'] { + return construct(this, schemaValidator) + .fromJson(json) + .toEntity(...additionalArgs); + } } diff --git a/packages/core/entityMapper/models/responseEntityMapper.model.ts b/packages/core/entityMapper/models/responseEntityMapper.model.ts index 16412492b..3c5a66037 100644 --- a/packages/core/entityMapper/models/responseEntityMapper.model.ts +++ b/packages/core/entityMapper/models/responseEntityMapper.model.ts @@ -1,7 +1,7 @@ -import { AnySchemaValidator } from "@forklaunch/validator"; -import { BaseEntity } from "../../database/mikro/models/entities/base.entity"; -import { EntityMapperConstructor } from "../interfaces/entityMapper.interface"; -import { BaseEntityMapper, construct } from "./baseEntityMapper.model"; +import { AnySchemaValidator } from '@forklaunch/validator'; +import { BaseEntity } from '../../database/mikro/models/entities/base.entity'; +import { EntityMapperConstructor } from '../interfaces/entityMapper.interface'; +import { BaseEntityMapper, construct } from './baseEntityMapper.model'; /** * Abstract class representing a response entityMapper. @@ -10,67 +10,89 @@ import { BaseEntityMapper, construct } from "./baseEntityMapper.model"; * @template SV - A type that extends SchemaValidator. * @extends {BaseEntityMapper} */ -export abstract class ResponseEntityMapper extends BaseEntityMapper { - /** - * The entity type. - * @type {Entity} - * @protected - */ - _Entity!: Entity; +export abstract class ResponseEntityMapper< + Entity extends BaseEntity, + SV extends AnySchemaValidator +> extends BaseEntityMapper { + /** + * The entity type. + * @type {Entity} + * @protected + */ + _Entity!: Entity; - /** - * Populates entityMapper with DTO from an entity. - * - * @abstract - * @param {Entity} entity - The entity to convert. - * @returns {this} - The instance of the ResponseEntityMapper. - */ - abstract fromEntity(entity: Entity, ...additionalArgs: unknown[]): this; + /** + * Populates entityMapper with DTO from an entity. + * + * @abstract + * @param {Entity} entity - The entity to convert. + * @returns {this} - The instance of the ResponseEntityMapper. + */ + abstract fromEntity(entity: Entity, ...additionalArgs: unknown[]): this; - /** - * Converts the underlying DTO to a JSON object. - * - * @param {...unknown[]} additionalArgs - Additional arguments. - * @returns {this['_dto']} - The JSON object. - */ - toJson(): this['_dto'] { - if (!this.schemaValidator.validate(this.schemaValidator.schemify(this.schema), this.dto)) { - throw new Error('Invalid DTO'); - } - return this.dto; + /** + * Converts the underlying DTO to a JSON object. + * + * @param {...unknown[]} additionalArgs - Additional arguments. + * @returns {this['_dto']} - The JSON object. + */ + toJson(): this['_dto'] { + if ( + !this.schemaValidator.validate( + this.schemaValidator.schemify(this.schema), + this.dto + ) + ) { + throw new Error('Invalid DTO'); } + return this.dto; + } - /** - * Serializes an entity to a JSON object. - * - * @param {Entity} entity - The entity to serialize. - * @returns {this['_dto']} - The JSON object. - */ - serializeEntityToJson(entity: Entity): this['_dto'] { - return this.fromEntity(entity).toJson(); - } + /** + * Serializes an entity to a JSON object. + * + * @param {Entity} entity - The entity to serialize. + * @returns {this['_dto']} - The JSON object. + */ + serializeEntityToJson(entity: Entity): this['_dto'] { + return this.fromEntity(entity).toJson(); + } - /** - * Populates entityMapper with DTO from an entity. - * - * @template T - A type that extends ResponseEntityMapper. - * @param {EntityMapperConstructor} this - The constructor of the T. - * @param {T['_Entity']} entity - The entity to convert. - * @returns {T} - An instance of the T. - */ - static fromEntity, SV extends AnySchemaValidator>(this: EntityMapperConstructor, schemaValidator: SV, entity: T['_Entity']): T { - return construct(this, schemaValidator).fromEntity(entity); - } + /** + * Populates entityMapper with DTO from an entity. + * + * @template T - A type that extends ResponseEntityMapper. + * @param {EntityMapperConstructor} this - The constructor of the T. + * @param {T['_Entity']} entity - The entity to convert. + * @returns {T} - An instance of the T. + */ + static fromEntity< + T extends ResponseEntityMapper, + SV extends AnySchemaValidator + >( + this: EntityMapperConstructor, + schemaValidator: SV, + entity: T['_Entity'] + ): T { + return construct(this, schemaValidator).fromEntity(entity); + } - /** - * Serializes an entity to a JSON object. - * - * @template T - A type that extends ResponseEntityMapper. - * @param {EntityMapperConstructor} this - The constructor of the T. - * @param {T['_Entity']} entity - The entity to serialize. - * @returns {T['_dto']} - The JSON object. - */ - static serializeEntityToJson, SV extends AnySchemaValidator>(this: EntityMapperConstructor, schemaValidator: SV, entity: T['_Entity']): T['_dto'] { - return construct(this, schemaValidator).serializeEntityToJson(entity); - } + /** + * Serializes an entity to a JSON object. + * + * @template T - A type that extends ResponseEntityMapper. + * @param {EntityMapperConstructor} this - The constructor of the T. + * @param {T['_Entity']} entity - The entity to serialize. + * @returns {T['_dto']} - The JSON object. + */ + static serializeEntityToJson< + T extends ResponseEntityMapper, + SV extends AnySchemaValidator + >( + this: EntityMapperConstructor, + schemaValidator: SV, + entity: T['_Entity'] + ): T['_dto'] { + return construct(this, schemaValidator).serializeEntityToJson(entity); + } } diff --git a/packages/core/entityMapper/types/entityMapper.types.ts b/packages/core/entityMapper/types/entityMapper.types.ts index 16de7ce5b..39a80bcff 100644 --- a/packages/core/entityMapper/types/entityMapper.types.ts +++ b/packages/core/entityMapper/types/entityMapper.types.ts @@ -1,10 +1,12 @@ -import { AnySchemaValidator } from "@forklaunch/validator"; -import { UnboxedObjectSchema } from "@forklaunch/validator/types"; +import { AnySchemaValidator } from '@forklaunch/validator'; +import { UnboxedObjectSchema } from '@forklaunch/validator/types'; /** * Type representing a schema validator object for an entity mapper. - * + * * @template SV - A type that extends SchemaValidator. * @typedef {ValidSchemaObject | UnboxedObjectSchema> & {}} EntityMapperSchemaValidatorObject */ -export type EntityMapperSchemaValidatorObject = SV['_ValidSchemaObject'] | UnboxedObjectSchema; \ No newline at end of file +export type EntityMapperSchemaValidatorObject = + | SV['_ValidSchemaObject'] + | UnboxedObjectSchema; diff --git a/packages/core/http/index.ts b/packages/core/http/index.ts index 5a00e5455..47d6563b7 100644 --- a/packages/core/http/index.ts +++ b/packages/core/http/index.ts @@ -1,3 +1,2 @@ export * from './middlewares'; export * from './types'; - diff --git a/packages/core/http/middlewares/request.middleware.ts b/packages/core/http/middlewares/request.middleware.ts index 06d60b238..09cedecc0 100644 --- a/packages/core/http/middlewares/request.middleware.ts +++ b/packages/core/http/middlewares/request.middleware.ts @@ -1,209 +1,257 @@ -import { AnySchemaValidator, SchemaValidator } from "@forklaunch/validator"; -import * as jose from "jose"; -import { v4 } from "uuid"; -import { ForklaunchNextFunction, ForklaunchRequest, ForklaunchResponse } from "../types/api.types"; -import { AuthMethod, HttpContractDetails, PathParamHttpContractDetails, StringOnlyObject } from "../types/primitive.types"; +import { AnySchemaValidator, SchemaValidator } from '@forklaunch/validator'; +import * as jose from 'jose'; +import { v4 } from 'uuid'; +import { + ForklaunchNextFunction, + ForklaunchRequest, + ForklaunchResponse +} from '../types/api.types'; +import { + AuthMethod, + HttpContractDetails, + PathParamHttpContractDetails, + StringOnlyObject +} from '../types/primitive.types'; export function createRequestContext< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(schemaValidator: SV) { - return (req: Request, res: Response, next?: NextFunction) => { - req.schemaValidator = schemaValidator as unknown as SchemaValidator; + return (req: Request, res: Response, next?: NextFunction) => { + req.schemaValidator = schemaValidator as unknown as SchemaValidator; - let correlationId = v4(); + let correlationId = v4(); - if (req.headers['x-correlation-id']) { - correlationId = req.headers['x-correlation-id'] as string; - } + if (req.headers['x-correlation-id']) { + correlationId = req.headers['x-correlation-id'] as string; + } - res.setHeader('x-correlation-id', correlationId); + res.setHeader('x-correlation-id', correlationId); - req.context = { - correlationId: correlationId - } + req.context = { + correlationId: correlationId + }; - if (next) { - next(); - } + if (next) { + next(); } + }; } export function enrichRequestDetails< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(contractDetails: PathParamHttpContractDetails | HttpContractDetails) { - return (req: Request, _res: Response, next?: NextFunction) => { - req.contractDetails = contractDetails; + return (req: Request, _res: Response, next?: NextFunction) => { + req.contractDetails = contractDetails; - if (next) { - next(); - } + if (next) { + next(); } + }; } -export function preHandlerParse(schemaValidator: SchemaValidator, object: unknown, schemaInput?: StringOnlyObject) { - if (!schemaInput) { - return; - } +export function preHandlerParse( + schemaValidator: SchemaValidator, + object: unknown, + schemaInput?: StringOnlyObject +) { + if (!schemaInput) { + return; + } - const schema = schemaValidator.schemify(schemaInput); - if (!schemaValidator.validate(schema, object)) { - return 400; - } + const schema = schemaValidator.schemify(schemaInput); + if (!schemaValidator.validate(schema, object)) { + return 400; + } } export function parseRequestParams< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const params = req.contractDetails.params; - if (preHandlerParse(req.schemaValidator, req.params, params) === 400) { - res.status(400).send("Invalid request parameters."); - if (next) { - next(new Error("Invalid request parameters.")); - } - }; + const params = req.contractDetails.params; + if (preHandlerParse(req.schemaValidator, req.params, params) === 400) { + res.status(400).send('Invalid request parameters.'); if (next) { - next(); + next(new Error('Invalid request parameters.')); } + } + if (next) { + next(); + } } export function parseRequestBody< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - if (req.headers['content-type'] === 'application/json') { - const body = (req.schemaValidator, req.contractDetails as HttpContractDetails).body; - if (preHandlerParse(req.schemaValidator, req.body, body as StringOnlyObject) === 400) { - res.status(400).send("Invalid request body."); - if (next) { - next(new Error("Invalid request body.")); - } - } - } - if (next) { - next(); + if (req.headers['content-type'] === 'application/json') { + const body = (req.schemaValidator, + req.contractDetails as HttpContractDetails).body; + if ( + preHandlerParse( + req.schemaValidator, + req.body, + body as StringOnlyObject + ) === 400 + ) { + res.status(400).send('Invalid request body.'); + if (next) { + next(new Error('Invalid request body.')); + } } + } + if (next) { + next(); + } } export function parseRequestHeaders< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const headers = req.contractDetails.requestHeaders; - if (preHandlerParse(req.schemaValidator, req.headers, headers) === 400) { - res.status(400).send("Invalid request headers."); - if (next) { - next(new Error("Invalid request headers.")); - } - } + const headers = req.contractDetails.requestHeaders; + if (preHandlerParse(req.schemaValidator, req.headers, headers) === 400) { + res.status(400).send('Invalid request headers.'); if (next) { - next(); + next(new Error('Invalid request headers.')); } + } + if (next) { + next(); + } } export function parseRequestQuery< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const query = req.contractDetails.query; - if (preHandlerParse(req.schemaValidator, req.query, query) === 400) { - res.status(400).send("Invalid request query."); - if (next) { - next(new Error("Invalid request query.")); - } - } + const query = req.contractDetails.query; + if (preHandlerParse(req.schemaValidator, req.query, query) === 400) { + res.status(400).send('Invalid request query.'); if (next) { - next(); + next(new Error('Invalid request query.')); } + } + if (next) { + next(); + } } -async function checkAuthorizationToken(authorizationMethod?: AuthMethod, authorizationString?: string): Promise<[401 | 403, string] | string | undefined> { - if (!authorizationString) { - return [401, "No Authorization token provided."]; - } - switch (authorizationMethod) { - case 'jwt': { - if (!authorizationString.startsWith('Bearer ')) { - return [401, "Invalid Authorization token format."]; - } - try { - const decodedJwt = await jose.jwtVerify(authorizationString.split(' ')[1], new TextEncoder().encode(process.env.JWT_SECRET || 'your-256-bit-secret')); - return decodedJwt.payload.iss; - } catch (error) { - console.error(error); - return [403, "Invalid Authorization token."]; - } - } - default: - return [401, "Invalid Authorization method."]; +async function checkAuthorizationToken( + authorizationMethod?: AuthMethod, + authorizationString?: string +): Promise<[401 | 403, string] | string | undefined> { + if (!authorizationString) { + return [401, 'No Authorization token provided.']; + } + switch (authorizationMethod) { + case 'jwt': { + if (!authorizationString.startsWith('Bearer ')) { + return [401, 'Invalid Authorization token format.']; + } + try { + const decodedJwt = await jose.jwtVerify( + authorizationString.split(' ')[1], + new TextEncoder().encode( + process.env.JWT_SECRET || 'your-256-bit-secret' + ) + ); + return decodedJwt.payload.iss; + } catch (error) { + console.error(error); + return [403, 'Invalid Authorization token.']; + } } + default: + return [401, 'Invalid Authorization method.']; + } } -function mapRoles(authorizationType?: AuthMethod, authorizationToken?: string): string[] { - return []; +function mapRoles( + authorizationType?: AuthMethod, + authorizationToken?: string +): string[] { + return []; } -function mapPermissions(authorizationType?: AuthMethod, authorizationToken?: string): string[] { - return []; +function mapPermissions( + authorizationType?: AuthMethod, + authorizationToken?: string +): string[] { + return []; } export async function parseRequestAuth< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const auth = req.contractDetails.auth; - if (auth) { - const errorAndMessage = await checkAuthorizationToken(auth.method, req.headers.authorization); - if (Array.isArray(errorAndMessage)) { - res.status(errorAndMessage[0]).send(errorAndMessage[1]); - if (next) { - next(new Error(errorAndMessage[1])); - } - } - - // TODO: Implement role and permission checking - const permissionSlugs = mapPermissions(auth.method, req.headers.authorization); - const roles = mapRoles(auth.method, req.headers.authorization); - - const permissionErrorMessage = "User does not have sufficient permissions to perform action."; - const roleErrorMessage = "User does not have correct role to perform action."; - - // this is wrong, we need to check if any of the user's permissions are in the allowed permissions, while checking that any of the permissions is not in the forbidden slugs - // currently this is checking if any of the user's permissions are NOT in the allowed permissions - permissionSlugs.forEach(permissionSlug => { - if (!req.contractDetails.auth?.allowedSlugs?.has(permissionSlug) || req.contractDetails.auth?.forbiddenSlugs?.has(permissionSlug)) { - res.status(403).send(permissionErrorMessage); - if (next) { - next(new Error(permissionErrorMessage)); - } - } - }); - roles.forEach(role => { - if (!req.contractDetails.auth?.allowedRoles?.has(role) || req.contractDetails.auth?.forbiddenRoles?.has(role)) { - res.status(403).send(roleErrorMessage); - if (next) { - next(new Error(roleErrorMessage)); - } - } - }); + const auth = req.contractDetails.auth; + if (auth) { + const errorAndMessage = await checkAuthorizationToken( + auth.method, + req.headers.authorization + ); + if (Array.isArray(errorAndMessage)) { + res.status(errorAndMessage[0]).send(errorAndMessage[1]); + if (next) { + next(new Error(errorAndMessage[1])); + } } - // if (next) { - // next(); - // } -} \ No newline at end of file + // TODO: Implement role and permission checking + const permissionSlugs = mapPermissions( + auth.method, + req.headers.authorization + ); + const roles = mapRoles(auth.method, req.headers.authorization); + + const permissionErrorMessage = + 'User does not have sufficient permissions to perform action.'; + const roleErrorMessage = + 'User does not have correct role to perform action.'; + + // this is wrong, we need to check if any of the user's permissions are in the allowed permissions, while checking that any of the permissions is not in the forbidden slugs + // currently this is checking if any of the user's permissions are NOT in the allowed permissions + permissionSlugs.forEach((permissionSlug) => { + if ( + !req.contractDetails.auth?.allowedSlugs?.has(permissionSlug) || + req.contractDetails.auth?.forbiddenSlugs?.has(permissionSlug) + ) { + res.status(403).send(permissionErrorMessage); + if (next) { + next(new Error(permissionErrorMessage)); + } + } + }); + roles.forEach((role) => { + if ( + !req.contractDetails.auth?.allowedRoles?.has(role) || + req.contractDetails.auth?.forbiddenRoles?.has(role) + ) { + res.status(403).send(roleErrorMessage); + if (next) { + next(new Error(roleErrorMessage)); + } + } + }); + } + + // if (next) { + // next(); + // } +} diff --git a/packages/core/http/middlewares/response.middleware.ts b/packages/core/http/middlewares/response.middleware.ts index c0f98121f..53308b9d8 100644 --- a/packages/core/http/middlewares/response.middleware.ts +++ b/packages/core/http/middlewares/response.middleware.ts @@ -1,39 +1,63 @@ -import { AnySchemaValidator } from "@forklaunch/validator"; -import { ForklaunchNextFunction, ForklaunchRequest, ForklaunchResponse } from "../types/api.types"; -import { HttpContractDetails } from "../types/primitive.types"; +import { AnySchemaValidator } from '@forklaunch/validator'; +import { + ForklaunchNextFunction, + ForklaunchRequest, + ForklaunchResponse +} from '../types/api.types'; +import { HttpContractDetails } from '../types/primitive.types'; -function checkAnyValidation(contractDetails: HttpContractDetails) { - return contractDetails.body || contractDetails.params || contractDetails.requestHeaders || contractDetails.query; +function checkAnyValidation( + contractDetails: HttpContractDetails +) { + return ( + contractDetails.body || + contractDetails.params || + contractDetails.requestHeaders || + contractDetails.query + ); } export function parseResponse< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction -> (req: Request, res: Response, next?: NextFunction) { - if (req.contractDetails.responseHeaders) { - const schema = req.schemaValidator.schemify(req.contractDetails.responseHeaders); - req.schemaValidator.validate(schema, res.getHeaders()); - } - - if (res.statusCode === 500 || - (checkAnyValidation(req.contractDetails) && res.statusCode === 400) || - (req.contractDetails.auth && (res.statusCode === 401 || res.statusCode === 403)) - ) { - req.schemaValidator.validate(req.schemaValidator.string, res.bodyData); - return; - } - if (Object.prototype.hasOwnProperty.call(!req.contractDetails.responses, res.statusCode)) { - if (next) { - next(new Error(`Response code ${res.statusCode} not defined in contract.`)); - }; - } - - const schema = req.schemaValidator.schemify(req.contractDetails.responses[res.statusCode]); - req.schemaValidator.validate(schema, res.bodyData); + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction +>(req: Request, res: Response, next?: NextFunction) { + if (req.contractDetails.responseHeaders) { + const schema = req.schemaValidator.schemify( + req.contractDetails.responseHeaders + ); + req.schemaValidator.validate(schema, res.getHeaders()); + } + if ( + res.statusCode === 500 || + (checkAnyValidation(req.contractDetails) && res.statusCode === 400) || + (req.contractDetails.auth && + (res.statusCode === 401 || res.statusCode === 403)) + ) { + req.schemaValidator.validate(req.schemaValidator.string, res.bodyData); + return; + } + if ( + Object.prototype.hasOwnProperty.call( + !req.contractDetails.responses, + res.statusCode + ) + ) { if (next) { - next(); + next( + new Error(`Response code ${res.statusCode} not defined in contract.`) + ); } -} \ No newline at end of file + } + + const schema = req.schemaValidator.schemify( + req.contractDetails.responses[res.statusCode] + ); + req.schemaValidator.validate(schema, res.bodyData); + + if (next) { + next(); + } +} diff --git a/packages/core/http/types/api.types.ts b/packages/core/http/types/api.types.ts index 3d30c7ff8..1d13f9be6 100644 --- a/packages/core/http/types/api.types.ts +++ b/packages/core/http/types/api.types.ts @@ -1,69 +1,84 @@ -import { Prettify } from "@forklaunch/common"; -import { AnySchemaValidator, Schema, SchemaValidator } from "@forklaunch/validator"; -import { IdiomaticSchema } from "@forklaunch/validator/types"; -import { IncomingHttpHeaders, OutgoingHttpHeader } from "http"; -import { ParsedQs } from "qs"; -import { HttpContractDetails, ParamsDictionary, PathParamHttpContractDetails } from "./primitive.types"; +import { Prettify } from '@forklaunch/common'; +import { + AnySchemaValidator, + Schema, + SchemaValidator +} from '@forklaunch/validator'; +import { IdiomaticSchema } from '@forklaunch/validator/types'; +import { IncomingHttpHeaders, OutgoingHttpHeader } from 'http'; +import { ParsedQs } from 'qs'; +import { + HttpContractDetails, + ParamsDictionary, + PathParamHttpContractDetails +} from './primitive.types'; export interface RequestContext { - correlationId: string; - idempotencyKey?: string; + correlationId: string; + idempotencyKey?: string; } export interface ForklaunchRequest< - SV extends AnySchemaValidator, - P = ParamsDictionary, - ReqBody = unknown, - ReqQuery = ParsedQs, - Headers = IncomingHttpHeaders, + SV extends AnySchemaValidator, + P = ParamsDictionary, + ReqBody = unknown, + ReqQuery = ParsedQs, + Headers = IncomingHttpHeaders > { - context: Prettify; - contractDetails: HttpContractDetails | PathParamHttpContractDetails; - schemaValidator: SchemaValidator; + context: Prettify; + contractDetails: HttpContractDetails | PathParamHttpContractDetails; + schemaValidator: SchemaValidator; - params: P; - headers: Headers; - body: ReqBody; - query: ReqQuery; + params: P; + headers: Headers; + body: ReqBody; + query: ReqQuery; } export interface ForklaunchResponse< - ResBody = { - 400: unknown; - 401: unknown; - 403: unknown; - 500: unknown; - }, - StatusCode = number, + ResBody = { + 400: unknown; + 401: unknown; + 403: unknown; + 500: unknown; + }, + StatusCode = number > { - bodyData: unknown; - statusCode: StatusCode; - corked: boolean; + bodyData: unknown; + statusCode: StatusCode; + corked: boolean; - getHeaders: () => OutgoingHttpHeader; - setHeader: (key: string, value: string) => void; - status: { - (code: U): ForklaunchResponse; - (code: U, message?: string): ForklaunchResponse; - (code: U): ForklaunchResponse; - (code: U, message?: string): ForklaunchResponse; - } - send: { - (body?: ResBody, close_connection?: boolean): T; - (body?: ResBody): T; - } - json: { - (body?: ResBody): boolean; - (body?: ResBody): T; - } - jsonp: { - (body?: ResBody): boolean; - (body?: ResBody): T; - } + getHeaders: () => OutgoingHttpHeader; + setHeader: (key: string, value: string) => void; + status: { + (code: U): ForklaunchResponse; + ( + code: U, + message?: string + ): ForklaunchResponse; + (code: U): ForklaunchResponse; + (code: U, message?: string): ForklaunchResponse; + }; + send: { + (body?: ResBody, close_connection?: boolean): T; + (body?: ResBody): T; + }; + json: { + (body?: ResBody): boolean; + (body?: ResBody): T; + }; + jsonp: { + (body?: ResBody): boolean; + (body?: ResBody): T; + }; } -export type MapSchema | SV['_ValidSchemaObject']> = Schema extends infer U ? - { [key: string]: unknown } extends U ? - never : - U : - never; -export type ForklaunchNextFunction = (err?: unknown) => void; \ No newline at end of file +export type MapSchema< + SV extends AnySchemaValidator, + T extends IdiomaticSchema | SV['_ValidSchemaObject'] +> = + Schema extends infer U + ? { [key: string]: unknown } extends U + ? never + : U + : never; +export type ForklaunchNextFunction = (err?: unknown) => void; diff --git a/packages/core/http/types/primitive.types.ts b/packages/core/http/types/primitive.types.ts index e2bca41d9..1b4d1f2b7 100644 --- a/packages/core/http/types/primitive.types.ts +++ b/packages/core/http/types/primitive.types.ts @@ -1,55 +1,75 @@ -import { AnySchemaValidator } from "@forklaunch/validator"; -import { UnboxedObjectSchema } from "@forklaunch/validator/types"; +import { AnySchemaValidator } from '@forklaunch/validator'; +import { UnboxedObjectSchema } from '@forklaunch/validator/types'; -export type ParamsDictionary = { [key: string]: string; }; +export type ParamsDictionary = { [key: string]: string }; -export type StringOnlyObject = Omit, number | symbol>; -export type NumberOnlyObject = Omit, string | symbol>; +export type StringOnlyObject = Omit< + UnboxedObjectSchema, + number | symbol +>; +export type NumberOnlyObject = Omit< + UnboxedObjectSchema, + string | symbol +>; -export type BodyObject = StringOnlyObject & unknown; -export type ParamsObject = StringOnlyObject & unknown; -export type QueryObject = StringOnlyObject & unknown; -export type HeadersObject = StringOnlyObject & unknown; +export type BodyObject = StringOnlyObject & + unknown; +export type ParamsObject = StringOnlyObject & + unknown; +export type QueryObject = StringOnlyObject & + unknown; +export type HeadersObject = + StringOnlyObject & unknown; export type ResponsesObject = { - [key: number]: SV['_ValidSchemaObject'] | UnboxedObjectSchema | string | SV['string']; + [key: number]: + | SV['_ValidSchemaObject'] + | UnboxedObjectSchema + | string + | SV['string']; } & unknown; -export type Body = BodyObject - | SV['_ValidSchemaObject'] - | SV['_SchemaCatchall']; +export type Body = + | BodyObject + | SV['_ValidSchemaObject'] + | SV['_SchemaCatchall']; export type AuthMethod = 'jwt' | 'session'; export interface PathParamHttpContractDetails< - SV extends AnySchemaValidator, - ParamSchemas extends ParamsObject = ParamsObject, - ResponseSchemas extends ResponsesObject = ResponsesObject, - QuerySchemas extends QueryObject = QueryObject + SV extends AnySchemaValidator, + ParamSchemas extends ParamsObject = ParamsObject, + ResponseSchemas extends ResponsesObject = ResponsesObject, + QuerySchemas extends QueryObject = QueryObject > { - name: string, - summary: string, - responses: ResponseSchemas, - requestHeaders?: HeadersObject, - responseHeaders?: HeadersObject, - params?: ParamSchemas, - query?: QuerySchemas, - auth?: { - method: AuthMethod, - allowedSlugs?: Set, - forbiddenSlugs?: Set, - allowedRoles?: Set, - forbiddenRoles?: Set - } + name: string; + summary: string; + responses: ResponseSchemas; + requestHeaders?: HeadersObject; + responseHeaders?: HeadersObject; + params?: ParamSchemas; + query?: QuerySchemas; + auth?: { + method: AuthMethod; + allowedSlugs?: Set; + forbiddenSlugs?: Set; + allowedRoles?: Set; + forbiddenRoles?: Set; + }; } export interface HttpContractDetails< - SV extends AnySchemaValidator, - ParamSchemas extends ParamsObject = ParamsObject, - ResponseSchemas extends ResponsesObject = ResponsesObject, - BodySchema extends Body = Body, - QuerySchemas extends QueryObject = QueryObject -> extends PathParamHttpContractDetails { - body?: BodySchema, - contentType?: + SV extends AnySchemaValidator, + ParamSchemas extends ParamsObject = ParamsObject, + ResponseSchemas extends ResponsesObject = ResponsesObject, + BodySchema extends Body = Body, + QuerySchemas extends QueryObject = QueryObject +> extends PathParamHttpContractDetails< + SV, + ParamSchemas, + ResponseSchemas, + QuerySchemas + > { + body?: BodySchema; + contentType?: | 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded'; diff --git a/packages/core/index.ts b/packages/core/index.ts index d40174e54..a27da9dbb 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -4,4 +4,3 @@ export * from './database'; export * from './entityMapper'; export * from './http'; export * from './services'; - diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index b9c84ca90..e52889d64 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -1,4 +1,4 @@ -import type {Config} from 'jest'; +import type { Config } from 'jest'; const config: Config = { verbose: true, @@ -7,4 +7,4 @@ const config: Config = { testPathIgnorePatterns: ['dist/', 'node_modules/'] }; -export default config; \ No newline at end of file +export default config; diff --git a/packages/core/services/interfaces/baseService.ts b/packages/core/services/interfaces/baseService.ts index 8a322587c..c15bf89dd 100644 --- a/packages/core/services/interfaces/baseService.ts +++ b/packages/core/services/interfaces/baseService.ts @@ -1,15 +1,15 @@ -import { EntityManager } from "@mikro-orm/core"; +import { EntityManager } from '@mikro-orm/core'; /** * Interface representing a base service. - * + * * @interface BaseService */ export default interface BaseService { - /** - * The EntityManager instance for managing entities. - * - * @type {EntityManager} - */ - em: EntityManager; + /** + * The EntityManager instance for managing entities. + * + * @type {EntityManager} + */ + em: EntityManager; } diff --git a/packages/core/tests/entityMapper.test.ts b/packages/core/tests/entityMapper.test.ts index aa001a192..98bbd47e4 100644 --- a/packages/core/tests/entityMapper.test.ts +++ b/packages/core/tests/entityMapper.test.ts @@ -1,186 +1,219 @@ -import { TypeboxSchemaValidator, number, string } from "@forklaunch/validator/typebox"; -import { BaseEntity } from "../database/mikro/models/entities/base.entity"; -import { RequestEntityMapper } from "../entityMapper/models/requestEntityMapper.model"; -import { ResponseEntityMapper } from "../entityMapper/models/responseEntityMapper.model"; +import { + TypeboxSchemaValidator, + number, + string +} from '@forklaunch/validator/typebox'; +import { BaseEntity } from '../database/mikro/models/entities/base.entity'; +import { RequestEntityMapper } from '../entityMapper/models/requestEntityMapper.model'; +import { ResponseEntityMapper } from '../entityMapper/models/responseEntityMapper.model'; class TestEntity extends BaseEntity { - name: string; - age: number; + name: string; + age: number; } -class TestRequestEntityMapper extends RequestEntityMapper { - schema = { - id: string, - name: string, - age: number, - }; - - toEntity(...additionalArgs: unknown[]): TestEntity { - const entity = new TestEntity(); - entity.id = this.dto.id; - entity.name = this.dto.name; - entity.age = this.dto.age; +class TestRequestEntityMapper extends RequestEntityMapper< + TestEntity, + TypeboxSchemaValidator +> { + schema = { + id: string, + name: string, + age: number + }; + + toEntity(...additionalArgs: unknown[]): TestEntity { + const entity = new TestEntity(); + entity.id = this.dto.id; + entity.name = this.dto.name; + entity.age = this.dto.age; - return entity; - } + return entity; + } } -class TestResponseEntityMapper extends ResponseEntityMapper { - schema = { - id: string, - name: string, - age: number +class TestResponseEntityMapper extends ResponseEntityMapper< + TestEntity, + TypeboxSchemaValidator +> { + schema = { + id: string, + name: string, + age: number + }; + + fromEntity(entity: TestEntity): this { + this.dto = { + id: entity.id, + name: entity.name, + age: entity.age }; - fromEntity(entity: TestEntity): this { - this.dto = { - id: entity.id, - name: entity.name, - age: entity.age - }; - - return this; - } + return this; + } } function extractNonTimeBasedEntityFields(entity: T): T { - entity.createdAt = new Date(0); - entity.updatedAt = new Date(0); - return entity; + entity.createdAt = new Date(0); + entity.updatedAt = new Date(0); + return entity; } describe('Request Entity Mapper Test', () => { - let TestRequestEM: TestRequestEntityMapper; - - - beforeAll(() => { - TestRequestEM = new TestRequestEntityMapper(new TypeboxSchemaValidator()); - }); - - test('Schema Equality', async () => { - expect(TestRequestEM.schema).toEqual(TestRequestEntityMapper.schema()); - }); - - test('From JSON', async () => { - const json = { - id: '123', - name: 'test', - age: 1, - }; - - const responseEM = TestRequestEM.fromJson(json); - const staticEM = TestRequestEntityMapper.fromJson(new TypeboxSchemaValidator(), json); - const expectedDto = { - id: '123', - name: 'test', - age: 1, - }; - - expect(staticEM.dto).toEqual(expectedDto); - expect(responseEM.dto).toEqual(expectedDto); - expect(responseEM.dto).toEqual(staticEM.dto); - }); - - test('Deserialization Equality', async () => { - const json = { - id: '123', - name: 'test', - age: 1, - }; - - const entity = extractNonTimeBasedEntityFields(TestRequestEM.deserializeJsonToEntity(json)); - const objectEntity = extractNonTimeBasedEntityFields(TestRequestEM.fromJson(json).toEntity()); - const staticEntity = extractNonTimeBasedEntityFields(TestRequestEntityMapper.deserializeJsonToEntity(new TypeboxSchemaValidator(), json)); - let expectedEntity = new TestEntity(); - expectedEntity.id = '123'; - expectedEntity.name = 'test'; - expectedEntity.age = 1; - - expectedEntity = extractNonTimeBasedEntityFields(expectedEntity); - - expect(entity).toEqual(expectedEntity); - expect(objectEntity).toEqual(expectedEntity); - expect(staticEntity).toEqual(expectedEntity); - expect(entity).toEqual(objectEntity); - expect(entity).toEqual(staticEntity); - expect(staticEntity).toEqual(expectedEntity); - expect(staticEntity).toEqual(objectEntity); - }); - - test('Serialization Failure', async () => { - const json = { - id: '123', - name: 'test', - }; - - // @ts-expect-error - expect(() => TestRequestEM.fromJson(json)).toThrow(); - // @ts-expect-error - expect(() => TestRequestEntityMapper.fromJson(new TypeboxSchemaValidator(), json)).toThrow(); - }); + let TestRequestEM: TestRequestEntityMapper; + + beforeAll(() => { + TestRequestEM = new TestRequestEntityMapper(new TypeboxSchemaValidator()); + }); + + test('Schema Equality', async () => { + expect(TestRequestEM.schema).toEqual(TestRequestEntityMapper.schema()); + }); + + test('From JSON', async () => { + const json = { + id: '123', + name: 'test', + age: 1 + }; + + const responseEM = TestRequestEM.fromJson(json); + const staticEM = TestRequestEntityMapper.fromJson( + new TypeboxSchemaValidator(), + json + ); + const expectedDto = { + id: '123', + name: 'test', + age: 1 + }; + + expect(staticEM.dto).toEqual(expectedDto); + expect(responseEM.dto).toEqual(expectedDto); + expect(responseEM.dto).toEqual(staticEM.dto); + }); + + test('Deserialization Equality', async () => { + const json = { + id: '123', + name: 'test', + age: 1 + }; + + const entity = extractNonTimeBasedEntityFields( + TestRequestEM.deserializeJsonToEntity(json) + ); + const objectEntity = extractNonTimeBasedEntityFields( + TestRequestEM.fromJson(json).toEntity() + ); + const staticEntity = extractNonTimeBasedEntityFields( + TestRequestEntityMapper.deserializeJsonToEntity( + new TypeboxSchemaValidator(), + json + ) + ); + let expectedEntity = new TestEntity(); + expectedEntity.id = '123'; + expectedEntity.name = 'test'; + expectedEntity.age = 1; + + expectedEntity = extractNonTimeBasedEntityFields(expectedEntity); + + expect(entity).toEqual(expectedEntity); + expect(objectEntity).toEqual(expectedEntity); + expect(staticEntity).toEqual(expectedEntity); + expect(entity).toEqual(objectEntity); + expect(entity).toEqual(staticEntity); + expect(staticEntity).toEqual(expectedEntity); + expect(staticEntity).toEqual(objectEntity); + }); + + test('Serialization Failure', async () => { + const json = { + id: '123', + name: 'test' + }; + + // @ts-expect-error + expect(() => TestRequestEM.fromJson(json)).toThrow(); + // @ts-expect-error + expect(() => + TestRequestEntityMapper.fromJson(new TypeboxSchemaValidator(), json) + ).toThrow(); + }); }); describe('Response Entity Mapper Test', () => { - let TestResponseEM: TestResponseEntityMapper; - - beforeAll(() => { - TestResponseEM = new TestResponseEntityMapper(new TypeboxSchemaValidator()); - }); - - test('Schema Equality', async () => { - expect(TestResponseEM.schema).toEqual(TestResponseEntityMapper.schema()); - }); - - test('From Entity', async () => { - const entity = new TestEntity(); - entity.id = '123'; - entity.name = 'test'; - entity.age = 1; - - const responseEM = TestResponseEM.fromEntity(entity); - const staticEM = TestResponseEntityMapper.fromEntity(new TypeboxSchemaValidator(), entity); - const expectedDto = { - id: '123', - name: 'test', - age: 1, - }; - - expect(staticEM.dto).toEqual(expectedDto); - expect(responseEM.dto).toEqual(expectedDto); - expect(responseEM.dto).toEqual(staticEM.dto); - }); - - test('Serialization Equality', async () => { - const entity = new TestEntity(); - entity.id = '123'; - entity.name = 'test'; - entity.age = 1; - - const json = TestResponseEM.serializeEntityToJson(entity); - const objectJson = TestResponseEM.fromEntity(entity).toJson(); - const staticJson = TestResponseEntityMapper.serializeEntityToJson(new TypeboxSchemaValidator(), entity); - const expectedJson = { - id: '123', - name: 'test', - age: 1, - }; - - expect(json).toEqual(expectedJson); - expect(objectJson).toEqual(expectedJson); - expect(staticJson).toEqual(expectedJson); - expect(json).toEqual(objectJson); - expect(json).toEqual(staticJson); - expect(staticJson).toEqual(expectedJson); - expect(staticJson).toEqual(objectJson); - }); - - test('Serialization Failure', async () => { - const entity = new TestEntity(); - entity.id = '123'; - entity.name = 'test'; - - expect(() => TestResponseEM.fromEntity(entity).toJson()).toThrow(); - expect(() => TestResponseEntityMapper.fromEntity(new TypeboxSchemaValidator(), entity).toJson()).toThrow(); - }); -}); + let TestResponseEM: TestResponseEntityMapper; + + beforeAll(() => { + TestResponseEM = new TestResponseEntityMapper(new TypeboxSchemaValidator()); + }); + + test('Schema Equality', async () => { + expect(TestResponseEM.schema).toEqual(TestResponseEntityMapper.schema()); + }); + + test('From Entity', async () => { + const entity = new TestEntity(); + entity.id = '123'; + entity.name = 'test'; + entity.age = 1; + + const responseEM = TestResponseEM.fromEntity(entity); + const staticEM = TestResponseEntityMapper.fromEntity( + new TypeboxSchemaValidator(), + entity + ); + const expectedDto = { + id: '123', + name: 'test', + age: 1 + }; + expect(staticEM.dto).toEqual(expectedDto); + expect(responseEM.dto).toEqual(expectedDto); + expect(responseEM.dto).toEqual(staticEM.dto); + }); + + test('Serialization Equality', async () => { + const entity = new TestEntity(); + entity.id = '123'; + entity.name = 'test'; + entity.age = 1; + + const json = TestResponseEM.serializeEntityToJson(entity); + const objectJson = TestResponseEM.fromEntity(entity).toJson(); + const staticJson = TestResponseEntityMapper.serializeEntityToJson( + new TypeboxSchemaValidator(), + entity + ); + const expectedJson = { + id: '123', + name: 'test', + age: 1 + }; + + expect(json).toEqual(expectedJson); + expect(objectJson).toEqual(expectedJson); + expect(staticJson).toEqual(expectedJson); + expect(json).toEqual(objectJson); + expect(json).toEqual(staticJson); + expect(staticJson).toEqual(expectedJson); + expect(staticJson).toEqual(objectJson); + }); + + test('Serialization Failure', async () => { + const entity = new TestEntity(); + entity.id = '123'; + entity.name = 'test'; + + expect(() => TestResponseEM.fromEntity(entity).toJson()).toThrow(); + expect(() => + TestResponseEntityMapper.fromEntity( + new TypeboxSchemaValidator(), + entity + ).toJson() + ).toThrow(); + }); +}); diff --git a/packages/core/tests/http.middleware.test.ts b/packages/core/tests/http.middleware.test.ts index bee551bbe..b076b1eb5 100644 --- a/packages/core/tests/http.middleware.test.ts +++ b/packages/core/tests/http.middleware.test.ts @@ -1,21 +1,20 @@ -import { MockSchemaValidator } from "@forklaunch/validator/tests/mockSchemaValidator"; -import { HttpContractDetails } from "../http"; +import { MockSchemaValidator } from '@forklaunch/validator/tests/mockSchemaValidator'; +import { HttpContractDetails } from '../http'; -declare module '@forklaunch/validator' { -} +declare module '@forklaunch/validator' {} describe('Http Middleware Tests', () => { - let contractDetails: HttpContractDetails - beforeAll(() => { - contractDetails = { - name: 'Test Contract', - summary: 'Test Contract Summary', - responses: { - 200: { - test: 'test' as const - }, - 400: "hello" - }, - } - }); -}); \ No newline at end of file + let contractDetails: HttpContractDetails; + beforeAll(() => { + contractDetails = { + name: 'Test Contract', + summary: 'Test Contract Summary', + responses: { + 200: { + test: 'test' as const + }, + 400: 'hello' + } + }; + }); +}); diff --git a/packages/core/tests/redisTtlCache.test.ts b/packages/core/tests/redisTtlCache.test.ts index 0f96b604c..9e14e9194 100644 --- a/packages/core/tests/redisTtlCache.test.ts +++ b/packages/core/tests/redisTtlCache.test.ts @@ -2,62 +2,61 @@ import { GenericContainer, StartedTestContainer } from 'testcontainers'; import { RedisTtlCache } from '../cache/redisTtlCache'; describe('RedisTtlCache', () => { - let container: StartedTestContainer; - let cache: RedisTtlCache; - let key: string; - let value: unknown; - let ttlMilliseconds: number; - - beforeAll(async () => { - container = await new GenericContainer("redis") - .withExposedPorts(6379) - .start(); - - cache = new RedisTtlCache(5000, { - url: `redis://${container.getHost()}:${container.getMappedPort(6379)}` - }); - - key = 'testKey'; - value = { data: 'testValue' }; - ttlMilliseconds = 1000; - }, 30000); - - - afterAll(async () => { - await cache.disconnect(); - await container.stop(); + let container: StartedTestContainer; + let cache: RedisTtlCache; + let key: string; + let value: unknown; + let ttlMilliseconds: number; + + beforeAll(async () => { + container = await new GenericContainer('redis') + .withExposedPorts(6379) + .start(); + + cache = new RedisTtlCache(5000, { + url: `redis://${container.getHost()}:${container.getMappedPort(6379)}` }); - it('PutRecord', async () => { - await cache.putRecord({ key, value, ttlMilliseconds }); - }); + key = 'testKey'; + value = { data: 'testValue' }; + ttlMilliseconds = 1000; + }, 30000); - test('Read Record', async () => { - const storedValue = await cache.readRecord(key); + afterAll(async () => { + await cache.disconnect(); + await container.stop(); + }); - expect(storedValue).toEqual({ - key, - ttlMilliseconds, - value - }); - }) + it('PutRecord', async () => { + await cache.putRecord({ key, value, ttlMilliseconds }); + }); - test('Peek Record', async () => { - const exists = await cache.peekRecord(key); + test('Read Record', async () => { + const storedValue = await cache.readRecord(key); - expect(exists).toBeTruthy(); + expect(storedValue).toEqual({ + key, + ttlMilliseconds, + value }); + }); - test('Delete Record', async () => { - await cache.deleteRecord(key); - const existsAfterDelete = await cache.peekRecord(key); + test('Peek Record', async () => { + const exists = await cache.peekRecord(key); - expect(existsAfterDelete).toBeFalsy(); - }); + expect(exists).toBeTruthy(); + }); - test('Check No Record', async () => { - await Promise.resolve(setTimeout(async () => {}, ttlMilliseconds)); - const existsAfterTtl = await cache.peekRecord(key); - expect(existsAfterTtl).toBeFalsy(); - }); -}); \ No newline at end of file + test('Delete Record', async () => { + await cache.deleteRecord(key); + const existsAfterDelete = await cache.peekRecord(key); + + expect(existsAfterDelete).toBeFalsy(); + }); + + test('Check No Record', async () => { + await Promise.resolve(setTimeout(async () => {}, ttlMilliseconds)); + const existsAfterTtl = await cache.peekRecord(key); + expect(existsAfterTtl).toBeFalsy(); + }); +}); diff --git a/packages/validator/.prettierignore b/packages/validator/.prettierignore new file mode 100644 index 000000000..04c01ba7b --- /dev/null +++ b/packages/validator/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/packages/validator/.prettierrc b/packages/validator/.prettierrc new file mode 100644 index 000000000..a1bd96fea --- /dev/null +++ b/packages/validator/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "trailingComma": "none", + "singleQuote": true, + "printWidth": 80 +} \ No newline at end of file diff --git a/packages/validator/package.json b/packages/validator/package.json index f65ac19e9..47b63b453 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -12,6 +12,7 @@ "scripts": { "test": "jest", "build": "tsc", + "docs": "typedoc --out docs *", "lint": "eslint . -c eslint.config.mjs", "lint:fix": "eslint . -c eslint.config.mjs --fix", "format": "prettier --ignore-path=.prettierignore --config .prettierrc '**/*.ts' --write" From 512284ccdc48065e5b9354d1a8cef7773e1a6e23 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 20:22:59 -0500 Subject: [PATCH 11/13] mock schema validator declaration merging example --- packages/validator/package.json | 2 +- .../validator/tests/mockSchemaValidator.ts | 33 +++++++++++++++++-- packages/validator/types/schema.types.ts | 20 +++++------ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/packages/validator/package.json b/packages/validator/package.json index 47b63b453..d8e39fed9 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@forklaunch/validator", - "version": "0.2.6", + "version": "0.2.7", "description": "Schema validator for ForkLaunch components.", "files": [ "dist" diff --git a/packages/validator/tests/mockSchemaValidator.ts b/packages/validator/tests/mockSchemaValidator.ts index bcac83a64..b6f152d32 100644 --- a/packages/validator/tests/mockSchemaValidator.ts +++ b/packages/validator/tests/mockSchemaValidator.ts @@ -1,6 +1,16 @@ import { SchemaValidator } from '../index'; import { LiteralSchema } from '../types/schema.types'; +declare module '../types/schema.types' { + interface SchemaResolve { + Mock: T; + } + + interface SchemaTranslate { + Mock: T; + } +} + export class MockSchemaValidator implements SchemaValidator { _Type!: 'Mock'; _SchemaCatchall!: string; @@ -29,8 +39,8 @@ export class MockSchemaValidator implements SchemaValidator { union(schemas: T[]) { return schemas.join(' | '); } - literal(schema: T) { - return 'literal ' + schema; + literal(schema: T): `literal ${T}` { + return `literal ${schema}`; } validate(schema: T) { return true; @@ -39,3 +49,22 @@ export class MockSchemaValidator implements SchemaValidator { return {}; } } + +export const mockSchemaValidator = new MockSchemaValidator(); +export const string = mockSchemaValidator.string; +export const number = mockSchemaValidator.number; +export const bigint = mockSchemaValidator.bigint; +export const boolean = mockSchemaValidator.boolean; +export const date = mockSchemaValidator.date; +export const symbol = mockSchemaValidator.symbol; +export const empty = mockSchemaValidator.empty; +export const any = mockSchemaValidator.any; +export const unknown = mockSchemaValidator.unknown; +export const never = mockSchemaValidator.never; +export const schemify = mockSchemaValidator.schemify.bind(mockSchemaValidator); +export const optional = mockSchemaValidator.optional.bind(mockSchemaValidator); +export const array = mockSchemaValidator.array.bind(mockSchemaValidator); +export const union = mockSchemaValidator.union.bind(mockSchemaValidator); +export const literal = mockSchemaValidator.literal.bind(mockSchemaValidator); +export const validate = mockSchemaValidator.validate.bind(mockSchemaValidator); +export const openapi = mockSchemaValidator.openapi.bind(mockSchemaValidator); diff --git a/packages/validator/types/schema.types.ts b/packages/validator/types/schema.types.ts index 275f6ec1f..4f5dda550 100644 --- a/packages/validator/types/schema.types.ts +++ b/packages/validator/types/schema.types.ts @@ -152,29 +152,29 @@ export type AnySchemaValidator = SchemaValidator< unknown >; -interface SchemaResolve { +export interface SchemaResolve { Zod: ZodResolve; TypeBox: TResolve; } -interface SchemaTranslate { +export interface SchemaTranslate { Zod: ZodSchemaTranslate; TypeBox: TSchemaTranslate; } type SchemaPrettify< T, - SV extends AnySchemaValidator -> = SV['_Type'] extends keyof SchemaTranslate - ? Prettify[SV['_Type']]> - : never; + SV extends { + _Type: keyof SchemaResolve; + } & AnySchemaValidator +> = Prettify[SV['_Type']]>; export type Schema< T extends SV['_ValidSchemaObject'] | IdiomaticSchema, - SV extends AnySchemaValidator -> = SV['_Type'] extends keyof SchemaResolve - ? SchemaPrettify[SV['_Type']], SV> - : never; + SV extends { + _Type: keyof SchemaResolve; + } & AnySchemaValidator +> = SchemaPrettify[SV['_Type']], SV>; /** * Represents a schema for an unboxed object where each key can have an idiomatic schema. From 11895d373f612d5c8ce35c9b76166b4c896e2faf Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Sun, 7 Jul 2024 22:25:15 -0400 Subject: [PATCH 12/13] partially done with http tests --- .../http/middlewares/request.middleware.ts | 376 +++++++++--------- packages/core/http/types/api.types.ts | 117 +++--- packages/core/package-lock.json | 8 +- packages/core/package.json | 2 +- packages/core/tests/http.middleware.test.ts | 92 ++++- packages/validator/package.json | 2 +- .../validator/tests/mockSchemaValidator.ts | 40 +- packages/validator/types/schema.types.ts | 17 +- 8 files changed, 366 insertions(+), 288 deletions(-) diff --git a/packages/core/http/middlewares/request.middleware.ts b/packages/core/http/middlewares/request.middleware.ts index 09cedecc0..023901102 100644 --- a/packages/core/http/middlewares/request.middleware.ts +++ b/packages/core/http/middlewares/request.middleware.ts @@ -2,256 +2,256 @@ import { AnySchemaValidator, SchemaValidator } from '@forklaunch/validator'; import * as jose from 'jose'; import { v4 } from 'uuid'; import { - ForklaunchNextFunction, - ForklaunchRequest, - ForklaunchResponse + ForklaunchNextFunction, + ForklaunchRequest, + ForklaunchResponse } from '../types/api.types'; import { - AuthMethod, - HttpContractDetails, - PathParamHttpContractDetails, - StringOnlyObject + AuthMethod, + HttpContractDetails, + PathParamHttpContractDetails, + StringOnlyObject } from '../types/primitive.types'; export function createRequestContext< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(schemaValidator: SV) { - return (req: Request, res: Response, next?: NextFunction) => { - req.schemaValidator = schemaValidator as unknown as SchemaValidator; + return (req: Request, res: Response, next?: NextFunction) => { + req.schemaValidator = schemaValidator as SchemaValidator; - let correlationId = v4(); + let correlationId = v4(); - if (req.headers['x-correlation-id']) { - correlationId = req.headers['x-correlation-id'] as string; - } + if (req.headers['x-correlation-id']) { + correlationId = req.headers['x-correlation-id'] as string; + } - res.setHeader('x-correlation-id', correlationId); + res.setHeader('x-correlation-id', correlationId); - req.context = { - correlationId: correlationId - }; + req.context = { + correlationId: correlationId + }; - if (next) { - next(); - } - }; + if (next) { + next(); + } + }; } export function enrichRequestDetails< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(contractDetails: PathParamHttpContractDetails | HttpContractDetails) { - return (req: Request, _res: Response, next?: NextFunction) => { - req.contractDetails = contractDetails; + return (req: Request, _res: Response, next?: NextFunction) => { + req.contractDetails = contractDetails; - if (next) { - next(); - } - }; + if (next) { + next(); + } + }; } export function preHandlerParse( - schemaValidator: SchemaValidator, - object: unknown, - schemaInput?: StringOnlyObject + schemaValidator: SchemaValidator, + object: unknown, + schemaInput?: StringOnlyObject ) { - if (!schemaInput) { - return; - } + if (!schemaInput) { + return; + } - const schema = schemaValidator.schemify(schemaInput); - if (!schemaValidator.validate(schema, object)) { - return 400; - } + const schema = schemaValidator.schemify(schemaInput); + if (!schemaValidator.validate(schema, object)) { + return 400; + } } export function parseRequestParams< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const params = req.contractDetails.params; - if (preHandlerParse(req.schemaValidator, req.params, params) === 400) { - res.status(400).send('Invalid request parameters.'); + const params = req.contractDetails.params; + if (preHandlerParse(req.schemaValidator, req.params, params) === 400) { + res.status(400).send('Invalid request parameters.'); + if (next) { + next(new Error('Invalid request parameters.')); + } + } if (next) { - next(new Error('Invalid request parameters.')); + next(); } - } - if (next) { - next(); - } } export function parseRequestBody< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - if (req.headers['content-type'] === 'application/json') { - const body = (req.schemaValidator, - req.contractDetails as HttpContractDetails).body; - if ( - preHandlerParse( - req.schemaValidator, - req.body, - body as StringOnlyObject - ) === 400 - ) { - res.status(400).send('Invalid request body.'); - if (next) { - next(new Error('Invalid request body.')); - } + if (req.headers['content-type'] === 'application/json') { + const body = (req.schemaValidator, + req.contractDetails as HttpContractDetails).body; + if ( + preHandlerParse( + req.schemaValidator, + req.body, + body as StringOnlyObject + ) === 400 + ) { + res.status(400).send('Invalid request body.'); + if (next) { + next(new Error('Invalid request body.')); + } + } + } + if (next) { + next(); } - } - if (next) { - next(); - } } export function parseRequestHeaders< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const headers = req.contractDetails.requestHeaders; - if (preHandlerParse(req.schemaValidator, req.headers, headers) === 400) { - res.status(400).send('Invalid request headers.'); + const headers = req.contractDetails.requestHeaders; + if (preHandlerParse(req.schemaValidator, req.headers, headers) === 400) { + res.status(400).send('Invalid request headers.'); + if (next) { + next(new Error('Invalid request headers.')); + } + } if (next) { - next(new Error('Invalid request headers.')); + next(); } - } - if (next) { - next(); - } } export function parseRequestQuery< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const query = req.contractDetails.query; - if (preHandlerParse(req.schemaValidator, req.query, query) === 400) { - res.status(400).send('Invalid request query.'); + const query = req.contractDetails.query; + if (preHandlerParse(req.schemaValidator, req.query, query) === 400) { + res.status(400).send('Invalid request query.'); + if (next) { + next(new Error('Invalid request query.')); + } + } if (next) { - next(new Error('Invalid request query.')); + next(); } - } - if (next) { - next(); - } } async function checkAuthorizationToken( - authorizationMethod?: AuthMethod, - authorizationString?: string + authorizationMethod?: AuthMethod, + authorizationString?: string ): Promise<[401 | 403, string] | string | undefined> { - if (!authorizationString) { - return [401, 'No Authorization token provided.']; - } - switch (authorizationMethod) { - case 'jwt': { - if (!authorizationString.startsWith('Bearer ')) { - return [401, 'Invalid Authorization token format.']; - } - try { - const decodedJwt = await jose.jwtVerify( - authorizationString.split(' ')[1], - new TextEncoder().encode( - process.env.JWT_SECRET || 'your-256-bit-secret' - ) - ); - return decodedJwt.payload.iss; - } catch (error) { - console.error(error); - return [403, 'Invalid Authorization token.']; - } + if (!authorizationString) { + return [401, 'No Authorization token provided.']; + } + switch (authorizationMethod) { + case 'jwt': { + if (!authorizationString.startsWith('Bearer ')) { + return [401, 'Invalid Authorization token format.']; + } + try { + const decodedJwt = await jose.jwtVerify( + authorizationString.split(' ')[1], + new TextEncoder().encode( + process.env.JWT_SECRET || 'your-256-bit-secret' + ) + ); + return decodedJwt.payload.iss; + } catch (error) { + console.error(error); + return [403, 'Invalid Authorization token.']; + } + } + default: + return [401, 'Invalid Authorization method.']; } - default: - return [401, 'Invalid Authorization method.']; - } } function mapRoles( - authorizationType?: AuthMethod, - authorizationToken?: string + authorizationType?: AuthMethod, + authorizationToken?: string ): string[] { - return []; + return []; } function mapPermissions( - authorizationType?: AuthMethod, - authorizationToken?: string + authorizationType?: AuthMethod, + authorizationToken?: string ): string[] { - return []; + return []; } export async function parseRequestAuth< - SV extends AnySchemaValidator, - Request extends ForklaunchRequest, - Response extends ForklaunchResponse, - NextFunction extends ForklaunchNextFunction + SV extends AnySchemaValidator, + Request extends ForklaunchRequest, + Response extends ForklaunchResponse, + NextFunction extends ForklaunchNextFunction >(req: Request, res: Response, next?: NextFunction) { - const auth = req.contractDetails.auth; - if (auth) { - const errorAndMessage = await checkAuthorizationToken( - auth.method, - req.headers.authorization - ); - if (Array.isArray(errorAndMessage)) { - res.status(errorAndMessage[0]).send(errorAndMessage[1]); - if (next) { - next(new Error(errorAndMessage[1])); - } - } + const auth = req.contractDetails.auth; + if (auth) { + const errorAndMessage = await checkAuthorizationToken( + auth.method, + req.headers.authorization + ); + if (Array.isArray(errorAndMessage)) { + res.status(errorAndMessage[0]).send(errorAndMessage[1]); + if (next) { + next(new Error(errorAndMessage[1])); + } + } - // TODO: Implement role and permission checking - const permissionSlugs = mapPermissions( - auth.method, - req.headers.authorization - ); - const roles = mapRoles(auth.method, req.headers.authorization); + // TODO: Implement role and permission checking + const permissionSlugs = mapPermissions( + auth.method, + req.headers.authorization + ); + const roles = mapRoles(auth.method, req.headers.authorization); - const permissionErrorMessage = - 'User does not have sufficient permissions to perform action.'; - const roleErrorMessage = - 'User does not have correct role to perform action.'; + const permissionErrorMessage = + 'User does not have sufficient permissions to perform action.'; + const roleErrorMessage = + 'User does not have correct role to perform action.'; - // this is wrong, we need to check if any of the user's permissions are in the allowed permissions, while checking that any of the permissions is not in the forbidden slugs - // currently this is checking if any of the user's permissions are NOT in the allowed permissions - permissionSlugs.forEach((permissionSlug) => { - if ( - !req.contractDetails.auth?.allowedSlugs?.has(permissionSlug) || - req.contractDetails.auth?.forbiddenSlugs?.has(permissionSlug) - ) { - res.status(403).send(permissionErrorMessage); - if (next) { - next(new Error(permissionErrorMessage)); - } - } - }); - roles.forEach((role) => { - if ( - !req.contractDetails.auth?.allowedRoles?.has(role) || - req.contractDetails.auth?.forbiddenRoles?.has(role) - ) { - res.status(403).send(roleErrorMessage); - if (next) { - next(new Error(roleErrorMessage)); - } - } - }); - } + // this is wrong, we need to check if any of the user's permissions are in the allowed permissions, while checking that any of the permissions is not in the forbidden slugs + // currently this is checking if any of the user's permissions are NOT in the allowed permissions + permissionSlugs.forEach((permissionSlug) => { + if ( + !req.contractDetails.auth?.allowedSlugs?.has(permissionSlug) || + req.contractDetails.auth?.forbiddenSlugs?.has(permissionSlug) + ) { + res.status(403).send(permissionErrorMessage); + if (next) { + next(new Error(permissionErrorMessage)); + } + } + }); + roles.forEach((role) => { + if ( + !req.contractDetails.auth?.allowedRoles?.has(role) || + req.contractDetails.auth?.forbiddenRoles?.has(role) + ) { + res.status(403).send(roleErrorMessage); + if (next) { + next(new Error(roleErrorMessage)); + } + } + }); + } - // if (next) { - // next(); - // } + // if (next) { + // next(); + // } } diff --git a/packages/core/http/types/api.types.ts b/packages/core/http/types/api.types.ts index 1d13f9be6..1e916b77c 100644 --- a/packages/core/http/types/api.types.ts +++ b/packages/core/http/types/api.types.ts @@ -1,84 +1,83 @@ import { Prettify } from '@forklaunch/common'; import { - AnySchemaValidator, - Schema, - SchemaValidator + AnySchemaValidator, + Schema } from '@forklaunch/validator'; -import { IdiomaticSchema } from '@forklaunch/validator/types'; +import { IdiomaticSchema, SchemaValidator } from '@forklaunch/validator/types'; import { IncomingHttpHeaders, OutgoingHttpHeader } from 'http'; import { ParsedQs } from 'qs'; import { - HttpContractDetails, - ParamsDictionary, - PathParamHttpContractDetails + HttpContractDetails, + ParamsDictionary, + PathParamHttpContractDetails } from './primitive.types'; export interface RequestContext { - correlationId: string; - idempotencyKey?: string; + correlationId: string; + idempotencyKey?: string; } export interface ForklaunchRequest< - SV extends AnySchemaValidator, - P = ParamsDictionary, - ReqBody = unknown, - ReqQuery = ParsedQs, - Headers = IncomingHttpHeaders + SV extends AnySchemaValidator, + P = ParamsDictionary, + ReqBody = unknown, + ReqQuery = ParsedQs, + Headers = IncomingHttpHeaders > { - context: Prettify; - contractDetails: HttpContractDetails | PathParamHttpContractDetails; - schemaValidator: SchemaValidator; + context: Prettify; + contractDetails: HttpContractDetails | PathParamHttpContractDetails; + schemaValidator: SchemaValidator; - params: P; - headers: Headers; - body: ReqBody; - query: ReqQuery; + params: P; + headers: Headers; + body: ReqBody; + query: ReqQuery; } export interface ForklaunchResponse< - ResBody = { - 400: unknown; - 401: unknown; - 403: unknown; - 500: unknown; - }, - StatusCode = number + ResBody = { + 400: unknown; + 401: unknown; + 403: unknown; + 500: unknown; + }, + StatusCode = number > { - bodyData: unknown; - statusCode: StatusCode; - corked: boolean; + bodyData: unknown; + statusCode: StatusCode; + corked: boolean; - getHeaders: () => OutgoingHttpHeader; - setHeader: (key: string, value: string) => void; - status: { - (code: U): ForklaunchResponse; - ( - code: U, - message?: string - ): ForklaunchResponse; - (code: U): ForklaunchResponse; - (code: U, message?: string): ForklaunchResponse; - }; - send: { - (body?: ResBody, close_connection?: boolean): T; - (body?: ResBody): T; - }; - json: { - (body?: ResBody): boolean; - (body?: ResBody): T; - }; - jsonp: { - (body?: ResBody): boolean; - (body?: ResBody): T; - }; + getHeaders: () => OutgoingHttpHeader; + setHeader: (key: string, value: string) => void; + status: { + (code: U): ForklaunchResponse; + ( + code: U, + message?: string + ): ForklaunchResponse; + (code: U): ForklaunchResponse; + (code: U, message?: string): ForklaunchResponse; + }; + send: { + (body?: ResBody, close_connection?: boolean): T; + (body?: ResBody): T; + }; + json: { + (body?: ResBody): boolean; + (body?: ResBody): T; + }; + jsonp: { + (body?: ResBody): boolean; + (body?: ResBody): T; + }; } export type MapSchema< - SV extends AnySchemaValidator, - T extends IdiomaticSchema | SV['_ValidSchemaObject'] + SV extends AnySchemaValidator, + T extends IdiomaticSchema | SV['_ValidSchemaObject'] > = - Schema extends infer U + Schema extends infer U ? { [key: string]: unknown } extends U - ? never - : U + ? never + : U : never; export type ForklaunchNextFunction = (err?: unknown) => void; diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index aa35c78fc..1a0ca3dc3 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.1", "license": "MIT", "dependencies": { - "@forklaunch/validator": "^0.2.6", + "@forklaunch/validator": "^0.2.9", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", @@ -798,9 +798,9 @@ "integrity": "sha512-ThzqAO97Hk5PZYjtDyokoQFG7Ktq5Kjbyr3zRP4LslzOxe+wMPcbrm3wiQDabV2liQR/BZYXYi5m3RkmxlmaeA==" }, "node_modules/@forklaunch/validator": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.6.tgz", - "integrity": "sha512-sfbXZztOgkfVqikOpn7Y5DTvw1XNVlpJNlsbr+y4n6Y3vRi6+Xc3ejgrnYYfALgFfGnhuRMUXJ7xH3k/0Cooag==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@forklaunch/validator/-/validator-0.2.9.tgz", + "integrity": "sha512-zkcSc2SFLgVT5eTKY6Who8DTsNVnLhbTvhem3AQmLD8YCjiJOpTO95Zi3200x9zr7Mf+2mkdJxeUYeN57StFtA==", "dependencies": { "@anatine/zod-openapi": "^2.2.6", "@forklaunch/common": "^0.1.2", diff --git a/packages/core/package.json b/packages/core/package.json index a2056f20f..c781277ae 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -22,7 +22,7 @@ }, "homepage": "https://github.com/forklaunch/forklaunch-js#readme", "dependencies": { - "@forklaunch/validator": "^0.2.6", + "@forklaunch/validator": "^0.2.9", "@mikro-orm/core": "^6.2.9", "jose": "^5.6.2", "redis": "^4.6.14", diff --git a/packages/core/tests/http.middleware.test.ts b/packages/core/tests/http.middleware.test.ts index b076b1eb5..fccb406a7 100644 --- a/packages/core/tests/http.middleware.test.ts +++ b/packages/core/tests/http.middleware.test.ts @@ -1,20 +1,80 @@ -import { MockSchemaValidator } from '@forklaunch/validator/tests/mockSchemaValidator'; -import { HttpContractDetails } from '../http'; - -declare module '@forklaunch/validator' {} +import { SchemaValidator } from '@forklaunch/validator'; +import { + MockSchemaValidator, + literal, + optional, + union +} from '@forklaunch/validator/tests/mockSchemaValidator'; +import { ForklaunchRequest, ForklaunchResponse, HttpContractDetails, parseRequestBody, parseRequestHeaders, parseRequestParams, parseRequestQuery, parseResponse } from '../http'; describe('Http Middleware Tests', () => { - let contractDetails: HttpContractDetails; - beforeAll(() => { - contractDetails = { - name: 'Test Contract', - summary: 'Test Contract Summary', - responses: { - 200: { - test: 'test' as const - }, - 400: 'hello' - } + let contractDetails: HttpContractDetails; + let req: ForklaunchRequest; + let resp: ForklaunchResponse; + + const nextFunction = (err?: unknown) => { + expect(err).toBeFalsy() + }; + + const testSchema = { + test: union(['a', optional(literal('test'))] as const) }; - }); + + beforeAll(() => { + contractDetails = { + name: 'Test Contract', + summary: 'Test Contract Summary', + responses: { + 200: testSchema + } + }; + + req = { + context: { + correlationId: '123' + }, + contractDetails, + schemaValidator: new MockSchemaValidator() as SchemaValidator, + params: {}, + headers: {}, + body: {}, + query: {} + }; + + resp = { + bodyData: {}, + statusCode: 200, + corked: false, + getHeaders: jest.fn(), + setHeader: jest.fn(), + status: jest.fn(), + send: jest.fn(), + json: jest.fn(), + jsonp: jest.fn(), + }; + }); + + test('Validate Request Params', async () => { + parseRequestParams(req, resp, nextFunction); + }); + + test('Validate Request Headers', async () => { + parseRequestHeaders(req, resp, nextFunction); + }); + + test('Validate Request Body', async () => { + parseRequestBody(req, resp, nextFunction); + }); + + test('Validate Request Query Params', async () => { + parseRequestQuery(req, resp, nextFunction); + }); + + test('Validate Response', async () => { + parseResponse(req, resp, nextFunction); + }); + + // Not supported yet + // test('Validate Auth', async () => { + // }); }); diff --git a/packages/validator/package.json b/packages/validator/package.json index d8e39fed9..e35b28cf9 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@forklaunch/validator", - "version": "0.2.7", + "version": "0.2.9", "description": "Schema validator for ForkLaunch components.", "files": [ "dist" diff --git a/packages/validator/tests/mockSchemaValidator.ts b/packages/validator/tests/mockSchemaValidator.ts index b6f152d32..a4736bcc3 100644 --- a/packages/validator/tests/mockSchemaValidator.ts +++ b/packages/validator/tests/mockSchemaValidator.ts @@ -1,3 +1,4 @@ +import { SchemaObject } from 'openapi3-ts/oas31'; import { SchemaValidator } from '../index'; import { LiteralSchema } from '../types/schema.types'; @@ -11,7 +12,27 @@ declare module '../types/schema.types' { } } -export class MockSchemaValidator implements SchemaValidator { +type RecursiveUnion = T extends readonly [ + infer F extends string, + ...infer R extends readonly string[] +] + ? R extends [] + ? F + : `${F} | ${RecursiveUnion}` + : ''; + +export class MockSchemaValidator + implements + SchemaValidator< + (schema: T) => T, + (schema: T) => `optional ${T}`, + (schema: T) => `array ${T}`, + (schemas: T) => RecursiveUnion, + (schema: T) => `literal ${T}`, + (schema: T, value: string) => boolean, + (schema: T) => SchemaObject + > +{ _Type!: 'Mock'; _SchemaCatchall!: string; _ValidSchemaObject!: string; @@ -30,22 +51,22 @@ export class MockSchemaValidator implements SchemaValidator { schemify(schema: T) { return schema; } - optional(schema: T) { - return 'optional ' + schema; + optional(schema: T): `optional ${T}` { + return ('optional ' + schema) as `optional ${T}`; } - array(schema: T) { - return 'array ' + schema; + array(schema: T): `array ${T}` { + return ('array ' + schema) as `array ${T}`; } - union(schemas: T[]) { - return schemas.join(' | '); + union(schemas: T): RecursiveUnion { + return schemas.join(' | ') as RecursiveUnion; } literal(schema: T): `literal ${T}` { return `literal ${schema}`; } - validate(schema: T) { + validate(schema: T, value: string): boolean { return true; } - openapi(schema: T) { + openapi(schema: T): SchemaObject { return {}; } } @@ -64,6 +85,7 @@ export const never = mockSchemaValidator.never; export const schemify = mockSchemaValidator.schemify.bind(mockSchemaValidator); export const optional = mockSchemaValidator.optional.bind(mockSchemaValidator); export const array = mockSchemaValidator.array.bind(mockSchemaValidator); +// note, use 'as const' when calling on the input array, for proper type coercion export const union = mockSchemaValidator.union.bind(mockSchemaValidator); export const literal = mockSchemaValidator.literal.bind(mockSchemaValidator); export const validate = mockSchemaValidator.validate.bind(mockSchemaValidator); diff --git a/packages/validator/types/schema.types.ts b/packages/validator/types/schema.types.ts index 4f5dda550..d09da63ca 100644 --- a/packages/validator/types/schema.types.ts +++ b/packages/validator/types/schema.types.ts @@ -150,7 +150,9 @@ export type AnySchemaValidator = SchemaValidator< unknown, unknown, unknown ->; +> & { + _Type: keyof SchemaResolve; +}; export interface SchemaResolve { Zod: ZodResolve; @@ -162,18 +164,13 @@ export interface SchemaTranslate { TypeBox: TSchemaTranslate; } -type SchemaPrettify< - T, - SV extends { - _Type: keyof SchemaResolve; - } & AnySchemaValidator -> = Prettify[SV['_Type']]>; +type SchemaPrettify = Prettify< + SchemaTranslate[SV['_Type']] +>; export type Schema< T extends SV['_ValidSchemaObject'] | IdiomaticSchema, - SV extends { - _Type: keyof SchemaResolve; - } & AnySchemaValidator + SV extends AnySchemaValidator > = SchemaPrettify[SV['_Type']], SV>; /** From ff64a3e83095e082e6eb56aceb514d44f54d3855 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Mon, 8 Jul 2024 23:07:41 -0400 Subject: [PATCH 13/13] added http middleware tests (without auth) --- packages/core/tests/entityMapper.test.ts | 2 +- packages/core/tests/http.middleware.test.ts | 39 ++++++++++++++----- .../validator/tests/mockSchemaValidator.ts | 26 ++++++------- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/packages/core/tests/entityMapper.test.ts b/packages/core/tests/entityMapper.test.ts index 98bbd47e4..d2f8e4619 100644 --- a/packages/core/tests/entityMapper.test.ts +++ b/packages/core/tests/entityMapper.test.ts @@ -136,8 +136,8 @@ describe('Request Entity Mapper Test', () => { // @ts-expect-error expect(() => TestRequestEM.fromJson(json)).toThrow(); - // @ts-expect-error expect(() => + // @ts-expect-error TestRequestEntityMapper.fromJson(new TypeboxSchemaValidator(), json) ).toThrow(); }); diff --git a/packages/core/tests/http.middleware.test.ts b/packages/core/tests/http.middleware.test.ts index fccb406a7..204dc1e67 100644 --- a/packages/core/tests/http.middleware.test.ts +++ b/packages/core/tests/http.middleware.test.ts @@ -2,10 +2,11 @@ import { SchemaValidator } from '@forklaunch/validator'; import { MockSchemaValidator, literal, + mockSchemaValidator, optional, union } from '@forklaunch/validator/tests/mockSchemaValidator'; -import { ForklaunchRequest, ForklaunchResponse, HttpContractDetails, parseRequestBody, parseRequestHeaders, parseRequestParams, parseRequestQuery, parseResponse } from '../http'; +import { ForklaunchRequest, ForklaunchResponse, HttpContractDetails, RequestContext, createRequestContext, enrichRequestDetails, parseRequestBody, parseRequestHeaders, parseRequestParams, parseRequestQuery, parseResponse } from '../http'; describe('Http Middleware Tests', () => { let contractDetails: HttpContractDetails; @@ -24,21 +25,23 @@ describe('Http Middleware Tests', () => { contractDetails = { name: 'Test Contract', summary: 'Test Contract Summary', + body: testSchema, + params: testSchema, + requestHeaders: testSchema, + query: testSchema, responses: { 200: testSchema } }; req = { - context: { - correlationId: '123' - }, - contractDetails, - schemaValidator: new MockSchemaValidator() as SchemaValidator, - params: {}, - headers: {}, - body: {}, - query: {} + context: {} as RequestContext, + contractDetails: {} as HttpContractDetails, + schemaValidator: {} as SchemaValidator, + params: testSchema, + headers: testSchema, + body: testSchema, + query: testSchema }; resp = { @@ -54,6 +57,22 @@ describe('Http Middleware Tests', () => { }; }); + + + test('Create Request Context', async () => { + req.context = {} as RequestContext; + req.schemaValidator = {} as SchemaValidator; + createRequestContext(mockSchemaValidator)(req, resp, nextFunction); + expect(req.context.correlationId).not.toBe('123'); + expect(req.schemaValidator).toBe(mockSchemaValidator); + }); + + test('Enrich Request Details', async () => { + req.contractDetails = {} as HttpContractDetails; + enrichRequestDetails(contractDetails)(req, resp, nextFunction); + expect(req.contractDetails).toEqual(contractDetails); + }); + test('Validate Request Params', async () => { parseRequestParams(req, resp, nextFunction); }); diff --git a/packages/validator/tests/mockSchemaValidator.ts b/packages/validator/tests/mockSchemaValidator.ts index a4736bcc3..a5a80fb43 100644 --- a/packages/validator/tests/mockSchemaValidator.ts +++ b/packages/validator/tests/mockSchemaValidator.ts @@ -17,21 +17,21 @@ type RecursiveUnion = T extends readonly [ ...infer R extends readonly string[] ] ? R extends [] - ? F - : `${F} | ${RecursiveUnion}` + ? F + : `${F} | ${RecursiveUnion}` : ''; export class MockSchemaValidator implements - SchemaValidator< - (schema: T) => T, - (schema: T) => `optional ${T}`, - (schema: T) => `array ${T}`, - (schemas: T) => RecursiveUnion, - (schema: T) => `literal ${T}`, - (schema: T, value: string) => boolean, - (schema: T) => SchemaObject - > + SchemaValidator< + (schema: T) => T, + (schema: T) => `optional ${T}`, + (schema: T) => `array ${T}`, + (schemas: T) => RecursiveUnion, + (schema: T) => `literal ${T}`, + (schema: T, value: string) => boolean, + (schema: T) => SchemaObject + > { _Type!: 'Mock'; _SchemaCatchall!: string; @@ -64,9 +64,9 @@ export class MockSchemaValidator return `literal ${schema}`; } validate(schema: T, value: string): boolean { - return true; + return schema === value; } - openapi(schema: T): SchemaObject { + openapi(_schema: T): SchemaObject { return {}; } }