From 6b02dbea763a3d50b2b8f58d3e42a1111b30a070 Mon Sep 17 00:00:00 2001 From: Brent McKendrick Date: Fri, 23 Aug 2024 22:30:55 +1000 Subject: [PATCH] feat: add support for yup schema library * modifications to allow either yup or zod to be used * renamed sst example to sst2 as sst3 is released. --- examples/{sst-api => sst-v2}/.gitignore | 0 .../{sst-api => sst-v2}/.vscode/launch.json | 0 .../{sst-api => sst-v2}/.vscode/settings.json | 0 examples/{sst-api => sst-v2}/package.json | 2 +- .../packages/core/package.json | 2 +- .../packages/core/src/event.ts | 0 .../packages/core/src/todo.ts | 0 .../packages/core/sst-env.d.ts | 0 .../packages/core/tsconfig.json | 0 .../packages/functions/package.json | 6 +-- .../packages/functions/src/api-yup/models.ts | 28 +++++++++++ .../packages/functions/src/api-yup}/todo.ts | 2 +- .../packages/functions/src/api-zod}/models.ts | 0 .../packages/functions/src/api-zod/todo.ts | 46 ++++++++++++++++++ .../functions/src/events/todo-created.ts | 2 +- .../packages/functions/src/lambda.ts | 0 .../packages/functions/sst-env.d.ts | 0 .../packages/functions/tsconfig.json | 2 +- examples/{sst-api => sst-v2}/pnpm-lock.yaml | 35 ++++++++++++++ .../{sst-api => sst-v2}/pnpm-workspace.yaml | 0 examples/{sst-api => sst-v2}/readme.md | 4 +- examples/{sst-api => sst-v2}/sst.config.ts | 2 +- .../stacks/ExampleStack.ts | 0 .../{sst-api => sst-v2}/stacks/MyStack.ts | 0 examples/{sst-api => sst-v2}/tsconfig.json | 0 package.json | 4 +- package/src/core/parsers.ts | 7 ++- .../api/middleware/validator.mware.ts | 5 ++ .../integrations/api/validation-yup.test.ts | 47 +++++++++++++++++++ .../test/integrations/api/validation.test.ts | 2 +- pnpm-lock.yaml | 36 +++++++++++++- readme.md | 40 +++++++++++----- 32 files changed, 242 insertions(+), 30 deletions(-) rename examples/{sst-api => sst-v2}/.gitignore (100%) rename examples/{sst-api => sst-v2}/.vscode/launch.json (100%) rename examples/{sst-api => sst-v2}/.vscode/settings.json (100%) rename examples/{sst-api => sst-v2}/package.json (95%) rename examples/{sst-api => sst-v2}/packages/core/package.json (91%) rename examples/{sst-api => sst-v2}/packages/core/src/event.ts (100%) rename examples/{sst-api => sst-v2}/packages/core/src/todo.ts (100%) rename examples/{sst-api => sst-v2}/packages/core/sst-env.d.ts (100%) rename examples/{sst-api => sst-v2}/packages/core/tsconfig.json (100%) rename examples/{sst-api => sst-v2}/packages/functions/package.json (76%) create mode 100644 examples/sst-v2/packages/functions/src/api-yup/models.ts rename examples/{sst-api/packages/functions/src => sst-v2/packages/functions/src/api-yup}/todo.ts (95%) rename examples/{sst-api/packages/functions/src => sst-v2/packages/functions/src/api-zod}/models.ts (100%) create mode 100644 examples/sst-v2/packages/functions/src/api-zod/todo.ts rename examples/{sst-api => sst-v2}/packages/functions/src/events/todo-created.ts (79%) rename examples/{sst-api => sst-v2}/packages/functions/src/lambda.ts (100%) rename examples/{sst-api => sst-v2}/packages/functions/sst-env.d.ts (100%) rename examples/{sst-api => sst-v2}/packages/functions/tsconfig.json (85%) rename examples/{sst-api => sst-v2}/pnpm-lock.yaml (99%) rename examples/{sst-api => sst-v2}/pnpm-workspace.yaml (100%) rename examples/{sst-api => sst-v2}/readme.md (88%) rename examples/{sst-api => sst-v2}/sst.config.ts (90%) rename examples/{sst-api => sst-v2}/stacks/ExampleStack.ts (100%) rename examples/{sst-api => sst-v2}/stacks/MyStack.ts (100%) rename examples/{sst-api => sst-v2}/tsconfig.json (100%) create mode 100644 package/test/integrations/api/validation-yup.test.ts diff --git a/examples/sst-api/.gitignore b/examples/sst-v2/.gitignore similarity index 100% rename from examples/sst-api/.gitignore rename to examples/sst-v2/.gitignore diff --git a/examples/sst-api/.vscode/launch.json b/examples/sst-v2/.vscode/launch.json similarity index 100% rename from examples/sst-api/.vscode/launch.json rename to examples/sst-v2/.vscode/launch.json diff --git a/examples/sst-api/.vscode/settings.json b/examples/sst-v2/.vscode/settings.json similarity index 100% rename from examples/sst-api/.vscode/settings.json rename to examples/sst-v2/.vscode/settings.json diff --git a/examples/sst-api/package.json b/examples/sst-v2/package.json similarity index 95% rename from examples/sst-api/package.json rename to examples/sst-v2/package.json index 6320577..5b9b939 100644 --- a/examples/sst-api/package.json +++ b/examples/sst-v2/package.json @@ -1,5 +1,5 @@ { - "name": "sst-api", + "name": "sst-v2", "version": "0.0.0", "private": true, "type": "module", diff --git a/examples/sst-api/packages/core/package.json b/examples/sst-v2/packages/core/package.json similarity index 91% rename from examples/sst-api/packages/core/package.json rename to examples/sst-v2/packages/core/package.json index a9c8b1b..5fb2d85 100644 --- a/examples/sst-api/packages/core/package.json +++ b/examples/sst-v2/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "@sst-api/core", + "name": "@sst-v2/core", "version": "0.0.0", "type": "module", "scripts": { diff --git a/examples/sst-api/packages/core/src/event.ts b/examples/sst-v2/packages/core/src/event.ts similarity index 100% rename from examples/sst-api/packages/core/src/event.ts rename to examples/sst-v2/packages/core/src/event.ts diff --git a/examples/sst-api/packages/core/src/todo.ts b/examples/sst-v2/packages/core/src/todo.ts similarity index 100% rename from examples/sst-api/packages/core/src/todo.ts rename to examples/sst-v2/packages/core/src/todo.ts diff --git a/examples/sst-api/packages/core/sst-env.d.ts b/examples/sst-v2/packages/core/sst-env.d.ts similarity index 100% rename from examples/sst-api/packages/core/sst-env.d.ts rename to examples/sst-v2/packages/core/sst-env.d.ts diff --git a/examples/sst-api/packages/core/tsconfig.json b/examples/sst-v2/packages/core/tsconfig.json similarity index 100% rename from examples/sst-api/packages/core/tsconfig.json rename to examples/sst-v2/packages/core/tsconfig.json diff --git a/examples/sst-api/packages/functions/package.json b/examples/sst-v2/packages/functions/package.json similarity index 76% rename from examples/sst-api/packages/functions/package.json rename to examples/sst-v2/packages/functions/package.json index 0d78995..5ea526a 100644 --- a/examples/sst-api/packages/functions/package.json +++ b/examples/sst-v2/packages/functions/package.json @@ -1,5 +1,5 @@ { - "name": "@sst-api/functions", + "name": "@sst-v2/functions", "version": "0.0.0", "type": "module", "scripts": { @@ -13,7 +13,7 @@ "vitest": "^1.5.0" }, "dependencies": { - "@rt/funcy": "link:../../../../", - "zod": "^3.22.4" + "zod": "^3.22.4", + "yup": "^1.4.0" } } diff --git a/examples/sst-v2/packages/functions/src/api-yup/models.ts b/examples/sst-v2/packages/functions/src/api-yup/models.ts new file mode 100644 index 0000000..1099a26 --- /dev/null +++ b/examples/sst-v2/packages/functions/src/api-yup/models.ts @@ -0,0 +1,28 @@ +import { object, string, number, date, array } from 'yup' + +export const GetTodoPath = object({ + id: string().required(), +}) + +export const ListQuery = object({ + skip: number().optional(), + take: number().optional(), +}) + +export const CreateTodoRequest = object({ + id: string(), + title: string(), + description: string(), + due: date(), +}) + +export const TodoResponse = object({ + id: string(), + title: string(), +}) + +export const ListTodoResponse = object({ + items: array(TodoResponse), + skip: number(), + take: number(), +}) diff --git a/examples/sst-api/packages/functions/src/todo.ts b/examples/sst-v2/packages/functions/src/api-yup/todo.ts similarity index 95% rename from examples/sst-api/packages/functions/src/todo.ts rename to examples/sst-v2/packages/functions/src/api-yup/todo.ts index 0906fc5..d773f09 100644 --- a/examples/sst-api/packages/functions/src/todo.ts +++ b/examples/sst-v2/packages/functions/src/api-yup/todo.ts @@ -1,4 +1,4 @@ -import { Todo } from '@sst-api/core/todo' +import { Todo } from '@sst-v2/core/todo' import { api, res } from '@refactorthis/funcy' import { CreateTodoRequest, GetTodoPath, ListQuery, ListTodoResponse, TodoResponse } from './models' diff --git a/examples/sst-api/packages/functions/src/models.ts b/examples/sst-v2/packages/functions/src/api-zod/models.ts similarity index 100% rename from examples/sst-api/packages/functions/src/models.ts rename to examples/sst-v2/packages/functions/src/api-zod/models.ts diff --git a/examples/sst-v2/packages/functions/src/api-zod/todo.ts b/examples/sst-v2/packages/functions/src/api-zod/todo.ts new file mode 100644 index 0000000..d773f09 --- /dev/null +++ b/examples/sst-v2/packages/functions/src/api-zod/todo.ts @@ -0,0 +1,46 @@ +import { Todo } from '@sst-v2/core/todo' +import { api, res } from '@refactorthis/funcy' +import { CreateTodoRequest, GetTodoPath, ListQuery, ListTodoResponse, TodoResponse } from './models' + +export const create = api({ + parser: { + request: CreateTodoRequest, + }, + handler: async ({ request }) => { + await Todo.create(request) + return res.created() + }, +}) + +export const get = api({ + parser: { + path: GetTodoPath, + response: TodoResponse, + }, + handler: async ({ path }) => { + const response = await Todo.get(path.id) + return res.ok(response) + }, +}) + +export const list = api({ + parser: { + query: ListQuery, + response: ListTodoResponse, + }, + handler: async ({ query }) => { + const { skip = 0, take = 20 } = query + const items = await Todo.list(skip, take) + return res.ok({ items, skip, take }) + }, +}) + +export const remove = api({ + parser: { + path: GetTodoPath, + }, + handler: async ({ path }) => { + await Todo.remove(path.id) + return res.ok() + }, +}) diff --git a/examples/sst-api/packages/functions/src/events/todo-created.ts b/examples/sst-v2/packages/functions/src/events/todo-created.ts similarity index 79% rename from examples/sst-api/packages/functions/src/events/todo-created.ts rename to examples/sst-v2/packages/functions/src/events/todo-created.ts index e068d84..cd0c5a4 100644 --- a/examples/sst-api/packages/functions/src/events/todo-created.ts +++ b/examples/sst-v2/packages/functions/src/events/todo-created.ts @@ -1,5 +1,5 @@ import { EventHandler } from 'sst/node/event-bus' -import { Todo } from '@sst-api/core/todo' +import { Todo } from '@sst-v2/core/todo' export const handler = EventHandler(Todo.Events.Created, async (evt) => { console.log('Todo created', evt) diff --git a/examples/sst-api/packages/functions/src/lambda.ts b/examples/sst-v2/packages/functions/src/lambda.ts similarity index 100% rename from examples/sst-api/packages/functions/src/lambda.ts rename to examples/sst-v2/packages/functions/src/lambda.ts diff --git a/examples/sst-api/packages/functions/sst-env.d.ts b/examples/sst-v2/packages/functions/sst-env.d.ts similarity index 100% rename from examples/sst-api/packages/functions/sst-env.d.ts rename to examples/sst-v2/packages/functions/sst-env.d.ts diff --git a/examples/sst-api/packages/functions/tsconfig.json b/examples/sst-v2/packages/functions/tsconfig.json similarity index 85% rename from examples/sst-api/packages/functions/tsconfig.json rename to examples/sst-v2/packages/functions/tsconfig.json index 0e84122..5e204f8 100644 --- a/examples/sst-api/packages/functions/tsconfig.json +++ b/examples/sst-v2/packages/functions/tsconfig.json @@ -6,7 +6,7 @@ "baseUrl": ".", "allowSyntheticDefaultImports": true, "paths": { - "@sst-api/core/*": ["../core/src/*"], + "@sst-v2/core/*": ["../core/src/*"], "@refactorthis/funcy": ["../../../../package"] } } diff --git a/examples/sst-api/pnpm-lock.yaml b/examples/sst-v2/pnpm-lock.yaml similarity index 99% rename from examples/sst-api/pnpm-lock.yaml rename to examples/sst-v2/pnpm-lock.yaml index 99d2c01..a0e6cd1 100644 --- a/examples/sst-api/pnpm-lock.yaml +++ b/examples/sst-v2/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + yup: + specifier: ^1.4.0 + version: 1.4.0 devDependencies: '@tsconfig/node18': specifier: ^18.2.4 @@ -3074,6 +3078,9 @@ packages: promptly@3.2.0: resolution: {integrity: sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==} + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -3363,6 +3370,9 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + tinybench@2.6.0: resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} @@ -3386,6 +3396,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3407,6 +3420,10 @@ packages: resolution: {integrity: sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==} engines: {node: '>=10'} + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -3687,6 +3704,9 @@ packages: yoga-wasm-web@0.3.3: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} + yup@1.4.0: + resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==} + zip-stream@4.1.1: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} @@ -9590,6 +9610,8 @@ snapshots: dependencies: read: 1.0.7 + property-expr@2.0.6: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -10158,6 +10180,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + tiny-case@1.0.3: {} + tinybench@2.6.0: {} tinypool@0.8.3: {} @@ -10172,6 +10196,8 @@ snapshots: toidentifier@1.0.1: {} + toposort@2.0.2: {} + tree-kill@1.2.2: {} tslib@1.14.1: {} @@ -10184,6 +10210,8 @@ snapshots: type-fest@0.12.0: {} + type-fest@2.19.0: {} + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -10429,6 +10457,13 @@ snapshots: yoga-wasm-web@0.3.3: {} + yup@1.4.0: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + zip-stream@4.1.1: dependencies: archiver-utils: 3.0.4 diff --git a/examples/sst-api/pnpm-workspace.yaml b/examples/sst-v2/pnpm-workspace.yaml similarity index 100% rename from examples/sst-api/pnpm-workspace.yaml rename to examples/sst-v2/pnpm-workspace.yaml diff --git a/examples/sst-api/readme.md b/examples/sst-v2/readme.md similarity index 88% rename from examples/sst-api/readme.md rename to examples/sst-v2/readme.md index e657f8f..cf08df5 100644 --- a/examples/sst-api/readme.md +++ b/examples/sst-v2/readme.md @@ -1,6 +1,6 @@ -# sst-api +# sst-v1 -This is an example project using SST & funcy. +This is an example project using SST v2 & funcy. See the [tutorial](https://sst.dev/examples/how-to-create-a-rest-api-with-serverless.html) for more information. diff --git a/examples/sst-api/sst.config.ts b/examples/sst-v2/sst.config.ts similarity index 90% rename from examples/sst-api/sst.config.ts rename to examples/sst-v2/sst.config.ts index 6a915d2..6bb5aeb 100644 --- a/examples/sst-api/sst.config.ts +++ b/examples/sst-v2/sst.config.ts @@ -4,7 +4,7 @@ import { API } from './stacks/MyStack' export default { config(_input) { return { - name: 'sst-api', + name: 'sst-v2', region: 'us-east-1', } }, diff --git a/examples/sst-api/stacks/ExampleStack.ts b/examples/sst-v2/stacks/ExampleStack.ts similarity index 100% rename from examples/sst-api/stacks/ExampleStack.ts rename to examples/sst-v2/stacks/ExampleStack.ts diff --git a/examples/sst-api/stacks/MyStack.ts b/examples/sst-v2/stacks/MyStack.ts similarity index 100% rename from examples/sst-api/stacks/MyStack.ts rename to examples/sst-v2/stacks/MyStack.ts diff --git a/examples/sst-api/tsconfig.json b/examples/sst-v2/tsconfig.json similarity index 100% rename from examples/sst-api/tsconfig.json rename to examples/sst-v2/tsconfig.json diff --git a/package.json b/package.json index 290e333..48473a8 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "typescript-eslint": "^7.18.0", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0", + "yup": "^1.4.0", "zod": "^3.23.8" }, "dependencies": { @@ -64,9 +65,6 @@ "@middy/warmup": "^5.4.6", "lodash.merge": "^4.6.2" }, - "peerDependencies": { - "zod": "^3.22.4" - }, "engines": { "node": ">=14" }, diff --git a/package/src/core/parsers.ts b/package/src/core/parsers.ts index 16099d2..d5bea69 100644 --- a/package/src/core/parsers.ts +++ b/package/src/core/parsers.ts @@ -5,13 +5,16 @@ * We use duck typing here, so that we don't need to pull in the package deps. */ -// zod export type ZodSchemaLike = { _input: TInput parseAsync(...args: any[]): any } -export type Schema = ZodSchemaLike // add other parsers here, eg. | YupSchemaLike +export type YupSchemaLike = { + validate(value: TInput, options?: any): Promise +} + +export type Schema = ZodSchemaLike | YupSchemaLike /** * Api parsing options diff --git a/package/src/integrations/api/middleware/validator.mware.ts b/package/src/integrations/api/middleware/validator.mware.ts index 3a1cd01..4de9764 100644 --- a/package/src/integrations/api/middleware/validator.mware.ts +++ b/package/src/integrations/api/middleware/validator.mware.ts @@ -80,5 +80,10 @@ const validate = async (schema: any, value: any, path: string[]) => { return await schema.parseAsync(value, { path }) } + // yup + if (typeof schema.validate === 'function') { + return await schema.validate(value) + } + throw new Error(`Unable to find validator function for schema ${schema}`) } diff --git a/package/test/integrations/api/validation-yup.test.ts b/package/test/integrations/api/validation-yup.test.ts new file mode 100644 index 0000000..c4ee88a --- /dev/null +++ b/package/test/integrations/api/validation-yup.test.ts @@ -0,0 +1,47 @@ +import { it, expect } from 'vitest' +import { createApi, res } from 'package/src/integrations/api' +import * as events from '../../mocks/api-proxy-events' +import { ctx } from '../../mocks/lambda-context' +import { object, number, string } from 'yup' +import { APIGatewayProxyEventV2 } from 'aws-lambda' + +const api = createApi() + +const fn = api({ + parser: { + request: object({ + id: number(), + name: string().required(), + }), + }, + handler: () => res.ok(), +}) + +it("should fail if doesn't pass validation", async () => { + const event: APIGatewayProxyEventV2 = { + ...events.payloadV2, + body: JSON.stringify({ + id: 1, + }), + } + + const response = await fn(event, ctx()) + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body!)).toMatchObject({ + message: 'Invalid Request', + details: ['name is a required field'], + }) +}) + +it('should succeed if passes validation', async () => { + const event: APIGatewayProxyEventV2 = { + ...events.payloadV2, + body: JSON.stringify({ + id: 1, + name: 'Test', + }), + } + + const response = await fn(event, ctx()) + expect(response.statusCode).toBe(200) +}) diff --git a/package/test/integrations/api/validation.test.ts b/package/test/integrations/api/validation.test.ts index e13ff49..ee2fdda 100644 --- a/package/test/integrations/api/validation.test.ts +++ b/package/test/integrations/api/validation.test.ts @@ -5,7 +5,7 @@ import { ctx } from '../../mocks/lambda-context' import z from 'zod' import { APIGatewayProxyEventV2 } from 'aws-lambda' -// TODO fix typescript warnings here +// TODO fix typescript warnings here (need to fix ApiResult to handle error schema) const api = createApi() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbbfb89..4746003 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,11 +108,14 @@ importers: vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.16.1)(@vitest/ui@1.6.0) + yup: + specifier: ^1.4.0 + version: 1.4.0 zod: specifier: ^3.23.8 version: 3.23.8 - examples/sst-api: + examples/sst-v2: devDependencies: '@tsconfig/node18': specifier: ^18.2.4 @@ -3259,6 +3262,9 @@ packages: promptly@3.2.0: resolution: {integrity: sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==} + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -3597,6 +3603,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -3620,6 +3629,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -3688,6 +3700,10 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -3968,6 +3984,9 @@ packages: yoga-wasm-web@0.3.3: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} + yup@1.4.0: + resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==} + zip-stream@4.1.1: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} @@ -8069,6 +8088,8 @@ snapshots: dependencies: read: 1.0.7 + property-expr@2.0.6: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -8520,6 +8541,8 @@ snapshots: dependencies: any-promise: 1.3.0 + tiny-case@1.0.3: {} + tinybench@2.9.0: {} tinypool@0.8.4: {} @@ -8534,6 +8557,8 @@ snapshots: toidentifier@1.0.1: {} + toposort@2.0.2: {} + totalist@3.0.1: {} tr46@1.0.1: @@ -8591,6 +8616,8 @@ snapshots: type-fest@0.20.2: {} + type-fest@2.19.0: {} + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -8845,6 +8872,13 @@ snapshots: yoga-wasm-web@0.3.3: {} + yup@1.4.0: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + zip-stream@4.1.1: dependencies: archiver-utils: 3.0.4 diff --git a/readme.md b/readme.md index 2e20716..1636b20 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@
-> **NOTE** funcy is in pre-release. The API may change at any time. +> **NOTE** funcy is in pre-release. We will attempt to keep the API as-is, however there may be breaking changes during this period. ## funcy @@ -32,6 +32,7 @@ - [Installation](#installation) - [Writing funcy Functions](#writing-funcy-functions) - [Examples](#examples) +- [Schema Parsing Support](#schema-parsing-support) - [Performance Comparisons](#performance-comparisons) - [Roadmap](#roadmap) - [See Also](#see-also) @@ -61,8 +62,8 @@ import { CreateCustomerRequest, CreateCustomerResponse } from './dtos' // create customer handler export const create = api({ parser: { - request: CreateCustomerRequest, // zod schema - response: CreateCustomerResponse, // zod schema + request: CreateCustomerRequest, // zod or yup schema + response: CreateCustomerResponse, // zod or yup schema }, handler: async ({ request }) = { const response = await Customer.create(request) @@ -86,16 +87,18 @@ funcy also has a full middleware pipeline, allowing you to control things like C ### Installation ```sh -pnpm add @funcy/api -#bun add @funcy/api -#yarn add @funcy/api -#npm install --save-dev @funcy/api +pnpm add @refactorthis/funcy +#bun add @refactorthis/funcy +#yarn add @refactorthis/funcy +#npm install --save @refactorthis/funcy ``` -If you haven't already installed your validation framework, add one to your package. We strongly recommend zod. +If you haven't already installed your validation framework, add one to your package. ```sh pnpm add zod +# or +# pnpm add yup ``` ### Writing funcy Functions @@ -108,8 +111,8 @@ import { MyRequest, MyResponse } from './dtos' export const handler = api({ parser: { - request: MyRequest, // zod schema - response: MyResponse, // zod schema + request: MyRequest, // zod or yup schema + response: MyResponse, // zod or yup schema }, handler: async({ request }) = { // request is the strongly typed request body @@ -219,6 +222,13 @@ export const handler = api({ }) ``` +## Schema Parsing Support + +funcy is built to be agnostic of schema library. At this time funcy supports: + +- [zod](https://github.com/colinhacks/zod) +- [yup](https://github.com/jquense/yup) + ## Performance Comparisons funcy @@ -228,7 +238,7 @@ Koa ## Roadmap -- Support for other validators +- Support for other schema libraries - Verify support for legacy API Gateway proxy integration (< v2) - Performance test comparison with nest.js, raw lambda, etc. - Other lambda integrations (s3, dynamo, etc) @@ -239,12 +249,18 @@ Koa ### Complementary Packages +Working with zod: + - [zod](https://github.com/colinhacks/zod) - a great Typescript-first validation framework, which can infer your DTO types automatically - [zod-to-openapi](https://github.com/asteasolutions/zod-to-openapi) - generate your Open API definition code-first from zod schemas. - [openapi-zod-client](https://github.com/astahmer/openapi-zod-client) - alternatively, generate your code from your design-first Open API definition +Working with yup: + +- [yup](https://github.com/jquense/yup) - Dead simple Object schema validation +- [openapi-yup-generator](https://github.com/igtm/openapi-yup-generator) - Generate your yup schemas from Open API definition + ### Acknowledgements - [middy.js](https://github.com/middyjs/middy) - powers the funcy pipeline with it's extensible middleware framework. - [TypeScript](https://github.com/microsoft/TypeScript) - strong-typing is critical for maintainability and reducing bugs. -- [zod](https://github.com/colinhacks/zod) - Developer-friendly, TypeScript validation framework