New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

composable-functions

Package Overview
Dependencies
Maintainers
0
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

composable-functions - npm Package Compare versions

Comparing version 4.1.0 to 4.2.0

101

API.md

@@ -181,3 +181,3 @@ # API Reference

These combinators are useful for composing composables. They all return another `Composable`, thus allowing further application in more compositions.
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.

@@ -191,5 +191,5 @@ ## all

```ts
const a = composable(({ id }: { id: number }) => String(id))
const b = composable(({ id }: { id: number }) => id + 1)
const c = composable(({ id }: { id: number }) => Boolean(id))
const a = ({ id }: { id: number }) => String(id)
const b = ({ id }: { id: number }) => id + 1
const c = ({ id }: { id: number }) => Boolean(id)

@@ -216,5 +216,5 @@ const result = await all(a, b, c)({ id: 1 })

})
const b = composable(() => {
const b = () => {
throw new Error('Error')
})
}

@@ -239,11 +239,5 @@ const result = await all(a, b)({ id: '1' })

```ts
const getIdOrEmail = composable((data: { id?: number, email?: string }) => {
return data.id ?? data.email
})
const findUserById = composable((id: number) => {
return db.users.find({ id })
})
const findUserByEmail = composable((email: string) => {
return db.users.find({ email })
})
const getIdOrEmail = (data: { id?: number, email?: string }) => data.id ?? data.email
const findUserById = (id: number) => db.users.find({ id })
const findUserByEmail = (email: string) => db.users.find({ email })
const findUserByIdOrEmail = branch(

@@ -266,4 +260,4 @@ getIdOrEmail,

```ts
const a = composable(() => 'a')
const b = composable(() => 'b')
const a = () => 'a'
const b = () => 'b'
const fn = branch(a, (data) => data === 'a' ? null : b)

@@ -295,3 +289,3 @@ // ^? Composable<() => 'a' | 'b'>

const getUser = composable((id: string) => fetchUser(id))
const getUser = (id: string) => fetchUser(id)
// ^? Composable<(id: string) => User>

@@ -312,5 +306,5 @@ const getOptionalUser = catchFailure(getUser, (errors, id) => {

```ts
const a = composable(() => '1')
const b = composable(() => 2)
const c = composable(() => true)
const a = () => '1'
const b = () => 2
const c = () => true

@@ -340,3 +334,3 @@ const results = await collect({ a, b, c })({})

```ts
const add = composable((a: number, b: number) => a + b)
const add = (a: number, b: number) => a + b
const addAndMultiplyBy2 = map(add, sum => sum * 2)

@@ -348,8 +342,6 @@ ```

```ts
const fetchAsText = composable(
({ userId }: { userId: number }) =>
fetch(`https://reqres.in/api/users/${String(userId)}`).then((r) =>
r.json(),
),
)
const fetchAsText = ({ userId }: { userId: number }) => {
return fetch(`https://reqres.in/api/users/${String(userId)}`)
.then((r) => r.json())
}
const fullName = withSchema(

@@ -381,3 +373,3 @@ z.object({ first_name: z.string(), last_name: z.string() }),

```ts
const add = composable((a: number, b: number) => a + b)
const add = (a: number, b: number) => a + b
const aggregateInputAndOutput = map(add, (result, a, b) => ({ result, a, b }))

@@ -396,3 +388,3 @@ // ^? Composable<(a: number, b: number) => { result: number, a: number, b: number }>

```ts
const increment = composable((n: number) => {
const increment = (n: number) => {
if (Number.isNaN(n)) {

@@ -402,3 +394,3 @@ throw new Error('Invalid input')

return n + 1
})
}
const summarizeErrors = (errors: Error[]) =>

@@ -425,8 +417,7 @@ [new Error('Number of errors: ' + errors.length)]

```ts
const getUser = composable(({ id }: { id: number }) => db.users.find({ id }))
// ^? Composable<(input: { id: number }) => User>
const getUser = ({ id }: { id: number }) => db.users.find({ id })
const getCurrentUser = mapParameters(
getUser,
(_input, user: { id: number }) => [{ id: user.id }]
(_input: unknown, user: { id: number }) => [{ id: user.id }]
)

@@ -443,5 +434,5 @@ // ^? Composable<(input: unknown, ctx: { id: number }) => User>

```ts
const a = composable((aNumber: number) => String(aNumber))
const b = composable((aString: string) => aString == '1')
const c = composable((aBoolean: boolean) => !aBoolean)
const a = (aNumber: number) => String(aNumber)
const b = (aString: string) => aString == '1'
const c = (aBoolean: boolean) => !aBoolean

@@ -472,5 +463,5 @@ const d = pipe(a, b, c)

```ts
const a = composable((aNumber: number) => String(aNumber))
const b = composable((aString: string) => aString == '1')
const c = composable((aBoolean: boolean) => !aBoolean)
const a = (aNumber: number) => String(aNumber)
const b = (aString: string) => aString == '1'
const c = (aBoolean: boolean) => !aBoolean

@@ -496,4 +487,4 @@ const d = sequence(a, b, c)

```ts
const a = composable((aNumber: number) => String(aNumber))
const b = composable((aString: string) => aString === '1')
const a = (aNumber: number) => String(aNumber)
const b = (aString: string) => aString === '1'

@@ -539,3 +530,3 @@ const c = map(sequence(a, b), ([a, b]) => ({ aString: a, aBoolean: b }))

These functions are better suited for use with `withSchema` rather than `composable` 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 `withSchema` (or `applySchema`) since they deal with external data and `withSchema` will ensure type-safety in runtime.

@@ -812,6 +803,6 @@ For more details on how to structure your data, refer to this [test file](./src/tests/input-resolvers.test.ts).

const getIdOrEmail = composable((data: { id?: number, email?: string }) => {
const getIdOrEmail = (data: { id?: number, email?: string }) => {
return data.id ?? data.email
})
const findUserById = composable((id: number, ctx: { user: User }) => {
}
const findUserById = (id: number, ctx: { user: User }) => {
if (!ctx.user.admin) {

@@ -821,4 +812,4 @@ throw new Error('Unauthorized')

return db.users.find({ id })
})
const findUserByEmail = composable((email: string, ctx: { user: User }) => {
}
const findUserByEmail = (email: string, ctx: { user: User }) => {
if (!ctx.user.admin) {

@@ -828,3 +819,3 @@ throw new Error('Unauthorized')

return db.users.find
})
}
const findUserByIdOrEmail = context.branch(

@@ -842,5 +833,5 @@ getIdOrEmail,

const a = composable((aNumber: number, ctx: { user: User }) => String(aNumber))
const b = composable((aString: string, ctx: { user: User }) => aString == '1')
const c = composable((aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin)
const a = (aNumber: number, ctx: { user: User }) => String(aNumber)
const b = (aString: string, ctx: { user: User }) => aString == '1'
const c = (aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin

@@ -858,5 +849,5 @@ const d = context.pipe(a, b, c)

const a = composable((aNumber: number, ctx: { user: User }) => String(aNumber))
const b = composable((aString: string, ctx: { user: User }) => aString === '1')
const c = composable((aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin)
const a = (aNumber: number, ctx: { user: User }) => String(aNumber)
const b = (aString: string, ctx: { user: User }) => aString === '1'
const c = (aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin

@@ -863,0 +854,0 @@ const d = context.sequence(a, b, c)

@@ -17,5 +17,5 @@ ## Context

import { context } from 'composable-functions'
const dangerousFunction = composable(async (input: string, { user } : { user: { name: string, admin: boolean } }) => {
const dangerousFunction = async (input: string, { user } : { user: { name: string, admin: boolean } }) => {
// do something that only the admin can do
})
}

@@ -36,4 +36,4 @@ const carryUser = context.pipe(gatherInput, dangerousFunction)

const a = composable((str: string, ctx: { user: User }) => str === '1')
const b = composable((bool: boolean, ctx: { user: User }) => bool && ctx.user.admin)
const a = (str: string, ctx: { user: User }) => str === '1'
const b = (bool: boolean, ctx: { user: User }) => bool && ctx.user.admin

@@ -58,4 +58,4 @@ const pipeline = context.pipe(a, b)

const a = composable((str: string, ctx: { user: User }) => str === '1')
const b = composable((bool: boolean, ctx: { user: User }) => bool && ctx.user.admin)
const a = (str: string, ctx: { user: User }) => str === '1'
const b = (bool: boolean, ctx: { user: User }) => bool && ctx.user.admin

@@ -81,5 +81,4 @@ const sequence = context.sequence(a, b)

const adminIncrement = composable((a: number, { user }: { user: { admin: boolean } }) =>
const adminIncrement = (a: number, { user }: { user: { admin: boolean } }) =>
user.admin ? a + 1 : a
)
const adminMakeItEven = (sum: number) => sum % 2 != 0 ? adminIncrement : null

@@ -86,0 +85,0 @@ const incrementUntilEven = context.branch(adminIncrement, adminMakeItEven)

@@ -34,10 +34,6 @@ import { composable, failure, fromSuccess, success } from './constructors.js';

* ```ts
* import { composable, pipe } from 'composable-functions'
* import { pipe } from 'composable-functions'
*
* const a = composable(
* ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }),
* )
* const b = composable(
* ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }),
* )
* const a = ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) })
* const b = ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' })
* const d = pipe(a, b)

@@ -62,7 +58,7 @@ * // ^? Composable<({ aNumber }: { aNumber: number }) => { aBoolean: boolean }>

* ```ts
* import { composable, all } from 'composable-functions'
* import { all } from 'composable-functions'
*
* const a = composable((id: number) => id + 1)
* const b = composable(String)
* const c = composable(Boolean)
* const a = (id: number) => id + 1
* const b = (x: unknown) => String(x)
* const c = (x: unknown) => Boolean(x)
* const cf = all(a, b, c)

@@ -73,4 +69,4 @@ * // ^? Composable<(id: number) => [string, number, boolean]>

function all(...fns) {
return (async (...args) => {
const results = await Promise.all(fns.map((fn) => fn(...args)));
const callable = (async (...args) => {
const results = await Promise.all(fns.map((fn) => composable(fn)(...args)));
if (results.some(({ success }) => success === false)) {

@@ -81,2 +77,4 @@ return failure(results.map(({ errors }) => errors).flat());

});
callable.kind = 'composable';
return callable;
}

@@ -89,6 +87,6 @@ /**

* ```ts
* import { composable, collect } from 'composable-functions'
* import { collect } from 'composable-functions'
*
* const a = composable(() => '1')
* const b = composable(() => 2)
* const a = () => '1'
* const b = () => 2
* const aComposable = collect({ a, b })

@@ -100,3 +98,4 @@ * // ^? Composable<() => { a: string, b: number }>

const fnsWithKey = Object.entries(fns).map(([key, cf]) => map(cf, (result) => ({ [key]: result })));
return map(all(...fnsWithKey), mergeObjects);
const allFns = all(...fnsWithKey);
return map(allFns, mergeObjects);
}

@@ -109,6 +108,6 @@ /**

* ```ts
* import { composable, sequence } from 'composable-functions'
* import { sequence } from 'composable-functions'
*
* const a = composable((aNumber: number) => String(aNumber))
* const b = composable((aString: string) => aString === '1')
* const a = (aNumber: number) => String(aNumber)
* const b = (aString: string) => aString === '1'
* const cf = sequence(a, b)

@@ -119,5 +118,5 @@ * // ^? Composable<(aNumber: number) => [string, boolean]>

function sequence(...fns) {
return (async (...args) => {
const callable = (async (...args) => {
const [head, ...tail] = fns;
const res = await head(...args);
const res = await composable(head)(...args);
if (!res.success)

@@ -127,3 +126,3 @@ return failure(res.errors);

for await (const fn of tail) {
const res = await fn(result.at(-1));
const res = await composable(fn)(result.at(-1));
if (!res.success)

@@ -135,2 +134,4 @@ return failure(res.errors);

});
callable.kind = 'composable';
return callable;
}

@@ -144,5 +145,5 @@ /**

* ```ts
* import { composable, map } from 'composable-functions'
* import { map } from 'composable-functions'
*
* const increment = composable((n: number) => n + 1)
* const increment = (n: number) => n + 1
* const incrementToString = map(increment, String)

@@ -155,8 +156,10 @@ * // ^? Composable<(n: number) => string>

function map(fn, mapper) {
return async (...args) => {
const result = await fn(...args);
const callable = (async (...args) => {
const result = await composable(fn)(...args);
if (!result.success)
return failure(result.errors);
return composable(mapper)(result.data, ...args);
};
});
callable.kind = 'composable';
return callable;
}

@@ -170,5 +173,5 @@ /**

* ```ts
* import { composable, mapParameters } from 'composable-functions'
* import { mapParameters } from 'composable-functions'
*
* const incrementId = composable(({ id }: { id: number }) => id + 1)
* const incrementId = ({ id }: { id: number }) => id + 1
* const increment = mapParameters(incrementId, (id: number) => [{ id }])

@@ -179,8 +182,10 @@ * // ^? Composable<(id: number) => number>

function mapParameters(fn, mapper) {
return async (...args) => {
const callable = (async (...args) => {
const output = await composable(mapper)(...args);
if (!output.success)
return failure(output.errors);
return fn(...output.data);
};
return composable(fn)(...output.data);
});
callable.kind = 'composable';
return callable;
}

@@ -193,5 +198,5 @@ /**

* ```ts
* import { composable, catchFailure } from 'composable-functions'
* import { catchFailure } from 'composable-functions'
*
* const increment = composable(({ id }: { id: number }) => id + 1)
* const increment = ({ id }: { id: number }) => id + 1
* const negativeOnError = catchFailure(increment, (result, originalInput) => (

@@ -203,8 +208,10 @@ * originalInput.id * -1

function catchFailure(fn, catcher) {
return async (...args) => {
const res = await fn(...args);
const callable = (async (...args) => {
const res = await composable(fn)(...args);
if (res.success)
return success(res.data);
return composable(catcher)(res.errors, ...args);
};
});
callable.kind = 'composable';
return callable;
}

@@ -217,5 +224,5 @@ /**

* ```ts
* import { composable, mapErrors } from 'composable-functions'
* import { mapErrors } from 'composable-functions'
*
* const increment = composable(({ id }: { id: number }) => id + 1)
* const increment = ({ id }: { id: number }) => id + 1
* const incrementWithErrorSummary = mapErrors(increment, (result) => ({

@@ -227,4 +234,4 @@ * errors: [{ message: 'Errors count: ' + result.errors.length }],

function mapErrors(fn, mapper) {
return async (...args) => {
const res = await fn(...args);
const callable = (async (...args) => {
const res = await composable(fn)(...args);
if (res.success)

@@ -239,3 +246,5 @@ return success(res.data);

}
};
});
callable.kind = 'composable';
return callable;
}

@@ -250,3 +259,3 @@ /**

* ```ts
* import { composable, trace } from 'composable-functions'
* import { trace } from 'composable-functions'
*

@@ -256,3 +265,3 @@ * const trackErrors = trace((result, ...args) => {

* })
* const increment = composable((id: number) => id + 1)
* const increment = (id: number) => id + 1
* const incrementAndTrackErrors = trackErrors(increment)

@@ -263,9 +272,13 @@ * // ^? Composable<(id: number) => number>

function trace(traceFn) {
return (fn) => async (...args) => {
const originalResult = await fn(...args);
const traceResult = await composable(traceFn)(originalResult, ...args);
if (traceResult.success)
return originalResult;
return failure(traceResult.errors);
};
return ((fn) => {
const callable = async (...args) => {
const originalResult = await composable(fn)(...args);
const traceResult = await composable(traceFn)(originalResult, ...args);
if (traceResult.success)
return originalResult;
return failure(traceResult.errors);
};
callable.kind = 'composable';
return callable;
});
}

@@ -294,4 +307,4 @@ /**

function branch(cf, resolver) {
return (async (...args) => {
const result = await cf(...args);
const callable = (async (...args) => {
const result = await composable(cf)(...args);
if (!result.success)

@@ -303,6 +316,8 @@ return result;

return result.data;
return fromSuccess(nextComposable)(result.data);
return fromSuccess(composable(nextComposable))(result.data);
})();
});
callable.kind = 'composable';
return callable;
}
export { all, branch, catchFailure, collect, map, mapErrors, mapParameters, mergeObjects, pipe, sequence, trace, };

@@ -1,2 +0,1 @@

import { mapErrors } from './combinators.js';
import { ContextError, ErrorList, InputError } from './errors.js';

@@ -31,3 +30,6 @@ /**

function composable(fn) {
return async (...args) => {
if ('kind' in fn && fn.kind === 'composable') {
return fn;
}
const callable = async (...args) => {
try {

@@ -45,2 +47,4 @@ // deno-lint-ignore no-explicit-any

};
callable.kind = 'composable';
return callable;
}

@@ -63,32 +67,10 @@ /**

function fromSuccess(fn, onError = (e) => e) {
return async (...args) => {
const result = await mapErrors(fn, onError)(...args);
return (async (...args) => {
const result = await fn(...args);
if (result.success)
return result.data;
throw new ErrorList(result.errors);
};
throw new ErrorList(await onError(result.errors));
});
}
/**
* 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}`
* })
*/
function withSchema(inputSchema, contextSchema) {
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler));
}
/**
* Takes a composable and creates a composable withSchema that will assert the input and context types according to the given schemas.

@@ -116,4 +98,5 @@ * @param fn a composable function

function applySchema(inputSchema, contextSchema) {
// TODO: Accept plain functions and equalize with withSchema
return (fn) => {
return ((input, context) => {
const callable = ((input, context) => {
const ctxResult = (contextSchema ?? alwaysUnknownSchema).safeParse(context);

@@ -128,4 +111,28 @@ const result = (inputSchema ?? alwaysUnknownSchema).safeParse(input);

});
callable.kind = 'composable';
return callable;
};
}
/**
* 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}`
* })
*/
function withSchema(inputSchema, contextSchema) {
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler));
}
const alwaysUnknownSchema = {

@@ -132,0 +139,0 @@ safeParse: (data) => ({ success: true, data }),

import * as A from '../combinators.js';
import { composable, fromSuccess } from '../constructors.js';
function applyContextToList(fns, context) {
return fns.map((fn) => (input) => fn(input, context));
return fns.map((fn) => {
const callable = ((input) => composable(fn)(input, context));
callable.kind = 'composable';
return callable;
});
}

@@ -25,3 +29,5 @@ /**

function pipe(...fns) {
return ((input, context) => A.pipe(...applyContextToList(fns, context))(input));
const callable = ((input, context) => A.pipe(...applyContextToList(fns, context))(input));
callable.kind = 'composable';
return callable;
}

@@ -43,3 +49,5 @@ /**

function sequence(...fns) {
return ((input, context) => A.sequence(...applyContextToList(fns, context))(input));
const callable = ((input, context) => A.sequence(...applyContextToList(fns, context))(input));
callable.kind = 'composable';
return callable;
}

@@ -50,5 +58,5 @@ /**

function branch(cf, resolver) {
return (async (...args) => {
const callable = (async (...args) => {
const [input, context] = args;
const result = await cf(input, context);
const result = await composable(cf)(input, context);
if (!result.success)

@@ -60,6 +68,8 @@ return result;

return result.data;
return fromSuccess(nextFn)(result.data, context);
return fromSuccess(composable(nextFn))(result.data, context);
})();
});
callable.kind = 'composable';
return callable;
}
export { branch, pipe, sequence };

@@ -9,3 +9,3 @@ # Migrating from domain-functions

- 🛡️ 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.
- 🤌 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**.

@@ -106,6 +106,6 @@ - 🔀 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.

### map
The `map`'s mapper function now receives all the arguments given to the composable. In domain-functions the mapper would only work with the output of the first function, that limitation is gone therefore we can work with both input and output.
The `map`'s mapper function now receives all the arguments given to the first function. In domain-functions the mapper would only work with the output of the first df, that limitation is gone therefore we can work with both input and output.
```ts
const add = composable((a: number, b: number) => a + b)
const add = (a: number, b: number) => a + b
const aggregateInputAndOutput = map(add, (result, a, b) => ({ result, a, b }))

@@ -153,3 +153,3 @@ // ^? Composable<(a: number, b: number) => { result: number, a: number, b: number }>

```ts
const fn = composable((a: number, b: number, c: number) => a + b + c)
const fn = (a: number, b: number, c: number) => a + b + c
const withTrace = trace((...args) => console.log(...args))(fn)

@@ -180,3 +180,3 @@ const result = await withTrace(1, 2, 3)

// you can do
const fn = map(sequence(nameDf, ageDf), ([name, age]) => ({ name, age }))
const fn = map(sequence(nameFn, ageFn), ([name, age]) => ({ name, age }))
```

@@ -197,4 +197,4 @@

// you can do
const fn1 = composable(() => ({ firstName: 'John' }))
const fn2 = composable(() => ({ lastName: 'Doe' }))
const fn1 = () => ({ firstName: 'John' })
const fn2 = () => ({ lastName: 'Doe' })
const fn = map(all(fn1, fn2), mergeObjects)

@@ -201,0 +201,0 @@ ```

{
"name": "composable-functions",
"version": "4.1.0",
"version": "4.2.0",
"description": "Types and functions to make composition easy and safe",

@@ -5,0 +5,0 @@ "author": "Seasoned",

@@ -48,7 +48,7 @@ <p align="center">

const faultyAdd = composable((a: number, b: number) => {
const faultyAdd = (a: number, b: number) => {
if (a === 1) throw new Error('a is 1')
return a + b
})
const show = composable(String)
}
const show = (a: number) => String(a)
const addAndShow = pipe(faultyAdd, show)

@@ -120,3 +120,3 @@

So we can define the `add` and the `toString` functions as a `Composable`:
We can create a `Composable` by wrapping a function with the `composable` method:

@@ -128,9 +128,19 @@ ```typescript

// ^? Composable<(a: number, b: number) => number>
```
const toString = composable((a: unknown) => `${a}`)
// ^? Composable<(a: unknown) => string>
Or we can use combinators work with both plain functions and `Composable` to create other composables:
```typescript
import { composable, pipe } from 'composable-functions'
const add = composable((a: number, b: number) => a + b)
// ^? Composable<(a: number, b: number) => number>
const toString = (a: unknown) => `${a}`
const addAndReturnString = pipe(add, toString)
// ^? Composable<(a: number, b: number) => string>
```
## Sequential composition
Now we can compose them using pipe to create `addAndReturnString`:
We can compose the functions above using pipe to create `addAndReturnString`:

@@ -156,6 +166,6 @@ ```typescript

### Using non-composables (mapping)
### Transforming the output (mapping)
Sometimes we want to use a simple function in this sort of sequential composition. Imagine that `toString` is not a composable, and you just want to apply a plain old function to the result of `add` when it succeeds.
The function `map` can be used for this, since we are mapping over the result of a `Composable`:
Sometimes we want to use a simple function to transform the output of another function. Imagine you want to apply a plain old function to the result of `add` when it succeeds.
The function `map` can be used for this:

@@ -166,2 +176,3 @@ ```typescript

const addAndReturnString = map(add, result => `${result}`)
// ^? Composable<(a: number, b: number) => string>
```

@@ -175,6 +186,6 @@

```typescript
import { composable, all } from 'composable-functions'
import { all } from 'composable-functions'
const add = composable((a: number, b: number) => a + b)
const mul = composable((a: number, b: number) => a * b)
const add = (a: number, b: number) => a + b
const mul = (a: number, b: number) => a * b
const addAndMul = all(add, mul)

@@ -186,2 +197,13 @@ // ^? Composable<(a: number, b: number) => [number, number]>

If you want to work with records instead of tuples, you can use the `collect` function:
```typescript
import { collect } from 'composable-functions'
const add = (a: number, b: number) => a + b
const mul = (a: number, b: number) => a * b
const addAndMul = collect({ add, mul })
// ^? Composable<(a: number, b: number) => { add: number, mul: number }>
```
## Handling errors

@@ -221,3 +243,3 @@ Since a `Composable` always return a type `Result<T>` that might be either a failure or a success, there are never exceptions to catch. Any exception inside a `Composable` will return as an object with the shape: `{ success: false, errors: Error[] }`.

```typescript
import { composable, catchFailure } from 'composable-functions'
import { catchFailure } from 'composable-functions'

@@ -224,0 +246,0 @@ // assuming we have the definitions from the previous example

@@ -38,10 +38,6 @@ "use strict";

* ```ts
* import { composable, pipe } from 'composable-functions'
* import { pipe } from 'composable-functions'
*
* const a = composable(
* ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }),
* )
* const b = composable(
* ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }),
* )
* const a = ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) })
* const b = ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' })
* const d = pipe(a, b)

@@ -67,7 +63,7 @@ * // ^? Composable<({ aNumber }: { aNumber: number }) => { aBoolean: boolean }>

* ```ts
* import { composable, all } from 'composable-functions'
* import { all } from 'composable-functions'
*
* const a = composable((id: number) => id + 1)
* const b = composable(String)
* const c = composable(Boolean)
* const a = (id: number) => id + 1
* const b = (x: unknown) => String(x)
* const c = (x: unknown) => Boolean(x)
* const cf = all(a, b, c)

@@ -78,4 +74,4 @@ * // ^? Composable<(id: number) => [string, number, boolean]>

function all(...fns) {
return (async (...args) => {
const results = await Promise.all(fns.map((fn) => fn(...args)));
const callable = (async (...args) => {
const results = await Promise.all(fns.map((fn) => (0, constructors_js_1.composable)(fn)(...args)));
if (results.some(({ success }) => success === false)) {

@@ -86,2 +82,4 @@ return (0, constructors_js_1.failure)(results.map(({ errors }) => errors).flat());

});
callable.kind = 'composable';
return callable;
}

@@ -95,6 +93,6 @@ exports.all = all;

* ```ts
* import { composable, collect } from 'composable-functions'
* import { collect } from 'composable-functions'
*
* const a = composable(() => '1')
* const b = composable(() => 2)
* const a = () => '1'
* const b = () => 2
* const aComposable = collect({ a, b })

@@ -106,3 +104,4 @@ * // ^? Composable<() => { a: string, b: number }>

const fnsWithKey = Object.entries(fns).map(([key, cf]) => map(cf, (result) => ({ [key]: result })));
return map(all(...fnsWithKey), mergeObjects);
const allFns = all(...fnsWithKey);
return map(allFns, mergeObjects);
}

@@ -116,6 +115,6 @@ exports.collect = collect;

* ```ts
* import { composable, sequence } from 'composable-functions'
* import { sequence } from 'composable-functions'
*
* const a = composable((aNumber: number) => String(aNumber))
* const b = composable((aString: string) => aString === '1')
* const a = (aNumber: number) => String(aNumber)
* const b = (aString: string) => aString === '1'
* const cf = sequence(a, b)

@@ -126,5 +125,5 @@ * // ^? Composable<(aNumber: number) => [string, boolean]>

function sequence(...fns) {
return (async (...args) => {
const callable = (async (...args) => {
const [head, ...tail] = fns;
const res = await head(...args);
const res = await (0, constructors_js_1.composable)(head)(...args);
if (!res.success)

@@ -134,3 +133,3 @@ return (0, constructors_js_1.failure)(res.errors);

for await (const fn of tail) {
const res = await fn(result.at(-1));
const res = await (0, constructors_js_1.composable)(fn)(result.at(-1));
if (!res.success)

@@ -142,2 +141,4 @@ return (0, constructors_js_1.failure)(res.errors);

});
callable.kind = 'composable';
return callable;
}

@@ -152,5 +153,5 @@ exports.sequence = sequence;

* ```ts
* import { composable, map } from 'composable-functions'
* import { map } from 'composable-functions'
*
* const increment = composable((n: number) => n + 1)
* const increment = (n: number) => n + 1
* const incrementToString = map(increment, String)

@@ -163,8 +164,10 @@ * // ^? Composable<(n: number) => string>

function map(fn, mapper) {
return async (...args) => {
const result = await fn(...args);
const callable = (async (...args) => {
const result = await (0, constructors_js_1.composable)(fn)(...args);
if (!result.success)
return (0, constructors_js_1.failure)(result.errors);
return (0, constructors_js_1.composable)(mapper)(result.data, ...args);
};
});
callable.kind = 'composable';
return callable;
}

@@ -179,5 +182,5 @@ exports.map = map;

* ```ts
* import { composable, mapParameters } from 'composable-functions'
* import { mapParameters } from 'composable-functions'
*
* const incrementId = composable(({ id }: { id: number }) => id + 1)
* const incrementId = ({ id }: { id: number }) => id + 1
* const increment = mapParameters(incrementId, (id: number) => [{ id }])

@@ -188,8 +191,10 @@ * // ^? Composable<(id: number) => number>

function mapParameters(fn, mapper) {
return async (...args) => {
const callable = (async (...args) => {
const output = await (0, constructors_js_1.composable)(mapper)(...args);
if (!output.success)
return (0, constructors_js_1.failure)(output.errors);
return fn(...output.data);
};
return (0, constructors_js_1.composable)(fn)(...output.data);
});
callable.kind = 'composable';
return callable;
}

@@ -203,5 +208,5 @@ exports.mapParameters = mapParameters;

* ```ts
* import { composable, catchFailure } from 'composable-functions'
* import { catchFailure } from 'composable-functions'
*
* const increment = composable(({ id }: { id: number }) => id + 1)
* const increment = ({ id }: { id: number }) => id + 1
* const negativeOnError = catchFailure(increment, (result, originalInput) => (

@@ -213,8 +218,10 @@ * originalInput.id * -1

function catchFailure(fn, catcher) {
return async (...args) => {
const res = await fn(...args);
const callable = (async (...args) => {
const res = await (0, constructors_js_1.composable)(fn)(...args);
if (res.success)
return (0, constructors_js_1.success)(res.data);
return (0, constructors_js_1.composable)(catcher)(res.errors, ...args);
};
});
callable.kind = 'composable';
return callable;
}

@@ -228,5 +235,5 @@ exports.catchFailure = catchFailure;

* ```ts
* import { composable, mapErrors } from 'composable-functions'
* import { mapErrors } from 'composable-functions'
*
* const increment = composable(({ id }: { id: number }) => id + 1)
* const increment = ({ id }: { id: number }) => id + 1
* const incrementWithErrorSummary = mapErrors(increment, (result) => ({

@@ -238,4 +245,4 @@ * errors: [{ message: 'Errors count: ' + result.errors.length }],

function mapErrors(fn, mapper) {
return async (...args) => {
const res = await fn(...args);
const callable = (async (...args) => {
const res = await (0, constructors_js_1.composable)(fn)(...args);
if (res.success)

@@ -250,3 +257,5 @@ return (0, constructors_js_1.success)(res.data);

}
};
});
callable.kind = 'composable';
return callable;
}

@@ -262,3 +271,3 @@ exports.mapErrors = mapErrors;

* ```ts
* import { composable, trace } from 'composable-functions'
* import { trace } from 'composable-functions'
*

@@ -268,3 +277,3 @@ * const trackErrors = trace((result, ...args) => {

* })
* const increment = composable((id: number) => id + 1)
* const increment = (id: number) => id + 1
* const incrementAndTrackErrors = trackErrors(increment)

@@ -275,9 +284,13 @@ * // ^? Composable<(id: number) => number>

function trace(traceFn) {
return (fn) => async (...args) => {
const originalResult = await fn(...args);
const traceResult = await (0, constructors_js_1.composable)(traceFn)(originalResult, ...args);
if (traceResult.success)
return originalResult;
return (0, constructors_js_1.failure)(traceResult.errors);
};
return ((fn) => {
const callable = async (...args) => {
const originalResult = await (0, constructors_js_1.composable)(fn)(...args);
const traceResult = await (0, constructors_js_1.composable)(traceFn)(originalResult, ...args);
if (traceResult.success)
return originalResult;
return (0, constructors_js_1.failure)(traceResult.errors);
};
callable.kind = 'composable';
return callable;
});
}

@@ -307,4 +320,4 @@ exports.trace = trace;

function branch(cf, resolver) {
return (async (...args) => {
const result = await cf(...args);
const callable = (async (...args) => {
const result = await (0, constructors_js_1.composable)(cf)(...args);
if (!result.success)

@@ -316,6 +329,8 @@ return result;

return result.data;
return (0, constructors_js_1.fromSuccess)(nextComposable)(result.data);
return (0, constructors_js_1.fromSuccess)((0, constructors_js_1.composable)(nextComposable))(result.data);
})();
});
callable.kind = 'composable';
return callable;
}
exports.branch = branch;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.withSchema = exports.success = exports.fromSuccess = exports.failure = exports.composable = exports.applySchema = void 0;
const combinators_js_1 = require("./combinators.js");
const errors_js_1 = require("./errors.js");

@@ -36,3 +35,6 @@ /**

function composable(fn) {
return async (...args) => {
if ('kind' in fn && fn.kind === 'composable') {
return fn;
}
const callable = async (...args) => {
try {

@@ -50,2 +52,4 @@ // deno-lint-ignore no-explicit-any

};
callable.kind = 'composable';
return callable;
}

@@ -69,34 +73,11 @@ exports.composable = composable;

function fromSuccess(fn, onError = (e) => e) {
return async (...args) => {
const result = await (0, combinators_js_1.mapErrors)(fn, onError)(...args);
return (async (...args) => {
const result = await fn(...args);
if (result.success)
return result.data;
throw new errors_js_1.ErrorList(result.errors);
};
throw new errors_js_1.ErrorList(await onError(result.errors));
});
}
exports.fromSuccess = fromSuccess;
/**
* 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}`
* })
*/
function withSchema(inputSchema, contextSchema) {
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler));
}
exports.withSchema = withSchema;
/**
* Takes a composable and creates a composable withSchema that will assert the input and context types according to the given schemas.

@@ -124,4 +105,5 @@ * @param fn a composable function

function applySchema(inputSchema, contextSchema) {
// TODO: Accept plain functions and equalize with withSchema
return (fn) => {
return ((input, context) => {
const callable = ((input, context) => {
const ctxResult = (contextSchema ?? alwaysUnknownSchema).safeParse(context);

@@ -136,7 +118,32 @@ const result = (inputSchema ?? alwaysUnknownSchema).safeParse(input);

});
callable.kind = 'composable';
return callable;
};
}
exports.applySchema = 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}`
* })
*/
function withSchema(inputSchema, contextSchema) {
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler));
}
exports.withSchema = withSchema;
const alwaysUnknownSchema = {
safeParse: (data) => ({ success: true, data }),
};

@@ -30,3 +30,7 @@ "use strict";

function applyContextToList(fns, context) {
return fns.map((fn) => (input) => fn(input, context));
return fns.map((fn) => {
const callable = ((input) => (0, constructors_js_1.composable)(fn)(input, context));
callable.kind = 'composable';
return callable;
});
}

@@ -52,3 +56,5 @@ /**

function pipe(...fns) {
return ((input, context) => A.pipe(...applyContextToList(fns, context))(input));
const callable = ((input, context) => A.pipe(...applyContextToList(fns, context))(input));
callable.kind = 'composable';
return callable;
}

@@ -71,3 +77,5 @@ exports.pipe = pipe;

function sequence(...fns) {
return ((input, context) => A.sequence(...applyContextToList(fns, context))(input));
const callable = ((input, context) => A.sequence(...applyContextToList(fns, context))(input));
callable.kind = 'composable';
return callable;
}

@@ -79,5 +87,5 @@ exports.sequence = sequence;

function branch(cf, resolver) {
return (async (...args) => {
const callable = (async (...args) => {
const [input, context] = args;
const result = await cf(input, context);
const result = await (0, constructors_js_1.composable)(cf)(input, context);
if (!result.success)

@@ -89,6 +97,8 @@ return result;

return result.data;
return (0, constructors_js_1.fromSuccess)(nextFn)(result.data, context);
return (0, constructors_js_1.fromSuccess)((0, constructors_js_1.composable)(nextFn))(result.data, context);
})();
});
callable.kind = 'composable';
return callable;
}
exports.branch = branch;
import type { BranchReturn, CanComposeInParallel, CanComposeInSequence, Composable, MapParametersReturn, MergeObjects, PipeReturn, RecordToTuple, Result, SequenceReturn, UnpackData } from './types.js';
import type { Internal } from './internal/types.js';
/**

@@ -32,10 +33,6 @@ * Merges a list of objects into a single object.

* ```ts
* import { composable, pipe } from 'composable-functions'
* import { pipe } from 'composable-functions'
*
* const a = composable(
* ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }),
* )
* const b = composable(
* ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }),
* )
* const a = ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) })
* const b = ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' })
* const d = pipe(a, b)

@@ -45,3 +42,3 @@ * // ^? Composable<({ aNumber }: { aNumber: number }) => { aBoolean: boolean }>

*/
declare function pipe<Fns extends [Composable, ...Composable[]]>(...fns: Fns): PipeReturn<CanComposeInSequence<Fns>>;
declare function pipe<Fns extends [Internal.AnyFn, ...Internal.AnyFn[]]>(...fns: Fns): PipeReturn<CanComposeInSequence<Internal.Composables<Fns>>>;
/**

@@ -58,7 +55,7 @@ * Composes functions to run in parallel returning a tuple of all results when all are successful.

* ```ts
* import { composable, all } from 'composable-functions'
* import { all } from 'composable-functions'
*
* const a = composable((id: number) => id + 1)
* const b = composable(String)
* const c = composable(Boolean)
* const a = (id: number) => id + 1
* const b = (x: unknown) => String(x)
* const c = (x: unknown) => Boolean(x)
* const cf = all(a, b, c)

@@ -68,4 +65,4 @@ * // ^? Composable<(id: number) => [string, number, boolean]>

*/
declare function all<Fns extends Composable[]>(...fns: Fns): Composable<(...args: Parameters<NonNullable<CanComposeInParallel<Fns>[0]>>) => {
[k in keyof Fns]: UnpackData<Fns[k]>;
declare function all<Fns extends Internal.AnyFn[]>(...fns: Fns): Composable<(...args: Parameters<NonNullable<CanComposeInParallel<Internal.Composables<Fns>>[0]>>) => {
[k in keyof Fns]: UnpackData<Internal.Composables<Fns>[k]>;
}>;

@@ -78,6 +75,6 @@ /**

* ```ts
* import { composable, collect } from 'composable-functions'
* import { collect } from 'composable-functions'
*
* const a = composable(() => '1')
* const b = composable(() => 2)
* const a = () => '1'
* const b = () => 2
* const aComposable = collect({ a, b })

@@ -87,4 +84,4 @@ * // ^? Composable<() => { a: string, b: number }>

*/
declare function collect<Fns extends Record<string, Composable>>(fns: Fns): Composable<(...args: Parameters<Exclude<CanComposeInParallel<RecordToTuple<Fns>>[0], undefined>>) => {
[key in keyof Fns]: UnpackData<Fns[key]>;
declare function collect<Fns extends Record<string, Internal.AnyFn>>(fns: Fns): Composable<(...args: Parameters<Exclude<CanComposeInParallel<RecordToTuple<Internal.Composables<Fns>>>[0], undefined>>) => {
[key in keyof Fns]: UnpackData<Composable<Fns[key]>>;
}>;

@@ -97,6 +94,6 @@ /**

* ```ts
* import { composable, sequence } from 'composable-functions'
* import { sequence } from 'composable-functions'
*
* const a = composable((aNumber: number) => String(aNumber))
* const b = composable((aString: string) => aString === '1')
* const a = (aNumber: number) => String(aNumber)
* const b = (aString: string) => aString === '1'
* const cf = sequence(a, b)

@@ -106,3 +103,3 @@ * // ^? Composable<(aNumber: number) => [string, boolean]>

*/
declare function sequence<Fns extends [Composable, ...Composable[]]>(...fns: Fns): SequenceReturn<CanComposeInSequence<Fns>>;
declare function sequence<Fns extends [Internal.AnyFn, ...Internal.AnyFn[]]>(...fns: Fns): SequenceReturn<CanComposeInSequence<Internal.Composables<Fns>>>;
/**

@@ -115,5 +112,5 @@ * It takes a Composable and a mapper to apply a transformation over the resulting output. It only runs if the function was successfull. When the given function fails, its error is returned wihout changes.

* ```ts
* import { composable, map } from 'composable-functions'
* import { map } from 'composable-functions'
*
* const increment = composable((n: number) => n + 1)
* const increment = (n: number) => n + 1
* const incrementToString = map(increment, String)

@@ -125,3 +122,3 @@ * // ^? Composable<(n: number) => string>

*/
declare function map<Fn extends Composable, O>(fn: Fn, mapper: (res: UnpackData<Fn>, ...originalInput: Parameters<Fn>) => O | Promise<O>): Composable<(...args: Parameters<Fn>) => O>;
declare function map<Fn extends Internal.AnyFn, O>(fn: Fn, mapper: (res: UnpackData<Composable<Fn>>, ...originalInput: Parameters<Fn>) => O | Promise<O>): Composable<(...args: Parameters<Fn>) => O>;
/**

@@ -134,5 +131,5 @@ * It takes a Composable and a function that will map the input parameters to the expected input of the given Composable. Good to adequate the output of a composable into the input of the next composable in a composition. The function must return an array of parameters that will be passed to the Composable.

* ```ts
* import { composable, mapParameters } from 'composable-functions'
* import { mapParameters } from 'composable-functions'
*
* const incrementId = composable(({ id }: { id: number }) => id + 1)
* const incrementId = ({ id }: { id: number }) => id + 1
* const increment = mapParameters(incrementId, (id: number) => [{ id }])

@@ -142,3 +139,3 @@ * // ^? Composable<(id: number) => number>

*/
declare function mapParameters<Fn extends Composable, NewParameters extends unknown[], const MapperOutput extends Parameters<Fn>>(fn: Fn, mapper: (...args: NewParameters) => Promise<MapperOutput> | MapperOutput): MapParametersReturn<Fn, NewParameters, MapperOutput>;
declare function mapParameters<Fn extends Internal.AnyFn, NewParameters extends unknown[], const MapperOutput extends Parameters<Composable<Fn>>>(fn: Fn, mapper: (...args: NewParameters) => Promise<MapperOutput> | MapperOutput): MapParametersReturn<Composable<Fn>, NewParameters, MapperOutput>;
/**

@@ -150,5 +147,5 @@ * Try to recover from a resulting Failure. When the given function succeeds, its result is returned without changes.

* ```ts
* import { composable, catchFailure } from 'composable-functions'
* import { catchFailure } from 'composable-functions'
*
* const increment = composable(({ id }: { id: number }) => id + 1)
* const increment = ({ id }: { id: number }) => id + 1
* const negativeOnError = catchFailure(increment, (result, originalInput) => (

@@ -159,3 +156,3 @@ * originalInput.id * -1

*/
declare function catchFailure<Fn extends Composable, C extends (err: Error[], ...originalInput: Parameters<Fn>) => any>(fn: Fn, catcher: C): Composable<(...args: Parameters<Fn>) => Awaited<ReturnType<C>> extends never[] ? UnpackData<Fn> extends any[] ? UnpackData<Fn> : Awaited<ReturnType<C>> | UnpackData<Fn> : Awaited<ReturnType<C>> | UnpackData<Fn>>;
declare function catchFailure<Fn extends Internal.AnyFn, C extends (err: Error[], ...originalInput: Parameters<Fn>) => any>(fn: Fn, catcher: C): Composable<(...args: Parameters<Fn>) => Awaited<ReturnType<C>> extends never[] ? UnpackData<Composable<Fn>> extends any[] ? UnpackData<Composable<Fn>> : Awaited<ReturnType<C>> | UnpackData<Composable<Fn>> : Awaited<ReturnType<C>> | UnpackData<Composable<Fn>>>;
/**

@@ -167,5 +164,5 @@ * Creates a new function that will apply a transformation over the list of Errors of a Failure from a given function. When the given function succeeds, its result is returned without changes.

* ```ts
* import { composable, mapErrors } from 'composable-functions'
* import { mapErrors } from 'composable-functions'
*
* const increment = composable(({ id }: { id: number }) => id + 1)
* const increment = ({ id }: { id: number }) => id + 1
* const incrementWithErrorSummary = mapErrors(increment, (result) => ({

@@ -176,3 +173,3 @@ * errors: [{ message: 'Errors count: ' + result.errors.length }],

*/
declare function mapErrors<P extends unknown[], Output>(fn: Composable<(...args: P) => Output>, mapper: (err: Error[]) => Error[] | Promise<Error[]>): Composable<(...args: P) => Output>;
declare function mapErrors<Fn extends Internal.AnyFn>(fn: Fn, mapper: (err: Error[]) => Error[] | Promise<Error[]>): Composable<Fn>;
/**

@@ -186,3 +183,3 @@ * Whenever you need to intercept inputs and a composable result without changing them you can use this function.

* ```ts
* import { composable, trace } from 'composable-functions'
* import { trace } from 'composable-functions'
*

@@ -192,3 +189,3 @@ * const trackErrors = trace((result, ...args) => {

* })
* const increment = composable((id: number) => id + 1)
* const increment = (id: number) => id + 1
* const incrementAndTrackErrors = trackErrors(increment)

@@ -198,3 +195,3 @@ * // ^? Composable<(id: number) => number>

*/
declare function trace(traceFn: (result: Result<unknown>, ...originalInput: unknown[]) => Promise<void> | void): <P extends unknown[], Output>(fn: Composable<(...args: P) => Output>) => Composable<(...args: P) => Output>;
declare function trace(traceFn: (result: Result<unknown>, ...originalInput: unknown[]) => Promise<void> | void): <Fn extends Internal.AnyFn>(fn: Fn) => Composable<Fn>;
/**

@@ -221,3 +218,3 @@ * Compose 2 functions conditionally.

*/
declare function branch<SourceComposable extends Composable, Resolver extends (o: UnpackData<SourceComposable>) => Composable | null | Promise<Composable | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<SourceComposable, Resolver>;
declare function branch<SourceComposable extends Internal.AnyFn, Resolver extends (o: UnpackData<Composable<SourceComposable>>) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<Composable<SourceComposable>, Resolver>;
export { all, branch, catchFailure, collect, map, mapErrors, mapParameters, mergeObjects, pipe, sequence, trace, };

@@ -1,2 +0,3 @@

import type { ApplySchemaReturn, Composable, ComposableWithSchema, Failure, ParserSchema, Success } from './types.js';
import type { Internal } from './internal/types.js';
import type { ApplySchemaReturn, Composable, ComposableWithSchema, Failure, ParserSchema, Success, UnpackData } from './types.js';
/**

@@ -15,3 +16,3 @@ * It receives any data (T) and returns a Success<T> object.

*/
declare function composable<T extends Function>(fn: T): Composable<T extends (...args: any[]) => any ? T : never>;
declare function composable<T extends Function>(fn: T): Composable<T extends Internal.AnyFn ? T : never>;
/**

@@ -34,22 +35,2 @@ * It can be used to call a composable from another composable. It will return the output of the given function if it was successfull, otherwise it will throw a `ErrorList` that will bubble up to the parent function.

/**
* 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}`
* })
*/
declare function withSchema<I, C>(inputSchema?: ParserSchema<I>, contextSchema?: ParserSchema<C>): <Output>(hander: (input: I, context: C) => Output) => ComposableWithSchema<Output>;
/**
* Takes a composable and creates a composable withSchema that will assert the input and context types according to the given schemas.

@@ -76,3 +57,27 @@ * @param fn a composable function

*/
declare function applySchema<ParsedInput, ParsedContext>(inputSchema?: ParserSchema<ParsedInput>, contextSchema?: ParserSchema<ParsedContext>): <R, Input, Context>(fn: Composable<(input?: Input | undefined, context?: Context | undefined) => R>) => ApplySchemaReturn<ParsedInput, ParsedContext, Composable<(input?: Input | undefined, context?: Context | undefined) => R>>;
declare function applySchema<ParsedInput, ParsedContext>(inputSchema?: ParserSchema<ParsedInput>, contextSchema?: ParserSchema<ParsedContext>): <R, Input, Context>(fn: ((input: Input, context: Context) => Promise<import("./types.js").Result<Awaited<R>>>) & {
kind: "composable";
}) => ApplySchemaReturn<ParsedInput, ParsedContext, ((input: Input, context: Context) => Promise<import("./types.js").Result<Awaited<R>>>) & {
kind: "composable";
}>;
/**
* 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}`
* })
*/
declare function withSchema<I, C>(inputSchema?: ParserSchema<I>, contextSchema?: ParserSchema<C>): <Fn extends (input: I, context: C) => unknown>(fn: Fn) => ComposableWithSchema<UnpackData<Composable<Fn>>>;
export { applySchema, composable, failure, fromSuccess, success, withSchema };
import type { Composable, UnpackData } from '../types.js';
import type { BranchReturn, PipeReturn, SequenceReturn } from './types.js';
import type { Internal } from '../internal/types.js';
/**

@@ -21,3 +22,3 @@ * Creates a single composable out of a chain of multiple functions. It will pass the same context to all given functions, and it will pass the output of a function as the next function's input in left-to-right order. The resulting data will be the output of the rightmost function.

*/
declare function pipe<Fns extends Composable[]>(...fns: Fns): PipeReturn<Fns>;
declare function pipe<Fns extends Internal.AnyFn[]>(...fns: Fns): PipeReturn<Internal.Composables<Fns>>;
/**

@@ -37,7 +38,7 @@ * Works like `context.pipe` but it will collect the output of every function in a tuple.

*/
declare function sequence<Fns extends Composable[]>(...fns: Fns): SequenceReturn<Fns>;
declare function sequence<Fns extends Internal.AnyFn[]>(...fns: Fns): SequenceReturn<Internal.Composables<Fns>>;
/**
* Like branch but preserving the context parameter.
*/
declare function branch<SourceComposable extends Composable, Resolver extends (o: UnpackData<SourceComposable>) => Composable | null | Promise<Composable | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<SourceComposable, Resolver>;
declare function branch<SourceComposable extends Internal.AnyFn, Resolver extends (o: UnpackData<Composable<SourceComposable>>) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<Composable<SourceComposable>, Resolver>;
export { branch, pipe, sequence };

@@ -23,5 +23,8 @@ import type { Internal } from '../internal/types.js';

type SetContext<Params extends unknown[], Ctx extends [unknown?] = [unknown?]> = Params extends [infer firstMandatory, ...any] ? [firstMandatory, ...Ctx] : Params extends [...Partial<[infer firstOptional, ...any]>] ? [firstOptional?, ...Ctx] : never;
type BranchContext<SourceComposable extends Composable, Resolver extends (...args: any[]) => Composable | null | Promise<Composable | null>> = Awaited<ReturnType<Resolver>> extends Composable<any> ? CommonContext<[SourceComposable, Awaited<ReturnType<Resolver>>]> : GetContext<Parameters<SourceComposable>>;
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Composable | null | Promise<Composable | null>> = CanComposeInSequence<[
type BranchContext<SourceComposable extends Composable, Resolver extends (...args: any[]) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>> = Awaited<ReturnType<Resolver>> extends Internal.AnyFn ? CommonContext<[
SourceComposable,
Composable<NonNullable<Awaited<ReturnType<Resolver>>>>
]> : GetContext<Parameters<SourceComposable>>;
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>> = CanComposeInSequence<[
SourceComposable,
Composable<Resolver>

@@ -34,3 +37,3 @@ ]> extends Composable[] ? Awaited<ReturnType<Resolver>> extends null ? SourceComposable : CanComposeInSequence<[

Awaited<ReturnType<Resolver>>
]>[0]>, BranchContext<SourceComposable, Resolver>>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>> : UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>;
]>[0]>, BranchContext<SourceComposable, Resolver>>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>> : UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>;
export type { BranchReturn, CommonContext, GetContext, PipeReturn, SequenceReturn, SetContext, };

@@ -0,1 +1,2 @@

import type { Composable } from '../types.js';
declare namespace Internal {

@@ -10,2 +11,3 @@ type IncompatibleArguments = {

};
type AnyFn = (...args: any[]) => any;
type Prettify<T> = {

@@ -15,3 +17,6 @@ [K in keyof T]: T[K];

type IsNever<A> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends never ? 1 : 2) ? true : false;
type ApplyArgumentsToFns<Fns extends any[], Args extends any[], Output extends any[] = []> = Fns extends [(...a: any[]) => infer OA, ...infer restA] ? ApplyArgumentsToFns<restA, Args, [...Output, (...a: Args) => OA]> : Output;
type ApplyArgumentsToFns<Fns extends unknown[], Args extends unknown[], Output extends Composable[] = []> = Fns extends [Composable<(...a: any[]) => infer OA>, ...infer restA] ? ApplyArgumentsToFns<restA, Args, [
...Output,
Composable<(...a: Args) => OA>
]> : Output;
type UnionToTuple<T> = ((T extends any ? (t: T) => T : never) extends infer U ? (U extends any ? (u: U) => any : never) extends (v: infer V) => any ? V : never : never) extends (_: any) => infer W ? [...UnionToTuple<Exclude<T, W>>, W] : [];

@@ -45,3 +50,6 @@ type Keys<R extends Record<string, any>> = UnionToTuple<keyof R>;

} ? B : A extends Record<PropertyKey, unknown> ? B extends Record<PropertyKey, unknown> ? Prettify<A & B> : FailToCompose<A, B> : FailToCompose<A, B>;
type Composables<Fns extends Record<string, AnyFn> | Array<AnyFn>> = {
[K in keyof Fns]: Composable<Extract<Fns[K], AnyFn>>;
};
}
export type { Internal };

@@ -41,3 +41,7 @@ import type { Internal } from './internal/types.js';

*/
type Composable<T extends (...args: any[]) => any = (...args: any[]) => any> = (...args: Parameters<T>) => Promise<Result<Awaited<ReturnType<T>>>>;
type Composable<T extends Internal.AnyFn = Internal.AnyFn> = T extends {
kind: 'composable';
} ? T : ((...args: Parameters<T>) => Promise<Result<Awaited<ReturnType<T>>>>) & {
kind: 'composable';
};
/**

@@ -80,3 +84,3 @@ * A composable async function with schema validation at runtime.

*/
type CanComposeInSequence<Fns extends any[], Arguments extends any[] = []> = Fns extends [Composable<(...a: infer PA) => infer OA>, ...infer restA] ? restA extends [
type CanComposeInSequence<Fns extends unknown[], Arguments extends unknown[] = []> = Fns extends [Composable<(...a: infer PA) => infer OA>, ...infer restA] ? restA extends [
Composable<(firstParameter: infer FirstBParameter, ...b: infer PB) => any>,

@@ -91,3 +95,3 @@ ...unknown[]

*/
type CanComposeInParallel<Fns extends any[], OriginalFns extends any[] = Fns> = Fns extends [Composable<(...a: infer PA) => any>, ...infer restA] ? restA extends [Composable<(...b: infer PB) => infer OB>, ...infer restB] ? Internal.SubtypesTuple<PA, PB> extends [...infer MergedP] ? CanComposeInParallel<[
type CanComposeInParallel<Fns extends unknown[], OriginalFns extends unknown[] = Fns> = Fns extends [Composable<(...a: infer PA) => unknown>, ...infer restA] ? restA extends [Composable<(...b: infer PB) => infer OB>, ...infer restB] ? Internal.SubtypesTuple<PA, PB> extends [...infer MergedP] ? CanComposeInParallel<[
Composable<(...args: MergedP) => OB>,

@@ -99,3 +103,3 @@ ...restB

*/
type RecordToTuple<T extends Record<string, Composable>> = Internal.RecordValuesFromKeysTuple<T, Internal.Keys<T>>;
type RecordToTuple<T extends Record<string, (...args: any) => any>> = Internal.RecordValuesFromKeysTuple<T, Internal.Keys<T>>;
/**

@@ -141,3 +145,3 @@ * A serializable error object.

*/
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Composable | null | Promise<Composable | null>> = CanComposeInSequence<[
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>> = CanComposeInSequence<[
SourceComposable,

@@ -151,3 +155,3 @@ Composable<Resolver>

Awaited<ReturnType<Resolver>>
]>[0]>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>> : UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>;
]>[0]>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>> : UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>;
/**

@@ -160,3 +164,3 @@ * Ensure that schemas are compatible with composable input and context otherwise return a FailToCompose.

*/
type MapParametersReturn<Fn extends Composable, NewParams extends any[], O extends Parameters<Fn>> = Composable<(...args: NewParams) => Internal.IsNever<O> extends true ? never : UnpackData<Fn>>;
type MapParametersReturn<Fn extends Composable, NewParams extends unknown[], O extends Parameters<Fn>> = Composable<(...args: NewParams) => Internal.IsNever<O> extends true ? never : UnpackData<Fn>>;
/**

@@ -163,0 +167,0 @@ * A type that represents an error when composing functions with incompatible arguments.

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc