diff --git a/API.md b/API.md index 77800943..f3142dee 100644 --- a/API.md +++ b/API.md @@ -5,7 +5,6 @@ - [failure](#failure) - [fromSuccess](#fromsuccess) - [success](#success) - - [withSchema](#withschema) - [Combinators](#combinators) - [all](#all) - [branch](#branch) @@ -48,17 +47,17 @@ # Constructors ## applySchema -It takes a composable and schemas for the input and context, and returns the same composable with the schemas applied. So the types will be asserted at runtime. +It turns a function or a composition of functions into a `ComposableWithSchema` which will have `unknown` input and context, so the types will be asserted at runtime. It is useful when dealing with external data, such as API requests, where you want to ensure the data is in the correct shape before processing it. ```ts -const fn = composable(( +const fn = ( { greeting }: { greeting: string }, { user }: { user: { name: string } }, ) => ({ message: `${greeting} ${user.name}` -})) +}) const safeFunction = applySchema( z.object({ greeting: z.string() }), @@ -68,8 +67,10 @@ const safeFunction = applySchema( )(fn) type Test = typeof safeFunction -// ^? Composable<(input?: unknown, ctx?: unknown) => { message: string }> +// ^? ComposableWithSchema<{ message: string }> ``` +For didactit purposes: `ComposableWithSchema === Composable<(i?: unknown, c?: unknown) => T>` + ## composable This is the primitive function to create composable functions. It takes a function and returns a composable function. @@ -150,32 +151,6 @@ expect(result).toEqual({ }) ``` -## withSchema -It creates a composable with unknown input and context types, and applies the schemas to them so the arguments are assured at runtime. - -See `applySchema` above for more information. - -```ts -import { z } from 'zod' - -const runtimeSafeAdd = withSchema(z.number(), z.number())((a, b) => a + b) -// ^? Composable<(i?: unknown, e?: unknown) => number> -const result = await runtimeSafeAdd(1, 2) -console.log(result.success ? result.data : result.errors) -``` - -If there are input or context errors, they will be returned in the `errors` property of the result. -```ts -const result = await runtimeSafeAdd('1', null) -// { -// success: false, -// errors: [ -// new InputError('Expected number, received string'), -// new ContextError('Expected number, received null') -// ], -// } -``` - # Combinators These combinators are useful for composing functions. They operate on either plain functions or composables. They all return a `Composable`, thus allowing further application in more compositions. @@ -208,7 +183,7 @@ For the example above, the result will be: If any of the constituent functions fail, the `errors` field (on the composite function's result) will be an array of the concatenated errors from each failing function: ```ts -const a = withSchema(z.object({ id: z.number() }))(({ id }) => { +const a = applySchema(z.object({ id: z.number() }))(({ id }) => { return String(id) }) const b = () => { @@ -335,7 +310,7 @@ const fetchAsText = ({ userId }: { userId: number }) => { return fetch(`https://reqres.in/api/users/${String(userId)}`) .then((r) => r.json()) } -const fullName = withSchema( +const fullName = applySchema( z.object({ first_name: z.string(), last_name: z.string() }), )(({ first_name, last_name }) => `${first_name} ${last_name}`) @@ -511,7 +486,7 @@ const trackErrors = trace(async (result, ...args) => { # Input Resolvers We export some functions to help you extract values out of your requests before sending them as user input. -These functions are better suited for composables with runtime validation, such as those built with `withSchema` (or `applySchema`) since they deal with external data and `withSchema` will ensure type-safety in runtime. +These functions are better suited for composables with runtime validation, such as those built with `applySchema` since they deal with external data and `applySchema` will ensure type-safety in runtime. For more details on how to structure your data, refer to this [test file](./src/tests/input-resolvers.test.ts). @@ -617,7 +592,7 @@ async (request: Request) => { # Error Constructors and Handlers The `Failure` results contain a list of errors that can be of any extended class of `Error`. -To help with composables `withSchema` though, we provide some constructors that will help you create errors to differentiate between kinds of errors. +However, to help with composables with schema, we provide some constructors that will help you create errors to differentiate between kinds of errors. ## ErrorList An `ErrorList` is a special kind of error that carries a list of errors that can be used to represent multiple errors in a single result. @@ -645,7 +620,7 @@ An `ContextError` is a special kind of error that represents an error in the con It has an optional second parameter that is an array of strings representing the path to the error in the context schema. ```ts -const fn = withSchema( +const fn = applySchema( z.object({ id: z.number() }), z.object({ user: z.object({ id: z.string() }), diff --git a/README.md b/README.md index 9bf02991..739a4971 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A set of types and functions to make compositions easy and safe. - 🏝️ Isolated Business Logic: Split your code into composable functions, making your code easier to test and maintain. - πŸ”’ End-to-End Type Safety: Achieve end-to-end type safety from the backend to the UI with serializable results, ensuring data integrity across your entire application. - ⚑ Parallel and Sequential Compositions: Compose functions both in parallel - with `all` and `collect` - and sequentially - with `pipe`, `branch`, and `sequence` -, to manage complex data flows optimizing your code for performance and clarity. -- πŸ•΅οΈβ€β™‚οΈ Runtime Validation: Use `withSchema` or `applySchema` with your favorite parser for optional runtime validation of inputs and context, enforcing data integrity only when needed. +- πŸ•΅οΈβ€β™‚οΈ Runtime Validation: Use `applySchema` with your favorite parser for optional runtime validation of inputs and context, enforcing data integrity only when needed. - πŸš‘ Resilient Error Handling: Leverage enhanced combinators like `mapErrors` and `catchFailure` to transform and handle errors more effectively. - πŸ“Š Traceable Compositions: Use the `trace` function to log and monitor your composable functions’ inputs and results, simplifying debugging and monitoring. @@ -93,7 +93,7 @@ We can also extend the same reasoning to functions that return promises in a tra This library also defines several operations besides the `pipe` to compose functions in arbitrary ways, giving a powerful tool for the developer to reason about the data flow **without worrying about mistakenly connecting the wrong parameters** or **forgetting to unwrap some promise** or **handle some error** along the way. ### Adding runtime validation to the Composable -To ensure type safety at runtime, use the `applySchema` or `withSchema` functions to validate external inputs against defined schemas. These schemas can be specified with libraries such as [Zod](https://github.com/colinhacks/zod/) or [ArkType](https://github.com/arktypeio/arktype). +To ensure type safety at runtime, use the `applySchema` function to validate external inputs against defined schemas. These schemas can be specified with libraries such as [Zod](https://github.com/colinhacks/zod/) or [ArkType](https://github.com/arktypeio/arktype). Note that the resulting `Composable` will have unknown types for the parameters now that we rely on runtime validation. @@ -105,9 +105,6 @@ const addAndReturnWithRuntimeValidation = applySchema( z.number(), z.number(), )(addAndReturnString) - -// Or you could have defined schemas and implementation in one shot: -const add = withSchema(z.number(), z.number())((a, b) => a + b) ``` For more information and examples, check the [Handling external input](./with-schema.md) guide. diff --git a/examples/arktype/README.md b/examples/arktype/README.md index a9b6a6e7..4344fa74 100644 --- a/examples/arktype/README.md +++ b/examples/arktype/README.md @@ -4,6 +4,6 @@ This simple example can be a reference to adapt composable-functions to any othe There are two approaches to use composable-functions with a custom parser: - Create an adapter function that will receive a schema and return a schema in the shape of a `ParserSchena`. Example: [the `adapt` function](./src/adapters.ts). This is our preferred approach and we wrote a [post about it](https://dev.to/seasonedcc/using-arktype-in-place-of-zod-how-to-adapt-parsers-3bd5). -- Create your custom `withSchema` and `applySchema` that will validate your input and context and return a `Result`. Example: [the `withArkSchema` and `applyArkSchema` functions](./src/adapters.ts). +- Create your custom `applySchema` that will validate your input and context and return a `Result`. Example: [the `applyArkSchema` function](./src/adapters.ts). Check out the [`./src`](./src/) directory to understand how we implemented both approaches with [`arktype`](https://github.com/arktypeio/arktype). diff --git a/examples/arktype/package.json b/examples/arktype/package.json index c77f20c8..3e7fbad5 100644 --- a/examples/arktype/package.json +++ b/examples/arktype/package.json @@ -11,8 +11,8 @@ "tsx": "^4.15.8" }, "dependencies": { - "arktype": "2.0.0-dev.26", + "arktype": "2.0.0-dev.29", "composable-functions": "file:../../npm", - "typescript": "^5.5.2" + "typescript": "^5.5.3" } } diff --git a/examples/arktype/pnpm-lock.yaml b/examples/arktype/pnpm-lock.yaml index d1a49673..9cffb70d 100644 --- a/examples/arktype/pnpm-lock.yaml +++ b/examples/arktype/pnpm-lock.yaml @@ -9,23 +9,23 @@ importers: .: dependencies: arktype: - specifier: 2.0.0-dev.26 - version: 2.0.0-dev.26 + specifier: 2.0.0-dev.29 + version: 2.0.0-dev.29 composable-functions: specifier: file:../../npm version: file:../../npm typescript: - specifier: ^5.5.2 - version: 5.5.2 + specifier: ^5.5.3 + version: 5.5.3 devDependencies: tsx: - specifier: ^4.15.7 - version: 4.15.7 + specifier: ^4.15.8 + version: 4.16.2 packages: - '@arktype/schema@0.1.18': - resolution: {integrity: sha512-6xMboHvnO3NtgYNP160PmghElUDpKe/aDMqH2hRRHoMb1Nh0bPilDfr9LJnXQEwfA5hDguqRM+vifOKkROtOLA==} + '@arktype/schema@0.1.22': + resolution: {integrity: sha512-3HAf9EcIgu/H9IIOtfY2yH1exuF5b7z3GmMrPuI9xhC0OwKw5DK/57MNGSxbx9JMw51RLxL/fcEOnyNiXEDT+g==} '@arktype/util@0.0.51': resolution: {integrity: sha512-lukmZcbSVI27ZJ4uJA7ztCKM5OligUfdTrr0E6k+F0tzz9iSEcplS0KZbVD2WfrLL3HXNKBE6g/w3nhYMAMoqw==} @@ -168,8 +168,8 @@ packages: cpu: [x64] os: [win32] - arktype@2.0.0-dev.26: - resolution: {integrity: sha512-L7JkKHPkvEoDRKhtXyrCQcHast2oXFo7sOX1SZ3GSbIFJcEnTRRW12lFvH8M4fFR589CaOE9LleHMg9jZFR2TA==} + arktype@2.0.0-dev.29: + resolution: {integrity: sha512-SJluMYDyaGOYUntlG0rxd8ErRdTGceUb6Svsf5u1aAzSurwsh6my9ktkHA99wtJGmz/CbTv9T7OOan3cUppzUQ==} composable-functions@file:../../npm: resolution: {directory: ../../npm, type: directory} @@ -190,19 +190,19 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - tsx@4.15.7: - resolution: {integrity: sha512-u3H0iSFDZM3za+VxkZ1kywdCeHCn+8/qHQS1MNoO2sONDgD95HlWtt8aB23OzeTmFP9IU4/8bZUdg58Uu5J4cg==} + tsx@4.16.2: + resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==} engines: {node: '>=18.0.0'} hasBin: true - typescript@5.5.2: - resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} hasBin: true snapshots: - '@arktype/schema@0.1.18': + '@arktype/schema@0.1.22': dependencies: '@arktype/util': 0.0.51 @@ -277,9 +277,9 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - arktype@2.0.0-dev.26: + arktype@2.0.0-dev.29: dependencies: - '@arktype/schema': 0.1.18 + '@arktype/schema': 0.1.22 '@arktype/util': 0.0.51 composable-functions@file:../../npm: {} @@ -319,11 +319,11 @@ snapshots: resolve-pkg-maps@1.0.0: {} - tsx@4.15.7: + tsx@4.16.2: dependencies: esbuild: 0.21.5 get-tsconfig: 4.7.5 optionalDependencies: fsevents: 2.3.3 - typescript@5.5.2: {} + typescript@5.5.3: {} diff --git a/examples/arktype/src/adapters.ts b/examples/arktype/src/adapters.ts index c572b2d0..21cbf5cd 100644 --- a/examples/arktype/src/adapters.ts +++ b/examples/arktype/src/adapters.ts @@ -1,13 +1,10 @@ import { ApplySchemaReturn, composable, - Composable, - ComposableWithSchema, ContextError, failure, InputError, ParserSchema, - UnpackData, } from 'composable-functions' import { type, Type } from 'arktype' @@ -31,8 +28,8 @@ function adapt(schema: T): ParserSchema { */ function applyArkSchema(inputSchema?: Type, contextSchema?: Type) { // - return ( - fn: Composable<(input: Input, context: Context) => R>, + return ( + fn: (input: Input, context: Context) => R, ) => { const callable = ((input?: unknown, context?: unknown) => { const ctxResult = (contextSchema ?? type('unknown'))(context) @@ -55,24 +52,11 @@ function applyArkSchema(inputSchema?: Type, contextSchema?: Type) { : [] return failure([...inputErrors, ...ctxErrors]) } - return fn(result as Input, ctxResult as Context) + return composable(fn)(result as Input, ctxResult as Context) }) as ApplySchemaReturn ;(callable as any).kind = 'composable' as const return callable } } -function withArkSchema( - inputSchema?: Type, - contextSchema?: Type, -): unknown>( - fn: Fn, -) => ComposableWithSchema>> { - return (handler) => - applyArkSchema( - inputSchema, - contextSchema, - )(composable(handler)) as ComposableWithSchema -} - -export { adapt, withArkSchema, applyArkSchema } +export { adapt, applyArkSchema } diff --git a/examples/arktype/src/usage.ts b/examples/arktype/src/usage.ts index c8fbb81f..3c4dec24 100644 --- a/examples/arktype/src/usage.ts +++ b/examples/arktype/src/usage.ts @@ -1,16 +1,16 @@ -import { composable, withSchema } from 'composable-functions' -import { applyArkSchema, withArkSchema, adapt } from './adapters' +import { applySchema, composable } from 'composable-functions' +import { applyArkSchema, adapt } from './adapters' import { type } from 'arktype' const appliedFn = applyArkSchema(type({ a: 'number', b: 'number' }))( composable(({ a, b }: { a: number; b: number }) => a + b), ) -const withFn = withArkSchema( +const withFn = applyArkSchema( type({ a: 'number' }), type({ b: 'number' }), )(({ a }, { b }) => a + b) -const withAdapt = withSchema(adapt(type({ a: 'number', b: 'number' })))( +const withAdapt = applySchema(adapt(type({ a: 'number', b: 'number' })))( ({ a, b }) => a + b, ) diff --git a/examples/remix/app/business/colors.ts b/examples/remix/app/business/colors.ts index 5ae599c1..8ade1069 100644 --- a/examples/remix/app/business/colors.ts +++ b/examples/remix/app/business/colors.ts @@ -1,5 +1,5 @@ import * as z from 'zod' -import { withSchema } from 'composable-functions' +import { applySchema } from 'composable-functions' import { makeService } from 'make-service' const reqRes = makeService('https://reqres.in/api') @@ -22,7 +22,7 @@ const getColor = async ({ id }: { id: string }) => { return response.json(z.object({ data: colorSchema })) } -const mutateColor = withSchema( +const mutateColor = applySchema( z.object({ id: z.string(), color: z.string().min(1, 'Color is required'), diff --git a/examples/remix/app/business/gpd.ts b/examples/remix/app/business/gpd.ts index 41904180..7eeaa631 100644 --- a/examples/remix/app/business/gpd.ts +++ b/examples/remix/app/business/gpd.ts @@ -1,12 +1,12 @@ import { z } from 'zod' -import { withSchema } from 'composable-functions' +import { applySchema } from 'composable-functions' import { createCookie } from '@remix-run/node' const cookie = createCookie('gpd', { maxAge: 20, // seconds }) -const getGPDInfo = withSchema( +const getGPDInfo = applySchema( z.any(), // The "context" knows there can be cookie information in the Request z.object({ agreed: z.boolean().optional() }), @@ -14,7 +14,7 @@ const getGPDInfo = withSchema( return { agreed } }) -const agreeToGPD = withSchema( +const agreeToGPD = applySchema( // Agreeing to the GPD is user input z.object({ agree: z.preprocess((v) => v === 'true', z.boolean()) }), )(async ({ agree }) => ({ agreed: agree })) diff --git a/examples/remix/app/routes/color.$id.tsx b/examples/remix/app/routes/color.$id.tsx index d2f8daa8..33ff6332 100644 --- a/examples/remix/app/routes/color.$id.tsx +++ b/examples/remix/app/routes/color.$id.tsx @@ -1,13 +1,13 @@ import { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node' import { Form, Link, useActionData, useLoaderData } from '@remix-run/react' -import { withSchema, inputFromForm } from 'composable-functions' +import { applySchema, inputFromForm } from 'composable-functions' import tinycolor from 'tinycolor2' import { getColor, mutateColor } from '~/business/colors' import { actionResponse, loaderResponseOrThrow } from '~/lib' import { z } from 'zod' export const loader = async ({ params }: LoaderFunctionArgs) => { - const result = await withSchema(z.object({ id: z.string() }))(getColor)( + const result = await applySchema(z.object({ id: z.string() }))(getColor)( params, ) return loaderResponseOrThrow(result) diff --git a/examples/remix/package.json b/examples/remix/package.json index ff63e4f9..529849ee 100644 --- a/examples/remix/package.json +++ b/examples/remix/package.json @@ -11,30 +11,30 @@ "typecheck": "tsc" }, "dependencies": { - "@remix-run/node": "^2.10.0", - "@remix-run/react": "^2.10.0", - "@remix-run/serve": "^2.10.0", + "@remix-run/node": "^2.10.2", + "@remix-run/react": "^2.10.2", + "@remix-run/serve": "^2.10.2", "@types/tinycolor2": "^1.4.6", "composable-functions": "file:../../npm", - "isbot": "^5.1.10", + "isbot": "^5.1.13", "make-service": "^3.0.0", - "postcss": "^8.4.38", + "postcss": "^8.4.39", "react": "^18.3.1", "react-dom": "^18.3.1", "tinycolor2": "^1.6.0", "zod": "^3.23.8" }, "devDependencies": { - "@remix-run/dev": "^2.10.0", - "@remix-run/eslint-config": "^2.10.0", + "@remix-run/dev": "^2.10.2", + "@remix-run/eslint-config": "^2.10.2", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.14.1", - "@typescript-eslint/parser": "^7.14.1", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "eslint": "^8.57.0", - "tailwindcss": "^3.4.4", - "typescript": "^5.5.2", - "vite": "^5.3.1", + "tailwindcss": "^3.4.5", + "typescript": "^5.5.3", + "vite": "^5.3.3", "vite-tsconfig-paths": "^4.3.2" }, "engines": { diff --git a/examples/remix/pnpm-lock.yaml b/examples/remix/pnpm-lock.yaml index fedf0907..abbad151 100644 --- a/examples/remix/pnpm-lock.yaml +++ b/examples/remix/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: dependencies: '@remix-run/node': - specifier: ^2.10.0 - version: 2.10.0(typescript@5.5.2) + specifier: ^2.10.2 + version: 2.10.2(typescript@5.5.3) '@remix-run/react': - specifier: ^2.10.0 - version: 2.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2) + specifier: ^2.10.2 + version: 2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) '@remix-run/serve': - specifier: ^2.10.0 - version: 2.10.0(typescript@5.5.2) + specifier: ^2.10.2 + version: 2.10.2(typescript@5.5.3) '@types/tinycolor2': specifier: ^1.4.6 version: 1.4.6 @@ -24,14 +24,14 @@ importers: specifier: file:../../npm version: file:../../npm isbot: - specifier: ^5.1.10 - version: 5.1.10 + specifier: ^5.1.13 + version: 5.1.13 make-service: specifier: ^3.0.0 version: 3.0.0 postcss: - specifier: ^8.4.38 - version: 8.4.38 + specifier: ^8.4.39 + version: 8.4.39 react: specifier: ^18.3.1 version: 18.3.1 @@ -46,11 +46,11 @@ importers: version: 3.23.8 devDependencies: '@remix-run/dev': - specifier: ^2.10.0 - version: 2.10.0(@remix-run/react@2.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/serve@2.10.0(typescript@5.5.2))(@types/node@20.14.9)(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.9)) + specifier: ^2.10.2 + version: 2.10.2(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(@remix-run/serve@2.10.2(typescript@5.5.3))(@types/node@20.14.9)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)) '@remix-run/eslint-config': - specifier: ^2.10.0 - version: 2.10.0(eslint@8.57.0)(react@18.3.1)(typescript@5.5.2) + specifier: ^2.10.2 + version: 2.10.2(eslint@8.57.0)(react@18.3.1)(typescript@5.5.3) '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -58,26 +58,26 @@ importers: specifier: ^18.3.0 version: 18.3.0 '@typescript-eslint/eslint-plugin': - specifier: ^7.14.1 - version: 7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2) + specifier: ^7.16.0 + version: 7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) '@typescript-eslint/parser': - specifier: ^7.14.1 - version: 7.14.1(eslint@8.57.0)(typescript@5.5.2) + specifier: ^7.16.0 + version: 7.16.0(eslint@8.57.0)(typescript@5.5.3) eslint: specifier: ^8.57.0 version: 8.57.0 tailwindcss: - specifier: ^3.4.4 - version: 3.4.4 + specifier: ^3.4.5 + version: 3.4.5 typescript: - specifier: ^5.5.2 - version: 5.5.2 + specifier: ^5.5.3 + version: 5.5.3 vite: - specifier: ^5.3.1 - version: 5.3.1(@types/node@20.14.9) + specifier: ^5.3.3 + version: 5.3.3(@types/node@20.14.9) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.9)) + version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)) packages: @@ -652,13 +652,13 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@remix-run/dev@2.10.0': - resolution: {integrity: sha512-GZE4Rck6/1Q16/QtRSq1SyfVf6KhUvJqHZoTmAl56xRVscyA1alDugXVHihwd0YuoaS6/vZ3Rb4eI5roWVrExw==} + '@remix-run/dev@2.10.2': + resolution: {integrity: sha512-7hHC9WY65IJ5ex9Vrv9PkSg15mmYH63unxPDAR74hSfSkectMgsWtMChzdx7Kp/CzN2rttt3cxPwZnAu6PXJUw==} engines: {node: '>=18.0.0'} hasBin: true peerDependencies: - '@remix-run/react': ^2.10.0 - '@remix-run/serve': ^2.10.0 + '@remix-run/react': ^2.10.2 + '@remix-run/serve': ^2.10.2 typescript: ^5.1.0 vite: ^5.1.0 wrangler: ^3.28.2 @@ -672,8 +672,8 @@ packages: wrangler: optional: true - '@remix-run/eslint-config@2.10.0': - resolution: {integrity: sha512-6UNOVsV6T7Q0BCQ28heT4tMzZFDnNPTZXzwCM3bPf6gRZgZ8IlBX3ZvmncisRHiiNphYtmXR/p/e3kJWNH5OCA==} + '@remix-run/eslint-config@2.10.2': + resolution: {integrity: sha512-pg1kZXUePaZMg+2gxMpaJ+t69un5anuVmw9CcuqTpPr+8QnP72NCxt0Ic88KXupajJ7GrIK7PfwUkfqNlCN6xQ==} engines: {node: '>=18.0.0'} peerDependencies: eslint: ^8.0.0 @@ -683,8 +683,8 @@ packages: typescript: optional: true - '@remix-run/express@2.10.0': - resolution: {integrity: sha512-xmHxumbglfbCVJa9a9dMDtOD408DYH27LAHuGfEAPPSf5sc9WMEFBItNxp3bJZ2W2NDeava+h+7BIeR5wNpgxw==} + '@remix-run/express@2.10.2': + resolution: {integrity: sha512-er8b1aLULkM3KHTrU97ovBy5KDu53gCE7VjbqefHG9ZYLMZPOifawmCUaNAirhpkxW/nb08gyJo/5c+WYRrsuQ==} engines: {node: '>=18.0.0'} peerDependencies: express: ^4.19.2 @@ -693,8 +693,8 @@ packages: typescript: optional: true - '@remix-run/node@2.10.0': - resolution: {integrity: sha512-hFBt431leCEoN84kVj6BExv60+3KHFORTU2t24igJNtXPNCHH/pujMMKYaSrS3a5oKDTwwqbCmSztTyyr7uFLA==} + '@remix-run/node@2.10.2': + resolution: {integrity: sha512-Ni4yMQCf6avK2fz91/luuS3wnHzqtbxsdc19es1gAWEnUKfeCwqq5v1R0kzNwrXyh5NYCRhxaegzVH3tGsdYFg==} engines: {node: '>=18.0.0'} peerDependencies: typescript: ^5.1.0 @@ -702,8 +702,8 @@ packages: typescript: optional: true - '@remix-run/react@2.10.0': - resolution: {integrity: sha512-XgNpyAoiNCq0jDZb8HPUS6sNJGWx31iJoqOlPy4KkjNexhq+COCN9QzXvsPIm2LpcPG3w7+loKmLMCTsTlH+Eg==} + '@remix-run/react@2.10.2': + resolution: {integrity: sha512-0Fx3AYNjfn6Z/0xmIlVC7exmof20M429PwuApWF1H8YXwdkI+cxLfivRzTa1z7vS55tshurqQum98jQQaUDjoA==} engines: {node: '>=18.0.0'} peerDependencies: react: ^18.0.0 @@ -713,17 +713,17 @@ packages: typescript: optional: true - '@remix-run/router@1.17.0': - resolution: {integrity: sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==} + '@remix-run/router@1.17.1': + resolution: {integrity: sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q==} engines: {node: '>=14.0.0'} - '@remix-run/serve@2.10.0': - resolution: {integrity: sha512-JOF2x8HwXo4G5hOqKEUEG7Xm2ZYYL0kZJcWdJVIQxaRN/EmbmJCKGLcW21rZ6VZ/FqpEkSnwmiGFoDkPQ9q6lg==} + '@remix-run/serve@2.10.2': + resolution: {integrity: sha512-ryWW5XK4Ww2mx1yhZPIycNqniZhzwybj61DIPO4cJxThvUkYgXf+Wdzq4jhva2B99naAiu18Em0nwh8VZxFMew==} engines: {node: '>=18.0.0'} hasBin: true - '@remix-run/server-runtime@2.10.0': - resolution: {integrity: sha512-lwgMq3m8U+oz0ZmgOrQeAE3tf6g1LPf2+ff3lgb5xb4bx7bV5VGGwKkzRK4+0fDguHXcW+YcppvdYdnKzQn1YQ==} + '@remix-run/server-runtime@2.10.2': + resolution: {integrity: sha512-c6CzKw4WBP4FkPnz63ua7g73/P1v34Uho2C44SZZf8IOVCGzEM9liLq6slDivn0m/UbyQnXThdXmsVjFcobmZg==} engines: {node: '>=18.0.0'} peerDependencies: typescript: ^5.1.0 @@ -902,8 +902,8 @@ packages: typescript: optional: true - '@typescript-eslint/eslint-plugin@7.14.1': - resolution: {integrity: sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==} + '@typescript-eslint/eslint-plugin@7.16.0': + resolution: {integrity: sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -923,8 +923,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.14.1': - resolution: {integrity: sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==} + '@typescript-eslint/parser@7.16.0': + resolution: {integrity: sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -937,8 +937,8 @@ packages: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/scope-manager@7.14.1': - resolution: {integrity: sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==} + '@typescript-eslint/scope-manager@7.16.0': + resolution: {integrity: sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==} engines: {node: ^18.18.0 || >=20.0.0} '@typescript-eslint/type-utils@5.62.0': @@ -951,8 +951,8 @@ packages: typescript: optional: true - '@typescript-eslint/type-utils@7.14.1': - resolution: {integrity: sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==} + '@typescript-eslint/type-utils@7.16.0': + resolution: {integrity: sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -965,8 +965,8 @@ packages: resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/types@7.14.1': - resolution: {integrity: sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==} + '@typescript-eslint/types@7.16.0': + resolution: {integrity: sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==} engines: {node: ^18.18.0 || >=20.0.0} '@typescript-eslint/typescript-estree@5.62.0': @@ -978,8 +978,8 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@7.14.1': - resolution: {integrity: sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==} + '@typescript-eslint/typescript-estree@7.16.0': + resolution: {integrity: sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -993,8 +993,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@typescript-eslint/utils@7.14.1': - resolution: {integrity: sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==} + '@typescript-eslint/utils@7.16.0': + resolution: {integrity: sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1003,8 +1003,8 @@ packages: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/visitor-keys@7.14.1': - resolution: {integrity: sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==} + '@typescript-eslint/visitor-keys@7.16.0': + resolution: {integrity: sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==} engines: {node: ^18.18.0 || >=20.0.0} '@ungap/structured-clone@1.2.0': @@ -2214,8 +2214,8 @@ packages: isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isbot@5.1.10: - resolution: {integrity: sha512-ob0oFuq/YcNxPZKhGJ+t0TDdDT/5BPNIk74r41528qAf2or2fgUY7Ty8vl0wEz/LIiaJ9JG4qSa1yBqCdPg5ag==} + isbot@5.1.13: + resolution: {integrity: sha512-RXtBib4m9zChSb+187EpNQML7Z3u2i34zDdqcRFZnqSJs0xdh91xzJytc5apYVg+9Y4NGnUQ0AIeJvX9FAnCUw==} engines: {node: '>=18'} isexe@2.0.0: @@ -2892,8 +2892,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + postcss@8.4.39: + resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2985,15 +2985,15 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react-router-dom@6.24.0: - resolution: {integrity: sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==} + react-router-dom@6.24.1: + resolution: {integrity: sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router@6.24.0: - resolution: {integrity: sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==} + react-router@6.24.1: + resolution: {integrity: sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' @@ -3302,8 +3302,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwindcss@3.4.4: - resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} + tailwindcss@3.4.5: + resolution: {integrity: sha512-DlTxttYcogpDfx3tf/8jfnma1nfAYi2cBUYV2YNoPPecwmO3YGiFlOX9D8tGAu+EDF38ryBzvrDKU/BLMsUwbw==} engines: {node: '>=14.0.0'} hasBin: true @@ -3425,8 +3425,8 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - typescript@5.5.2: - resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} hasBin: true @@ -3540,8 +3540,8 @@ packages: vite: optional: true - vite@5.3.1: - resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} + vite@5.3.3: + resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4217,7 +4217,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@remix-run/dev@2.10.0(@remix-run/react@2.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/serve@2.10.0(typescript@5.5.2))(@types/node@20.14.9)(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.9))': + '@remix-run/dev@2.10.2(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(@remix-run/serve@2.10.2(typescript@5.5.3))(@types/node@20.14.9)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9))': dependencies: '@babel/core': 7.24.7 '@babel/generator': 7.24.7 @@ -4229,10 +4229,10 @@ snapshots: '@babel/types': 7.24.7 '@mdx-js/mdx': 2.3.0 '@npmcli/package-json': 4.0.1 - '@remix-run/node': 2.10.0(typescript@5.5.2) - '@remix-run/react': 2.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2) - '@remix-run/router': 1.17.0 - '@remix-run/server-runtime': 2.10.0(typescript@5.5.2) + '@remix-run/node': 2.10.2(typescript@5.5.3) + '@remix-run/react': 2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) + '@remix-run/router': 1.17.1 + '@remix-run/server-runtime': 2.10.2(typescript@5.5.3) '@types/mdx': 2.0.13 '@vanilla-extract/integration': 6.5.0(@types/node@20.14.9) arg: 5.0.2 @@ -4259,10 +4259,10 @@ snapshots: picocolors: 1.0.1 picomatch: 2.3.1 pidtree: 0.6.0 - postcss: 8.4.38 - postcss-discard-duplicates: 5.1.0(postcss@8.4.38) - postcss-load-config: 4.0.2(postcss@8.4.38) - postcss-modules: 6.0.0(postcss@8.4.38) + postcss: 8.4.39 + postcss-discard-duplicates: 5.1.0(postcss@8.4.39) + postcss-load-config: 4.0.2(postcss@8.4.39) + postcss-modules: 6.0.0(postcss@8.4.39) prettier: 2.8.8 pretty-ms: 7.0.1 react-refresh: 0.14.2 @@ -4274,9 +4274,9 @@ snapshots: tsconfig-paths: 4.2.0 ws: 7.5.10 optionalDependencies: - '@remix-run/serve': 2.10.0(typescript@5.5.2) - typescript: 5.5.2 - vite: 5.3.1(@types/node@20.14.9) + '@remix-run/serve': 2.10.2(typescript@5.5.3) + typescript: 5.5.3 + vite: 5.3.3(@types/node@20.14.9) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -4292,43 +4292,43 @@ snapshots: - ts-node - utf-8-validate - '@remix-run/eslint-config@2.10.0(eslint@8.57.0)(react@18.3.1)(typescript@5.5.2)': + '@remix-run/eslint-config@2.10.2(eslint@8.57.0)(react@18.3.1)(typescript@5.5.3)': dependencies: '@babel/core': 7.24.7 '@babel/eslint-parser': 7.24.7(@babel/core@7.24.7)(eslint@8.57.0) '@babel/preset-react': 7.24.7(@babel/core@7.24.7) '@rushstack/eslint-patch': 1.10.3 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2) - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0) - eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0) + eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) eslint-plugin-jest-dom: 4.0.3(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-node: 11.1.0(eslint@8.57.0) eslint-plugin-react: 7.34.3(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) - eslint-plugin-testing-library: 5.11.1(eslint@8.57.0)(typescript@5.5.2) + eslint-plugin-testing-library: 5.11.1(eslint@8.57.0)(typescript@5.5.3) react: 18.3.1 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - eslint-import-resolver-webpack - jest - supports-color - '@remix-run/express@2.10.0(express@4.19.2)(typescript@5.5.2)': + '@remix-run/express@2.10.2(express@4.19.2)(typescript@5.5.3)': dependencies: - '@remix-run/node': 2.10.0(typescript@5.5.2) + '@remix-run/node': 2.10.2(typescript@5.5.3) express: 4.19.2 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 - '@remix-run/node@2.10.0(typescript@5.5.2)': + '@remix-run/node@2.10.2(typescript@5.5.3)': dependencies: - '@remix-run/server-runtime': 2.10.0(typescript@5.5.2) + '@remix-run/server-runtime': 2.10.2(typescript@5.5.3) '@remix-run/web-fetch': 4.4.2 '@web3-storage/multipart-parser': 1.0.0 cookie-signature: 1.2.1 @@ -4336,26 +4336,26 @@ snapshots: stream-slice: 0.1.2 undici: 6.19.2 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 - '@remix-run/react@2.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2)': + '@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': dependencies: - '@remix-run/router': 1.17.0 - '@remix-run/server-runtime': 2.10.0(typescript@5.5.2) + '@remix-run/router': 1.17.1 + '@remix-run/server-runtime': 2.10.2(typescript@5.5.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router: 6.24.0(react@18.3.1) - react-router-dom: 6.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 6.24.1(react@18.3.1) + react-router-dom: 6.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) turbo-stream: 2.2.0 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 - '@remix-run/router@1.17.0': {} + '@remix-run/router@1.17.1': {} - '@remix-run/serve@2.10.0(typescript@5.5.2)': + '@remix-run/serve@2.10.2(typescript@5.5.3)': dependencies: - '@remix-run/express': 2.10.0(express@4.19.2)(typescript@5.5.2) - '@remix-run/node': 2.10.0(typescript@5.5.2) + '@remix-run/express': 2.10.2(express@4.19.2)(typescript@5.5.3) + '@remix-run/node': 2.10.2(typescript@5.5.3) chokidar: 3.6.0 compression: 1.7.4 express: 4.19.2 @@ -4366,9 +4366,9 @@ snapshots: - supports-color - typescript - '@remix-run/server-runtime@2.10.0(typescript@5.5.2)': + '@remix-run/server-runtime@2.10.2(typescript@5.5.3)': dependencies: - '@remix-run/router': 1.17.0 + '@remix-run/router': 1.17.1 '@types/cookie': 0.6.0 '@web3-storage/multipart-parser': 1.0.0 cookie: 0.6.0 @@ -4376,7 +4376,7 @@ snapshots: source-map: 0.7.4 turbo-stream: 2.2.0 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 '@remix-run/web-blob@3.1.0': dependencies: @@ -4522,65 +4522,65 @@ snapshots: '@types/unist@2.0.10': {} - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': dependencies: '@eslint-community/regexpp': 4.10.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.3) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.5.2) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) debug: 4.3.5 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare-lite: 1.4.0 semver: 7.6.2 - tsutils: 3.21.0(typescript@5.5.2) + tsutils: 3.21.0(typescript@5.5.3) optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/eslint-plugin@7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': dependencies: '@eslint-community/regexpp': 4.10.1 - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.5.2) - '@typescript-eslint/scope-manager': 7.14.1 - '@typescript-eslint/type-utils': 7.14.1(eslint@8.57.0)(typescript@5.5.2) - '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.5.2) - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/scope-manager': 7.16.0 + '@typescript-eslint/type-utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 7.16.0 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.2) + ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) debug: 4.3.5 eslint: 8.57.0 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@typescript-eslint/scope-manager': 7.14.1 - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.5.2) - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/scope-manager': 7.16.0 + '@typescript-eslint/types': 7.16.0 + '@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 7.16.0 debug: 4.3.5 eslint: 8.57.0 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color @@ -4589,40 +4589,40 @@ snapshots: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - '@typescript-eslint/scope-manager@7.14.1': + '@typescript-eslint/scope-manager@7.16.0': dependencies: - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/types': 7.16.0 + '@typescript-eslint/visitor-keys': 7.16.0 - '@typescript-eslint/type-utils@5.62.0(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/type-utils@5.62.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.2) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) debug: 4.3.5 eslint: 8.57.0 - tsutils: 3.21.0(typescript@5.5.2) + tsutils: 3.21.0(typescript@5.5.3) optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.14.1(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/type-utils@7.16.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.5.2) - '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3) + '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) debug: 4.3.5 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.5.2) + ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@5.62.0': {} - '@typescript-eslint/types@7.14.1': {} + '@typescript-eslint/types@7.16.0': {} - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.2)': + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.3)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -4630,35 +4630,35 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 - tsutils: 3.21.0(typescript@5.5.2) + tsutils: 3.21.0(typescript@5.5.3) optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.14.1(typescript@5.5.2)': + '@typescript-eslint/typescript-estree@7.16.0(typescript@5.5.3)': dependencies: - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/types': 7.16.0 + '@typescript-eslint/visitor-keys': 7.16.0 debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.2 - ts-api-utils: 1.3.0(typescript@5.5.2) + ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.2 @@ -4666,12 +4666,12 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@7.14.1(eslint@8.57.0)(typescript@5.5.2)': + '@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@typescript-eslint/scope-manager': 7.14.1 - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.5.2) + '@typescript-eslint/scope-manager': 7.16.0 + '@typescript-eslint/types': 7.16.0 + '@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3) eslint: 8.57.0 transitivePeerDependencies: - supports-color @@ -4682,9 +4682,9 @@ snapshots: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@7.14.1': + '@typescript-eslint/visitor-keys@7.16.0': dependencies: - '@typescript-eslint/types': 7.14.1 + '@typescript-eslint/types': 7.16.0 eslint-visitor-keys: 3.4.3 '@ungap/structured-clone@1.2.0': {} @@ -4724,7 +4724,7 @@ snapshots: lodash: 4.17.21 mlly: 1.7.1 outdent: 0.8.0 - vite: 5.3.1(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.9) vite-node: 1.6.0(@types/node@20.14.9) transitivePeerDependencies: - '@types/node' @@ -5435,13 +5435,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.5 enhanced-resolve: 5.17.0 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.14.0 @@ -5452,22 +5452,22 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -5479,7 +5479,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -5489,7 +5489,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.14.0 is-glob: 4.0.3 @@ -5500,7 +5500,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -5513,12 +5513,12 @@ snapshots: eslint: 8.57.0 requireindex: 1.2.0 - eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2): + eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 optionalDependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) transitivePeerDependencies: - supports-color - typescript @@ -5579,9 +5579,9 @@ snapshots: semver: 6.3.1 string.prototype.matchall: 4.0.11 - eslint-plugin-testing-library@5.11.1(eslint@8.57.0)(typescript@5.5.2): + eslint-plugin-testing-library@5.11.1(eslint@8.57.0)(typescript@5.5.3): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 transitivePeerDependencies: - supports-color @@ -6021,9 +6021,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.4.38): + icss-utils@5.1.0(postcss@8.4.39): dependencies: - postcss: 8.4.38 + postcss: 8.4.39 ieee754@1.2.1: {} @@ -6193,7 +6193,7 @@ snapshots: isarray@2.0.5: {} - isbot@5.1.10: {} + isbot@5.1.13: {} isexe@2.0.0: {} @@ -6961,65 +6961,65 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-discard-duplicates@5.1.0(postcss@8.4.38): + postcss-discard-duplicates@5.1.0(postcss@8.4.39): dependencies: - postcss: 8.4.38 + postcss: 8.4.39 - postcss-import@15.1.0(postcss@8.4.38): + postcss-import@15.1.0(postcss@8.4.39): dependencies: - postcss: 8.4.38 + postcss: 8.4.39 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.38): + postcss-js@4.0.1(postcss@8.4.39): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.38 + postcss: 8.4.39 - postcss-load-config@4.0.2(postcss@8.4.38): + postcss-load-config@4.0.2(postcss@8.4.39): dependencies: lilconfig: 3.1.2 yaml: 2.4.5 optionalDependencies: - postcss: 8.4.38 + postcss: 8.4.39 - postcss-modules-extract-imports@3.1.0(postcss@8.4.38): + postcss-modules-extract-imports@3.1.0(postcss@8.4.39): dependencies: - postcss: 8.4.38 + postcss: 8.4.39 - postcss-modules-local-by-default@4.0.5(postcss@8.4.38): + postcss-modules-local-by-default@4.0.5(postcss@8.4.39): dependencies: - icss-utils: 5.1.0(postcss@8.4.38) - postcss: 8.4.38 + icss-utils: 5.1.0(postcss@8.4.39) + postcss: 8.4.39 postcss-selector-parser: 6.1.0 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.0(postcss@8.4.38): + postcss-modules-scope@3.2.0(postcss@8.4.39): dependencies: - postcss: 8.4.38 + postcss: 8.4.39 postcss-selector-parser: 6.1.0 - postcss-modules-values@4.0.0(postcss@8.4.38): + postcss-modules-values@4.0.0(postcss@8.4.39): dependencies: - icss-utils: 5.1.0(postcss@8.4.38) - postcss: 8.4.38 + icss-utils: 5.1.0(postcss@8.4.39) + postcss: 8.4.39 - postcss-modules@6.0.0(postcss@8.4.38): + postcss-modules@6.0.0(postcss@8.4.39): dependencies: generic-names: 4.0.0 - icss-utils: 5.1.0(postcss@8.4.38) + icss-utils: 5.1.0(postcss@8.4.39) lodash.camelcase: 4.3.0 - postcss: 8.4.38 - postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) - postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) - postcss-modules-scope: 3.2.0(postcss@8.4.38) - postcss-modules-values: 4.0.0(postcss@8.4.38) + postcss: 8.4.39 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.39) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.39) + postcss-modules-scope: 3.2.0(postcss@8.4.39) + postcss-modules-values: 4.0.0(postcss@8.4.39) string-hash: 1.1.3 - postcss-nested@6.0.1(postcss@8.4.38): + postcss-nested@6.0.1(postcss@8.4.39): dependencies: - postcss: 8.4.38 + postcss: 8.4.39 postcss-selector-parser: 6.1.0 postcss-selector-parser@6.1.0: @@ -7029,7 +7029,7 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.38: + postcss@8.4.39: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 @@ -7118,16 +7118,16 @@ snapshots: react-refresh@0.14.2: {} - react-router-dom@6.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router-dom@6.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@remix-run/router': 1.17.0 + '@remix-run/router': 1.17.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router: 6.24.0(react@18.3.1) + react-router: 6.24.1(react@18.3.1) - react-router@6.24.0(react@18.3.1): + react-router@6.24.1(react@18.3.1): dependencies: - '@remix-run/router': 1.17.0 + '@remix-run/router': 1.17.1 react: 18.3.1 react@18.3.1: @@ -7518,7 +7518,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@3.4.4: + tailwindcss@3.4.5: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -7534,11 +7534,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 - postcss: 8.4.38 - postcss-import: 15.1.0(postcss@8.4.38) - postcss-js: 4.0.1(postcss@8.4.38) - postcss-load-config: 4.0.2(postcss@8.4.38) - postcss-nested: 6.0.1(postcss@8.4.38) + postcss: 8.4.39 + postcss-import: 15.1.0(postcss@8.4.39) + postcss-js: 4.0.1(postcss@8.4.39) + postcss-load-config: 4.0.2(postcss@8.4.39) + postcss-nested: 6.0.1(postcss@8.4.39) postcss-selector-parser: 6.1.0 resolve: 1.22.8 sucrase: 3.35.0 @@ -7602,15 +7602,15 @@ snapshots: trough@2.2.0: {} - ts-api-utils@1.3.0(typescript@5.5.2): + ts-api-utils@1.3.0(typescript@5.5.3): dependencies: - typescript: 5.5.2 + typescript: 5.5.3 ts-interface-checker@0.1.13: {} - tsconfck@3.1.0(typescript@5.5.2): + tsconfck@3.1.0(typescript@5.5.3): optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.3 tsconfig-paths@3.15.0: dependencies: @@ -7627,10 +7627,10 @@ snapshots: tslib@1.14.1: {} - tsutils@3.21.0(typescript@5.5.2): + tsutils@3.21.0(typescript@5.5.3): dependencies: tslib: 1.14.1 - typescript: 5.5.2 + typescript: 5.5.3 turbo-stream@2.2.0: {} @@ -7677,7 +7677,7 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - typescript@5.5.2: {} + typescript@5.5.3: {} ufo@1.5.3: {} @@ -7804,7 +7804,7 @@ snapshots: debug: 4.3.5 pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.3.1(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.9) transitivePeerDependencies: - '@types/node' - less @@ -7815,21 +7815,21 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.2(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.9)): + vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)): dependencies: debug: 4.3.5 globrex: 0.1.2 - tsconfck: 3.1.0(typescript@5.5.2) + tsconfck: 3.1.0(typescript@5.5.3) optionalDependencies: - vite: 5.3.1(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.9) transitivePeerDependencies: - supports-color - typescript - vite@5.3.1(@types/node@20.14.9): + vite@5.3.3(@types/node@20.14.9): dependencies: esbuild: 0.21.5 - postcss: 8.4.38 + postcss: 8.4.39 rollup: 4.18.0 optionalDependencies: '@types/node': 20.14.9 diff --git a/migrating-df.md b/migrating-df.md index 1da8164b..e9158fbd 100644 --- a/migrating-df.md +++ b/migrating-df.md @@ -7,7 +7,7 @@ This document will guide you through the migration process. - πŸ›‘οΈ Enhanced Type Safety: Enjoy robust **type-safety during function composition**. The improved type-checking mechanisms prevent incompatible functions from being composed, reducing runtime errors and improving code reliability. - 🀌 Simplified Function Creation: **No need to define schemas**. Create composable functions easily and efficiently without the overhead of schema definitions. Work with plain functions in every combinator. -- πŸ•΅πŸ½ Runtime Validation: Use the [`withSchema`](./API.md#withschema) function for optional runtime validation of inputs and context. This provides flexibility to enforce data integrity when needed without mandating it for every function. Assuming you have a big chain of composables you can use [`applySchema`](./API.md#applyschema) to run your runtime validation only once **avoiding unnecessary processing**. +- πŸ•΅πŸ½ Runtime Validation: Use the [`applySchema`](./API.md#applyschema) function for optional runtime validation of inputs and context. This provides flexibility to enforce data integrity when needed without mandating it for every function. Assuming you have a big chain of composables you can use [`applySchema`](./API.md#applyschema) to run your runtime validation only once **avoiding unnecessary processing**. - πŸ”€ Flexible Compositions: The new combinators, such as [`context.pipe`](./API.md#contextpipe), [`context.sequence`](./API.md#contextsequence), and [`context.branch`](./API.md#contextbranch), offer powerful ways to manage **typed context** which are contextual information across your compositions. - πŸ› οΈ Incremental Migration: Seamlessly migrate your existing codebase incrementally. **Both `domain-functions` and `composable-functions` can coexist**, allowing you to transition module by module. - πŸ›Ÿ Enhanced Combinators: New and improved combinators like [`map`](./API.md#map), [`mapParameters`](./API.md#mapparameters), [`mapErrors`](./API.md#maperrors) and [`catchFailure`](./API.md#catchfailure) provide more control over error handling and transformation, making your **code more resilient**. @@ -39,7 +39,7 @@ The first thing you want to know is that the old `DomainFunction` is equivale A composable does not need a schema, but you can still use one for runtime assertion. What we used to call a Domain Function is now a Composable with [context](./context.md) and a schema. -The new constructor `withSchema` will work almost exactly as `makeDomainFunction`, except for the `Result` type of the resulting function. +The new constructor `applySchema` will work almost exactly as `makeDomainFunction`, except for the `Result` type of the resulting function. ### The new `Result` type - `Success | Failure` We removed the inputErrors and environmentErrors from the result and represent all of them using instances of `Error`. @@ -199,7 +199,7 @@ const fn = map(all(fn1, fn2), mergeObjects) You don't need to migrate the whole project at once. You can have both libraries in the project and migrate one module at a time. -Choose a module that has fewer dependants and swipe all constructors from `makeDomainFunction` to `withSchema`. +Choose a module that has fewer dependants and swipe all constructors from `makeDomainFunction` to `applySchema`. If your compositions are using domain functions from other modules, you'll see type errors. You can use the `toComposable` from `domain-functions@3.0` to avoid having to migrate those modules. @@ -252,8 +252,7 @@ if (result.errors.some(isInputError)) { #### Constructors | Domain Functions | Composable Functions | |---|---| -| `makeDomainFunction(z.string(), z.number())((input, env) => {})` | `withSchema(z.string, z.number())((input, ctx) => {})` | -| -- | `applySchema(z.string(), z.number())(composable((input, ctx) => {}))` | +| `makeDomainFunction(z.string(), z.number())((input, env) => {})` | `applySchema(z.string, z.number())((input, ctx) => {})` | | `makeSuccessResult(1)` | `success(1)` | | `makeErrorResult({ errors: [{ message: 'Something went wrong' }] })` | `failure([new Error('Something went wrong')])` | | `new InputError('required', 'user.name')` | `new InputError('required', ['user', 'name'])` | diff --git a/src/constructors.ts b/src/constructors.ts index ce96d293..8c6c83f6 100644 --- a/src/constructors.ts +++ b/src/constructors.ts @@ -3,11 +3,9 @@ import type { Internal } from './internal/types.ts' import type { ApplySchemaReturn, Composable, - ComposableWithSchema, Failure, ParserSchema, Success, - UnpackData, } from './types.ts' /** @@ -90,8 +88,8 @@ function fromSuccess( } /** - * Takes a composable and creates a composable withSchema that will assert the input and context types according to the given schemas. - * @param fn a composable function + * Takes a function and creates a ComposableWithSchema that will assert the input and context types according to the given schemas. + * @param fn a function * @param inputSchema the schema for the input * @param contextSchema the schema for the context * @returns a composable function that will assert the input and context types at runtime. @@ -103,21 +101,20 @@ function fromSuccess( * user: z.object({ name: z.string() }) * }), * ) - * const fn = safeFunction(composable(( + * const fn = safeFunction(( * { greeting }: { greeting: string }, * { user }: { user: { name: string } }, * ) => ({ * message: `${greeting} ${user.name}` - * }))) + * })) * ``` */ function applySchema( inputSchema?: ParserSchema, contextSchema?: ParserSchema, ) { - // TODO: Accept plain functions and equalize with withSchema - return ( - fn: Composable<(input: Input, context: Context) => R>, + return ( + fn: (input: Input, context: Context) => R, ): ApplySchemaReturn => { const callable = ((input?: unknown, context?: unknown) => { const ctxResult = (contextSchema ?? alwaysUnknownSchema).safeParse( @@ -134,7 +131,7 @@ function applySchema( ) return Promise.resolve(failure([...inputErrors, ...ctxErrors])) } - return fn(result.data as Input, ctxResult.data as Context) + return composable(fn)(result.data as Input, ctxResult.data as Context) }) as ApplySchemaReturn ;(callable as any).kind = 'composable' as const return callable @@ -142,35 +139,13 @@ function applySchema( } /** - * Creates a composable with unknown input and context that uses schemas to parse them into known types. - * This allows you to code the function with arbitrary types knowinng that they will be enforced in runtime. - * Very useful when piping data coming from any external source into your composables. - * After giving the input and context schemas, you can pass a handler function that takes type safe input and context. That function is gonna catch any errors and always return a Result. - * @param inputSchema the schema for the input - * @param contextSchema the schema for the context - * @returns a handler function that takes type safe input and context - * @example - * const safeFunction = withSchema( - * z.object({ greeting: z.string() }), - * z.object({ - * user: z.object({ name: z.string() }) - * }), - * ) - * const safeGreet = safeFunction(({ greeting }, { user }) => ({ - * message: `${greeting} ${user.name}` - * }) + * @deprecated use `applySchema` instead */ -function withSchema( - inputSchema?: ParserSchema, - contextSchema?: ParserSchema, -): unknown>( - fn: Fn, -) => ComposableWithSchema>> { - return (handler) => - applySchema( - inputSchema, - contextSchema, - )(composable(handler)) as ComposableWithSchema +function withSchema( + inputSchema?: ParserSchema, + contextSchema?: ParserSchema, +) { + return applySchema(inputSchema, contextSchema) } const alwaysUnknownSchema: ParserSchema = { diff --git a/src/context/combinators.ts b/src/context/combinators.ts index 8b964817..fd4f6ae0 100644 --- a/src/context/combinators.ts +++ b/src/context/combinators.ts @@ -20,16 +20,12 @@ function applyContextToList< * @example * * ```ts - * import { withSchema, context } from 'composable-functions' + * import { context } from 'composable-functions' * - * const a = withSchema(z.object({ aNumber: z.number() }))( - * ({ aNumber }) => ({ aString: String(aNumber) }), - * ) - * const b = withSchema(z.object({ aString: z.string() }))( - * ({ aString }) => ({ aBoolean: aString == '1' }), - * ) + * const a = (aNumber: number) => String(aNumber) + * const b = (aString: string) => aString === '1' * const d = context.pipe(a, b) - * // ^? ComposableWithSchema<{ aBoolean: boolean }> + * // ^? ComposableWithSchema * ``` */ function pipe( @@ -53,10 +49,10 @@ function pipe( * @example * * ```ts - * import { withSchema, context } from 'composable-functions' + * import { context } from 'composable-functions' * - * const a = withSchema(z.number())((aNumber) => String(aNumber)) - * const b = withSchema(z.string())((aString) => aString === '1') + * const a = (aNumber: number) => String(aNumber) + * const b = (aString: string) => aString === '1' * const aComposable = context.sequence(a, b) * // ^? ComposableWithSchema<[string, boolean]> * ``` diff --git a/src/context/tests/branch.test.ts b/src/context/tests/branch.test.ts index 599b2197..ddf168ba 100644 --- a/src/context/tests/branch.test.ts +++ b/src/context/tests/branch.test.ts @@ -1,12 +1,12 @@ import { assertEquals, assertIsError, describe, it, z } from './prelude.ts' import { all, + applySchema, composable, context, failure, InputError, success, - withSchema, } from '../../index.ts' import type { Composable, @@ -52,10 +52,10 @@ describe('branch', () => { }) it('should pipe a composable with a function that returns a composable with schema', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => id - 1) + const b = applySchema(z.object({ id: z.number() }))(({ id }) => id - 1) const c = context.branch(a, () => Promise.resolve(b)) type _R = Expect>> @@ -64,12 +64,12 @@ describe('branch', () => { }) it('should enable conditionally choosing the next composable with the output of first one', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, next: 'multiply', })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => String(id)) - const c = withSchema(z.object({ id: z.number() }))(({ id }) => id * 2) + const b = applySchema(z.object({ id: z.number() }))(({ id }) => String(id)) + const c = applySchema(z.object({ id: z.number() }))(({ id }) => id * 2) const d = context.branch(a, (output) => output.next === 'multiply' ? c : b) type _R = Expect>> @@ -77,11 +77,11 @@ describe('branch', () => { }) it('should not pipe if the predicate returns null', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, next: 'multiply', })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => String(id)) + const b = applySchema(z.object({ id: z.number() }))(({ id }) => String(id)) const d = context.branch(a, (output) => { type _Check = Expect>> return output.next === 'multiply' ? null : b @@ -143,7 +143,7 @@ describe('branch', () => { }) it('should gracefully fail if the first function fails', async () => { - const a = withSchema(z.number())((id) => ({ + const a = applySchema(z.number())((id) => ({ id: id + 2, })) const b = composable(({ id }: { id: number }) => id - 1) @@ -157,10 +157,10 @@ describe('branch', () => { }) it('should gracefully fail if the second function fails', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: String(id), })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => id - 1) + const b = applySchema(z.object({ id: z.number() }))(({ id }) => id - 1) const c = context.branch(a, () => b) type _R = Expect>> @@ -187,7 +187,7 @@ describe('branch', () => { }) it('should not break composition with other combinators', async () => { - const a = withSchema( + const a = applySchema( z.object({ id: z.number() }), )(({ id }) => ({ id: id + 2, diff --git a/src/context/tests/pipe.test.ts b/src/context/tests/pipe.test.ts index 6cb812d3..76a37b6e 100644 --- a/src/context/tests/pipe.test.ts +++ b/src/context/tests/pipe.test.ts @@ -1,22 +1,22 @@ import { assertEquals, describe, it, z } from './prelude.ts' import { + applySchema, composable, context, ContextError, failure, InputError, success, - withSchema, } from '../../index.ts' import type { Composable, ComposableWithSchema } from '../../index.ts' import type { Internal } from '../../internal/types.ts' describe('pipe', () => { it('should compose functions from left-to-right', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => id - 1) + const b = applySchema(z.object({ id: z.number() }))(({ id }) => id - 1) const c = context.pipe(a, b) type _R = Expect>> @@ -25,13 +25,13 @@ describe('pipe', () => { }) it('should use the same context in all composed functions', async () => { - const a = withSchema( + const a = applySchema( z.undefined(), z.object({ ctx: z.number() }), )((_input, { ctx }) => ({ inp: ctx + 2, })) - const b = withSchema( + const b = applySchema( z.object({ inp: z.number() }), z.object({ ctx: z.number() }), )(({ inp }, { ctx }) => inp + ctx) @@ -44,13 +44,13 @@ describe('pipe', () => { it('should fail on the first context parser failure', async () => { const ctxParser = z.object({ ctx: z.number() }) - const a = withSchema( + const a = applySchema( z.undefined(), ctxParser, )((_input, { ctx }) => ({ inp: ctx + 2, })) - const b = withSchema( + const b = applySchema( z.object({ inp: z.number() }), ctxParser, )(({ inp }, { ctx }) => inp + ctx) @@ -67,13 +67,13 @@ describe('pipe', () => { it('should fail on the first input parser failure', async () => { const firstInputParser = z.undefined() - const a = withSchema( + const a = applySchema( firstInputParser, z.object({ ctx: z.number() }), )((_input, { ctx }) => ({ inp: ctx + 2, })) - const b = withSchema( + const b = applySchema( z.object({ inp: z.number() }), z.object({ ctx: z.number() }), )(({ inp }, { ctx }) => inp + ctx) @@ -105,13 +105,13 @@ describe('pipe', () => { }) it('should compose more than 2 functions', async () => { - const a = withSchema(z.object({ aNumber: z.number() }))(({ aNumber }) => ({ + const a = applySchema(z.object({ aNumber: z.number() }))(({ aNumber }) => ({ aString: String(aNumber), })) - const b = withSchema(z.object({ aString: z.string() }))(({ aString }) => ({ + const b = applySchema(z.object({ aString: z.string() }))(({ aString }) => ({ aBoolean: aString == '1', })) - const c = withSchema(z.object({ aBoolean: z.boolean() }))( + const c = applySchema(z.object({ aBoolean: z.boolean() }))( ({ aBoolean }) => !aBoolean, ) diff --git a/src/context/tests/sequence.test.ts b/src/context/tests/sequence.test.ts index 0726b427..a10ea6c7 100644 --- a/src/context/tests/sequence.test.ts +++ b/src/context/tests/sequence.test.ts @@ -1,21 +1,21 @@ import { assertEquals, describe, it, z } from './prelude.ts' import { + applySchema, composable, context, ContextError, failure, InputError, success, - withSchema, } from '../../index.ts' import type { Composable } from '../../index.ts' describe('sequence', () => { it('should compose functions from left-to-right saving the results sequentially', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const b = applySchema(z.object({ id: z.number() }))(({ id }) => ({ result: id - 1, })) @@ -39,13 +39,13 @@ describe('sequence', () => { }) it('should use the same context in all composed functions', async () => { - const a = withSchema( + const a = applySchema( z.undefined(), z.object({ ctx: z.number() }), )((_input, { ctx }) => ({ inp: ctx + 2, })) - const b = withSchema( + const b = applySchema( z.object({ inp: z.number() }), z.object({ ctx: z.number() }), )(({ inp }, { ctx }) => ({ result: inp + ctx })) @@ -74,13 +74,13 @@ describe('sequence', () => { it('should fail on the first context parser failure', async () => { const ctxParser = z.object({ ctx: z.number() }) - const a = withSchema( + const a = applySchema( z.undefined(), ctxParser, )((_input, { ctx }) => ({ inp: ctx + 2, })) - const b = withSchema( + const b = applySchema( z.object({ inp: z.number() }), ctxParser, )(({ inp }, { ctx }) => inp + ctx) @@ -104,13 +104,13 @@ describe('sequence', () => { it('should fail on the first input parser failure', async () => { const firstInputParser = z.undefined() - const a = withSchema( + const a = applySchema( firstInputParser, z.object({ ctx: z.number() }), )((_input, { ctx }) => ({ inp: ctx + 2, })) - const b = withSchema( + const b = applySchema( z.object({ inp: z.number() }), z.object({ ctx: z.number() }), )(({ inp }, { ctx }) => inp + ctx) @@ -132,13 +132,13 @@ describe('sequence', () => { }) it('should fail on the second input parser failure', async () => { - const a = withSchema( + const a = applySchema( z.undefined(), z.object({ ctx: z.number() }), )(() => ({ inp: 'some invalid input', })) - const b = withSchema( + const b = applySchema( z.object({ inp: z.number() }), z.object({ ctx: z.number() }), )(({ inp }, { ctx }) => inp + ctx) @@ -160,13 +160,13 @@ describe('sequence', () => { }) it('should compose more than 2 functions', async () => { - const a = withSchema(z.object({ aNumber: z.number() }))(({ aNumber }) => ({ + const a = applySchema(z.object({ aNumber: z.number() }))(({ aNumber }) => ({ aString: String(aNumber), })) - const b = withSchema(z.object({ aString: z.string() }))(({ aString }) => ({ + const b = applySchema(z.object({ aString: z.string() }))(({ aString }) => ({ aBoolean: aString == '1', })) - const c = withSchema(z.object({ aBoolean: z.boolean() }))( + const c = applySchema(z.object({ aBoolean: z.boolean() }))( ({ aBoolean }) => ({ anotherBoolean: !aBoolean, }), diff --git a/src/errors.ts b/src/errors.ts index 63756f5e..2b00e4e7 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -2,9 +2,9 @@ * A custom error class for input errors. * * @example - * const aComposable = withSchema()(() => { + * const fn = () => { * throw new InputError('Invalid input', 'user.name') - * }) + * } */ class InputError extends Error { /** @@ -24,9 +24,9 @@ class InputError extends Error { * A custom error class for context errors. * * @example - * const aComposable = withSchema()(() => { + * const fn = () => { * throw new EnvironmentError('Invalid environment', 'user.name') - * }) + * } */ class EnvironmentError extends Error { /** @@ -45,9 +45,9 @@ class EnvironmentError extends Error { * A custom error class for context errors. * * @example - * const aComposable = withSchema()(() => { + * const fn = () => { * throw new ContextError('Invalid context', 'user.name') - * }) + * } */ class ContextError extends Error { /** diff --git a/src/tests/all.test.ts b/src/tests/all.test.ts index e053610a..fdca752a 100644 --- a/src/tests/all.test.ts +++ b/src/tests/all.test.ts @@ -1,16 +1,16 @@ import { all, + applySchema, composable, failure, InputError, success, - withSchema, } from '../index.ts' import type { Composable, ComposableWithSchema } from '../types.ts' import { assertEquals, assertIsError, describe, it, z } from './prelude.ts' const voidFn = composable(() => {}) -const toString = withSchema(z.unknown(), z.any())(String) +const toString = applySchema(z.unknown(), z.any())(String) const add = composable((a: number, b: number) => a + b) const optionalAdd = composable((a: number, b?: number) => a + (b ?? 1)) @@ -51,8 +51,8 @@ describe('all', () => { }) it('should return error when one of the schema functions has input errors', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => id) - const b = withSchema(z.object({ id: z.string() }))(({ id }) => id) + const a = applySchema(z.object({ id: z.number() }))(({ id }) => id) + const b = applySchema(z.object({ id: z.string() }))(({ id }) => id) const c = all(a, b) type _R = Expect>> @@ -78,8 +78,8 @@ describe('all', () => { }) it('should combine the InputError messages of both schema functions', async () => { - const a = withSchema(z.object({ id: z.string() }))(({ id }) => id) - const b = withSchema(z.object({ id: z.string() }))(({ id }) => id) + const a = applySchema(z.object({ id: z.string() }))(({ id }) => id) + const b = applySchema(z.object({ id: z.string() }))(({ id }) => id) const c = all(a, b) type _R = Expect>> diff --git a/src/tests/branch.test.ts b/src/tests/branch.test.ts index 0afd4f70..dd67488e 100644 --- a/src/tests/branch.test.ts +++ b/src/tests/branch.test.ts @@ -1,10 +1,10 @@ -import { branch } from '../combinators.ts' import { + applySchema, + branch, composable, failure, InputError, success, - withSchema, } from '../index.ts' import type { Composable, @@ -73,7 +73,7 @@ describe('branch', () => { }) it('should not pipe if the predicate returns null', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, next: 'multiply', })) @@ -98,7 +98,7 @@ describe('branch', () => { }) it('should gracefully fail if the first function fails', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, })) const b = composable(({ id }: { id: number }) => id - 1) @@ -115,7 +115,7 @@ describe('branch', () => { const a = composable(({ id }: { id: number }) => ({ id: String(id), })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => id - 1) + const b = applySchema(z.object({ id: z.number() }))(({ id }) => id - 1) const c = branch(a, () => b) type _R = Expect number>>> @@ -126,10 +126,10 @@ describe('branch', () => { }) it('should gracefully fail if the condition function fails', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + const a = applySchema(z.object({ id: z.number() }))(({ id }) => ({ id: id + 2, })) - const b = withSchema(z.object({ id: z.number() }))(({ id }) => id - 1) + const b = applySchema(z.object({ id: z.number() }))(({ id }) => id - 1) const c = branch(a, (_) => { throw new Error('condition function failed') // deno-lint-ignore no-unreachable diff --git a/src/tests/catch-failure.test.ts b/src/tests/catch-failure.test.ts index f0f85501..a79b1904 100644 --- a/src/tests/catch-failure.test.ts +++ b/src/tests/catch-failure.test.ts @@ -1,8 +1,8 @@ import { assertEquals, describe, it, z } from './prelude.ts' import type { Composable, Result } from '../index.ts' -import { catchFailure, composable, success, withSchema } from '../index.ts' +import { applySchema, catchFailure, composable, success } from '../index.ts' -const schemaFaultyAdd = withSchema( +const schemaFaultyAdd = applySchema( z.number(), z.number(), )((a: number, b: number) => { diff --git a/src/tests/collect.test.ts b/src/tests/collect.test.ts index dfa57f7e..f56416e9 100644 --- a/src/tests/collect.test.ts +++ b/src/tests/collect.test.ts @@ -1,16 +1,16 @@ import { assertEquals, describe, it, z } from './prelude.ts' import type { Composable, Result } from '../index.ts' import { + applySchema, collect, composable, failure, InputError, success, - withSchema, } from '../index.ts' const voidFn = composable(() => {}) -const toString = withSchema(z.unknown(), z.any())((a) => String(a)) +const toString = applySchema(z.unknown(), z.any())((a) => String(a)) const append = composable((a: string, b: string) => `${a}${b}`) const add = composable((a: number, b: number) => a + b) const faultyAdd = composable((a: number, b: number) => { @@ -130,8 +130,8 @@ describe('collect', () => { }) it('should return error when one of the schema functions has input errors', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => id) - const b = withSchema(z.object({ id: z.string() }))(({ id }) => id) + const a = applySchema(z.object({ id: z.number() }))(({ id }) => id) + const b = applySchema(z.object({ id: z.string() }))(({ id }) => id) const c = collect({ a, b }) type _R = Expect< @@ -150,8 +150,8 @@ describe('collect', () => { }) it('should combine the inputError messages of both schema functions', async () => { - const a = withSchema(z.object({ id: z.string() }))(({ id }) => id) - const b = withSchema(z.object({ id: z.string() }))(({ id }) => id) + const a = applySchema(z.object({ id: z.string() }))(({ id }) => id) + const b = applySchema(z.object({ id: z.string() }))(({ id }) => id) const c = collect({ a, b }) type _R = Expect< diff --git a/src/tests/constructors.test.ts b/src/tests/constructors.test.ts index ae815d4f..15e3fbb8 100644 --- a/src/tests/constructors.test.ts +++ b/src/tests/constructors.test.ts @@ -113,18 +113,18 @@ describe('composable', () => { describe('fromSuccess', () => { it('returns the result.data when the schema function suceeds', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => id + 1) + const a = composable((n: number) => n + 1) const c = fromSuccess(a) type _R = Expect< - Equal Promise> + Equal Promise> > - assertEquals(await c({ id: 1 }), 2) + assertEquals(await c(1), 2) }) it('throws an exception when the schema function fails', () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => id + 1) + const a = applySchema(z.number())((n) => n + 1) const c = fromSuccess(a) type _R = Expect< @@ -136,15 +136,6 @@ describe('fromSuccess', () => { }, ErrorList) }) - it('works with composable functions', async () => { - const a = composable(() => 1) - - const c = fromSuccess(a) - type _R = Expect Promise<1>>> - - assertEquals(await c(), 1) - }) - it('allows to throw any arbitrary value', async () => { const a = composable(() => { throw new Error('Some error') @@ -267,37 +258,86 @@ describe('withSchema', () => { assertEquals(await handler({ id: '1' }, { uid: '2' }), success([1, 2])) }) +}) - it('applies async validations', async () => { - const parser = z.object({ - id: z - .preprocess(Number, z.number()) - .refine((value) => value !== 1, { message: 'ID already taken' }), - }) +describe('applySchema', () => { + it('uses zod parsers to parse the input and context turning it into a schema function', async () => { + const inputSchema = z.object({ id: z.preprocess(Number, z.number()) }) + const ctxSchema = z.object({ uid: z.preprocess(Number, z.number()) }) - const ctxParser = z.object({ - uid: z - .preprocess(Number, z.number()) - .refine((value) => value !== 2, { message: 'UID already taken' }), - }) + const handler = applySchema( + inputSchema, + ctxSchema, + )( + ({ id }: { id: number }, { uid }: { uid: number }) => [id, uid] as const, + ) + type _R = Expect< + Equal> + > - const handler = withSchema( - parser, - ctxParser, - )(({ id }, { uid }) => [id, uid]) - type _R = Expect>> + assertEquals( + await handler({ id: 1 }, { uid: 2 }), + success<[number, number]>([1, 2]), + ) + }) + + it('allow composition with unknown context', async () => { + const inputSchema = z.string() + + const handler = applySchema( + inputSchema, + z.unknown(), + )((x) => x) + type _R = Expect>> + const result = await handler('a') + + assertEquals(result, success('a')) + }) + + it('accepts a plain function', async () => { + const inputSchema = z.object({ id: z.preprocess(Number, z.number()) }) + const ctxSchema = z.object({ uid: z.preprocess(Number, z.number()) }) + + const handler = applySchema( + inputSchema, + ctxSchema, + )( + ({ id }: { id: number }, { uid }: { uid: number }) => [id, uid] as const, + ) + type _R = Expect< + Equal> + > assertEquals( - await handler({ id: '1' }, { uid: '2' }), - failure([ - new InputError('ID already taken', ['id']), - new ContextError('UID already taken', ['uid']), - ]), + await handler({ id: 1 }, { uid: 2 }), + success<[number, number]>([1, 2]), + ) + }) + + it('fails to compose when there is an object schema with incompatible properties', async () => { + const inputSchema = z.object({ x: z.string() }) + + const handler = applySchema(inputSchema)( + composable(({ x }: { x: 'a' }) => x), ) + type _R = Expect< + Equal> + > + // @ts-expect-error: { x: 'a' } is not assignable to { x: string } + const _result = await handler({ x: 'a' }) + }) + + it('fails to compose when schema result is wider than composable input', async () => { + const inputSchema = z.string() + + const handler = applySchema(inputSchema)(composable((x: 'a') => x)) + type _R = Expect>> + // @ts-expect-error: 'a' is not assignable to 'string' + const _result = await handler('a') }) it('accepts literals as input of schema functions', async () => { - const handler = withSchema(z.number(), z.string())((n) => n + 1) + const handler = applySchema(z.number(), z.string())((n) => n + 1) type _R = Expect>> const result = await handler(1, 'not going to be used') @@ -305,7 +345,7 @@ describe('withSchema', () => { }) it('accepts sync functions', async () => { - const handler = withSchema(z.number())((n) => n + 1) + const handler = applySchema(z.number())((n) => n + 1) type _R = Expect>> const result = await handler(1) @@ -316,7 +356,7 @@ describe('withSchema', () => { const parser = z.object({ id: z.preprocess(Number, z.number()) }) const ctxParser = z.object({ uid: z.preprocess(Number, z.number()) }) - const handler = withSchema( + const handler = applySchema( parser, ctxParser, )(({ id }, { uid }) => [id, uid]) @@ -329,7 +369,7 @@ describe('withSchema', () => { }) it('returns error when the schema function throws an Error', async () => { - const handler = withSchema(z.object({ id: z.number() }))(() => { + const handler = applySchema(z.object({ id: z.number() }))(() => { throw new Error('Error') }) type _R = Expect>> @@ -341,7 +381,7 @@ describe('withSchema', () => { }) it('preserves entire original exception when the schema function throws an Error', async () => { - const handler = withSchema(z.object({ id: z.number() }))(() => { + const handler = applySchema(z.object({ id: z.number() }))(() => { throw new Error('Some message', { cause: { someUnknownFields: true } }) }) type _R = Expect>> @@ -354,7 +394,7 @@ describe('withSchema', () => { }) it('returns error when the schema function throws a string', async () => { - const handler = withSchema(z.object({ id: z.number() }))(() => { + const handler = applySchema(z.object({ id: z.number() }))(() => { throw 'Error' }) type _R = Expect>> @@ -363,7 +403,7 @@ describe('withSchema', () => { }) it('returns error when the schema function throws an object with message', async () => { - const handler = withSchema(z.object({ id: z.number() }))(() => { + const handler = applySchema(z.object({ id: z.number() }))(() => { throw { message: 'Error' } }) type _R = Expect>> @@ -376,7 +416,7 @@ describe('withSchema', () => { }) it('returns inputErrors when the schema function throws an InputError', async () => { - const handler = withSchema(z.object({ id: z.number() }))(() => { + const handler = applySchema(z.object({ id: z.number() }))(() => { throw new InputError('Custom input error', ['contact', 'id']) }) type _R = Expect>> @@ -388,7 +428,7 @@ describe('withSchema', () => { }) it('returns contextErrors when the schema function throws an ContextError', async () => { - const handler = withSchema(z.object({ id: z.number() }))(() => { + const handler = applySchema(z.object({ id: z.number() }))(() => { throw new ContextError('Custom ctx error', ['currentUser', 'role']) }) type _R = Expect>> @@ -400,7 +440,7 @@ describe('withSchema', () => { }) it('returns an error result when the schema function throws an ErrorList', async () => { - const handler = withSchema(z.object({ id: z.number() }))(() => { + const handler = applySchema(z.object({ id: z.number() }))(() => { throw new ErrorList([ new InputError('Custom input error', ['contact', 'id']), new ContextError('Custom ctx error', ['currentUser', 'role']), @@ -417,97 +457,3 @@ describe('withSchema', () => { ) }) }) - -describe('applySchema', () => { - it('uses zod parsers to parse the input and context turning it into a schema function', async () => { - const inputSchema = z.object({ id: z.preprocess(Number, z.number()) }) - const ctxSchema = z.object({ uid: z.preprocess(Number, z.number()) }) - - const handler = applySchema( - inputSchema, - ctxSchema, - )( - composable( - ({ id }: { id: number }, { uid }: { uid: number }) => - [id, uid] as const, - ), - ) - type _R = Expect< - Equal> - > - - assertEquals( - await handler({ id: 1 }, { uid: 2 }), - success<[number, number]>([1, 2]), - ) - }) - - it('allow composition with unknown context', async () => { - const inputSchema = z.string() - - const handler = applySchema( - inputSchema, - z.unknown(), - )(composable((x: string) => x)) - type _R = Expect>> - const result = await handler('a') - - assertEquals(result, success('a')) - }) - - // TODO: Accept plain functions and equalize with withSchema - // it('accepts a plain function', async () => { - // const inputSchema = z.object({ id: z.preprocess(Number, z.number()) }) - // const ctxSchema = z.object({ uid: z.preprocess(Number, z.number()) }) - - // const handler = applySchema( - // inputSchema, - // ctxSchema, - // )( - // composable( - // ({ id }: { id: number }, { uid }: { uid: number }) => - // [id, uid] as const, - // ), - // ) - // type _R = Expect< - // Equal> - // > - - // assertEquals( - // await handler({ id: 1 }, { uid: 2 }), - // success<[number, number]>([1, 2]), - // ) - // }); - - it('fails to compose when there is an object schema with incompatible properties', async () => { - const inputSchema = z.object({ x: z.string() }) - - const handler = applySchema(inputSchema)( - composable(({ x }: { x: 'a' }) => x), - ) - type _R = Expect< - Equal> - > - // @ts-expect-error: { x: 'a' } is not assignable to { x: string } - const _result = await handler({ x: 'a' }) - }) - - it('fails to compose when schema result is wider than composable input', async () => { - const inputSchema = z.string() - - const handler = applySchema(inputSchema)(composable((x: 'a') => x)) - type _R = Expect>> - // @ts-expect-error: 'a' is not assignable to 'string' - const _result = await handler('a') - }) - - it('can be used as a layer on top of withSchema fn', async () => { - const fn = withSchema(z.object({ id: z.number() }))(({ id }) => id + 1) - const prepareSchema = z.string().transform((v) => ({ id: Number(v) })) - const handler = applySchema(prepareSchema)(fn) - type _R = Expect>> - - const result = await handler('1') - assertEquals(result, success(2)) - }) -}) diff --git a/src/tests/sequence.test.ts b/src/tests/sequence.test.ts index ae82cf79..ef48590f 100644 --- a/src/tests/sequence.test.ts +++ b/src/tests/sequence.test.ts @@ -1,10 +1,9 @@ import { assertEquals, describe, it, z } from './prelude.ts' import type { Composable, Result } from '../index.ts' -import { composable, sequence, success } from '../index.ts' -import { withSchema } from '../index.ts' +import { applySchema, composable, sequence, success } from '../index.ts' const toString = composable((a: unknown) => `${a}`) -const schemaAdd = withSchema(z.number(), z.number())((a, b) => a + b) +const schemaAdd = applySchema(z.number(), z.number())((a, b) => a + b) const faultyAdd = composable((a: number, b: number) => { if (a === 1) throw new Error('a is 1') return a + b diff --git a/src/tests/trace.test.ts b/src/tests/trace.test.ts index ec328a89..e7c1ffad 100644 --- a/src/tests/trace.test.ts +++ b/src/tests/trace.test.ts @@ -1,16 +1,16 @@ import { assertEquals, assertIsError, describe, it, z } from './prelude.ts' import { + applySchema, composable, fromSuccess, success, trace, - withSchema, } from '../index.ts' import type { Composable, ComposableWithSchema, Result } from '../index.ts' describe('trace', () => { it('converts trace exceptions to failures', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => id + 1) + const a = applySchema(z.object({ id: z.number() }))(({ id }) => id + 1) const c = trace(() => { throw new Error('Problem in tracing') @@ -54,7 +54,7 @@ describe('trace', () => { }) it('intercepts inputs and outputs of a given composable', async () => { - const a = withSchema(z.object({ id: z.number() }))(({ id }) => id + 1) + const a = applySchema(z.object({ id: z.number() }))(({ id }) => id + 1) let contextFromFunctionA: unknown[] = [] diff --git a/src/tests/types.test.ts b/src/tests/types.test.ts index 5960c92c..19f56e1f 100644 --- a/src/tests/types.test.ts +++ b/src/tests/types.test.ts @@ -1,5 +1,5 @@ // deno-lint-ignore-file no-namespace ban-ts-comment -import { withSchema } from '../index.ts' +import { applySchema } from '../index.ts' import { assertEquals, describe, it } from './prelude.ts' import type * as Subject from '../types.ts' import type { Internal } from '../internal/types.ts' @@ -378,7 +378,7 @@ namespace UnpackData { Equal string>>, string> > - const result = withSchema()(() => ({ name: 'foo' } as const)) + const result = applySchema()(() => ({ name: 'foo' } as const)) type test = Expect< Equal, { readonly name: 'foo' }> diff --git a/src/types.ts b/src/types.ts index a810f696..2d010e6e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -234,10 +234,12 @@ type BranchReturn< type ApplySchemaReturn< ParsedInput, ParsedContext, - Fn extends Composable, + Fn extends Internal.AnyFn, > = ParsedInput extends Parameters[0] ? ParsedContext extends Parameters[1] - ? ComposableWithSchema> + ? Awaited> extends never ? ComposableWithSchema + : Awaited> extends Result ? ComposableWithSchema + : ComposableWithSchema>> : FailToCompose[1]> : FailToCompose[0]> diff --git a/with-schema.md b/with-schema.md index 29db3220..d6a7a116 100644 --- a/with-schema.md +++ b/with-schema.md @@ -4,21 +4,21 @@ When dealing with external data such as API requests or form submissions, it's c ## Using Schemas -To ensure type safety at runtime, use the `applySchema` or `withSchema` functions to validate external inputs against defined schemas. +To ensure type safety at runtime, use the `applySchema` function to validate external inputs against defined schemas. -**Note about schema validation libraries:** Composable functions use Zod for schema validation by default. If you prefer to use another library, you can create your own `withSchema` function based on the library of your choice. For an example, see the [Arktype example](./examples/arktype/README.md). +**Note about schema validation libraries:** Composable functions use Zod for schema validation by default. If you prefer to use another library, you can create your own `applySchema` function based on the library of your choice. For an example, see the [Arktype example](./examples/arktype/README.md). ### applySchema -The `applySchema` function takes a schemas for the input and context, and a composable, applying these schemas to ensure data integrity. +The `applySchema` function takes a schemas for the input and context, and a function, applying these schemas to ensure data integrity. ```typescript import { composable, applySchema } from 'composable-functions' import { z } from 'zod' -const fn = composable(({ greeting }: { greeting: string }, { user }: { user: { name: string } }) => ({ +const fn = ({ greeting }: { greeting: string }, { user }: { user: { name: string } }) => ({ message: `${greeting} ${user.name}` -})) +}) const safeFunction = applySchema( z.object({ greeting: z.string() }), @@ -27,27 +27,7 @@ const safeFunction = applySchema( const fnWithSchema = safeFunction(fn) type Test = typeof fnWithSchema -// ^? Composable<(input?: unknown, ctx?: unknown) => { message: string }> -``` - -### withSchema - -The withSchema function is similar to applySchema but provides a more concise syntax for creating runtime-safe composables. - -```ts -import { composable, withSchema } from 'composable-functions' -import { z } from 'zod' - -const runtimeSafeAdd = withSchema(z.number(), z.number())((a, b) => a + b) -// ^? Composable<(input?: unknown, ctx?: unknown) => number> -const result = await runtimeSafeAdd(1, 2) -/* -result = { - success: true, - data: 3, - errors: [] -} -*/ +// ^? ComposableWithSchema<{ message: string }> ``` ## Input Resolvers @@ -115,4 +95,4 @@ const values = inputFromSearch(qs) ## FAQ - I want to use composable-functions in a project that does not have Zod, how can I use other schema validation libraries? - - We [created an example](./examples/arktype/src/) in the example folder showing how to construct your own `withSchema` functions based on other parsers. + - We [created an example](./examples/arktype/src/) in the example folder showing how to construct your own `applySchema` functions based on other parsers.