fp-pack 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/dist/fp-pack.umd.js +1 -1
- package/dist/fp-pack.umd.js.map +1 -1
- package/dist/implement/async/index.d.ts +1 -0
- package/dist/implement/async/index.d.ts.map +1 -1
- package/dist/implement/async/pipeAsync.d.ts +1 -0
- package/dist/implement/async/pipeAsync.d.ts.map +1 -1
- package/dist/implement/async/pipeAsync.mjs.map +1 -1
- package/dist/implement/async/pipeAsyncSideEffect.d.ts +1 -0
- package/dist/implement/async/pipeAsyncSideEffect.d.ts.map +1 -1
- package/dist/implement/async/pipeAsyncSideEffect.mjs.map +1 -1
- package/dist/implement/async/pipeAsyncSideEffectStrict.d.ts +73 -0
- package/dist/implement/async/pipeAsyncSideEffectStrict.d.ts.map +1 -0
- package/dist/implement/async/pipeAsyncSideEffectStrict.mjs +16 -0
- package/dist/implement/async/pipeAsyncSideEffectStrict.mjs.map +1 -0
- package/dist/implement/composition/from.d.ts +6 -0
- package/dist/implement/composition/from.d.ts.map +1 -0
- package/dist/implement/composition/from.mjs +7 -0
- package/dist/implement/composition/from.mjs.map +1 -0
- package/dist/implement/composition/index.d.ts +2 -0
- package/dist/implement/composition/index.d.ts.map +1 -1
- package/dist/implement/composition/pipe.d.ts +8 -0
- package/dist/implement/composition/pipe.d.ts.map +1 -1
- package/dist/implement/composition/pipe.mjs.map +1 -1
- package/dist/implement/composition/pipe.type-test.d.ts +64 -0
- package/dist/implement/composition/pipe.type-test.d.ts.map +1 -1
- package/dist/implement/composition/pipeSideEffect.d.ts +8 -0
- package/dist/implement/composition/pipeSideEffect.d.ts.map +1 -1
- package/dist/implement/composition/pipeSideEffect.mjs.map +1 -1
- package/dist/implement/composition/pipeSideEffectStrict.d.ts +69 -0
- package/dist/implement/composition/pipeSideEffectStrict.d.ts.map +1 -0
- package/dist/implement/composition/pipeSideEffectStrict.mjs +16 -0
- package/dist/implement/composition/pipeSideEffectStrict.mjs.map +1 -0
- package/dist/implement/control/ifElse.d.ts +4 -3
- package/dist/implement/control/ifElse.d.ts.map +1 -1
- package/dist/implement/control/ifElse.mjs.map +1 -1
- package/dist/implement/control/unless.d.ts +14 -2
- package/dist/implement/control/unless.d.ts.map +1 -1
- package/dist/implement/control/unless.mjs.map +1 -1
- package/dist/implement/control/when.d.ts +14 -2
- package/dist/implement/control/when.d.ts.map +1 -1
- package/dist/implement/control/when.mjs.map +1 -1
- package/dist/implement/object/index.d.ts +1 -0
- package/dist/implement/object/index.d.ts.map +1 -1
- package/dist/implement/object/propStrict.d.ts +7 -0
- package/dist/implement/object/propStrict.d.ts.map +1 -0
- package/dist/implement/object/propStrict.mjs +12 -0
- package/dist/implement/object/propStrict.mjs.map +1 -0
- package/dist/index.mjs +248 -240
- package/dist/index.mjs.map +1 -1
- package/dist/skills/fp-pack/SKILL.md +1699 -0
- package/dist/skills/fp-pack.md +57 -9
- package/package.json +1 -1
- package/src/implement/async/index.ts +1 -0
- package/src/implement/async/pipeAsync.ts +8 -0
- package/src/implement/async/pipeAsyncSideEffect.ts +8 -0
- package/src/implement/async/pipeAsyncSideEffectStrict.test.ts +23 -0
- package/src/implement/async/pipeAsyncSideEffectStrict.ts +170 -0
- package/src/implement/composition/from.test.ts +16 -0
- package/src/implement/composition/from.ts +8 -0
- package/src/implement/composition/index.ts +2 -0
- package/src/implement/composition/pipe.test.ts +8 -0
- package/src/implement/composition/pipe.ts +28 -0
- package/src/implement/composition/pipe.type-test.ts +142 -0
- package/src/implement/composition/pipeSideEffect.test.ts +8 -0
- package/src/implement/composition/pipeSideEffect.ts +33 -0
- package/src/implement/composition/pipeSideEffectStrict.test.ts +23 -0
- package/src/implement/composition/pipeSideEffectStrict.ts +164 -0
- package/src/implement/control/curried.test.ts +18 -0
- package/src/implement/control/ifElse.ts +5 -3
- package/src/implement/control/unless.ts +20 -2
- package/src/implement/control/when.ts +20 -2
- package/src/implement/object/index.ts +1 -0
- package/src/implement/object/propStrict.test.ts +29 -0
- package/src/implement/object/propStrict.ts +20 -0
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import SideEffect from './sideEffect';
|
|
2
2
|
import pipe from './pipe';
|
|
3
3
|
import pipeSideEffect from './pipeSideEffect';
|
|
4
|
+
import pipeSideEffectStrict from './pipeSideEffectStrict';
|
|
4
5
|
import pipeAsync from '../async/pipeAsync';
|
|
5
6
|
import pipeAsyncSideEffect from '../async/pipeAsyncSideEffect';
|
|
7
|
+
import pipeAsyncSideEffectStrict from '../async/pipeAsyncSideEffectStrict';
|
|
6
8
|
|
|
7
9
|
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
|
|
8
10
|
? true
|
|
9
11
|
: false;
|
|
10
12
|
type Expect<T extends true> = T;
|
|
13
|
+
type EffectUnion<T> = Extract<T, SideEffect<any>> extends SideEffect<infer E> ? E : never;
|
|
14
|
+
type ValueUnion<T> = Exclude<T, SideEffect<any>>;
|
|
11
15
|
|
|
12
16
|
export const sideEffectInput = SideEffect.of(() => 0);
|
|
13
17
|
|
|
@@ -20,6 +24,18 @@ export const purePipe = pipe(
|
|
|
20
24
|
type PurePipeExpected = (input: number) => string;
|
|
21
25
|
export type PipePureIsStrict = Expect<Equal<typeof purePipe, PurePipeExpected>>;
|
|
22
26
|
|
|
27
|
+
export const purePipeSix = pipe(
|
|
28
|
+
(value: number) => value + 1,
|
|
29
|
+
(value) => value * 2,
|
|
30
|
+
(value) => `${value}`,
|
|
31
|
+
(value) => value.length,
|
|
32
|
+
(value) => value + 1,
|
|
33
|
+
(value) => `n:${value}`
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
type PurePipeSixExpected = (input: number) => string;
|
|
37
|
+
export type PipePureSixIsStrict = Expect<Equal<typeof purePipeSix, PurePipeSixExpected>>;
|
|
38
|
+
|
|
23
39
|
export const pipeWithSideEffectInput = pipeSideEffect(
|
|
24
40
|
(value: number) => value + 1,
|
|
25
41
|
(value) => value * 2,
|
|
@@ -31,6 +47,18 @@ export const pipeWithSideEffectValue = pipeWithSideEffectInput(sideEffectInput);
|
|
|
31
47
|
type PipeExpected = (input: number | SideEffect<any>) => string | SideEffect<any>;
|
|
32
48
|
export type PipeAcceptsSideEffectInput = Expect<Equal<typeof pipeWithSideEffectInput, PipeExpected>>;
|
|
33
49
|
|
|
50
|
+
export const pipeSideEffectSix = pipeSideEffect(
|
|
51
|
+
(value: number) => value + 1,
|
|
52
|
+
(value) => value * 2,
|
|
53
|
+
(value) => value + 3,
|
|
54
|
+
(value) => value - 1,
|
|
55
|
+
(value) => value * 2,
|
|
56
|
+
(value) => `n:${value}`
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
type PipeSideEffectSixExpected = (input: number | SideEffect<any>) => string | SideEffect<any>;
|
|
60
|
+
export type PipeSideEffectSixIsStrict = Expect<Equal<typeof pipeSideEffectSix, PipeSideEffectSixExpected>>;
|
|
61
|
+
|
|
34
62
|
export const purePipeAsync = pipeAsync(
|
|
35
63
|
(value: number) => value + 1,
|
|
36
64
|
async (value) => value * 2,
|
|
@@ -40,6 +68,18 @@ export const purePipeAsync = pipeAsync(
|
|
|
40
68
|
type PurePipeAsyncExpected = (input: number) => Promise<string>;
|
|
41
69
|
export type PipeAsyncPureIsStrict = Expect<Equal<typeof purePipeAsync, PurePipeAsyncExpected>>;
|
|
42
70
|
|
|
71
|
+
export const purePipeAsyncSix = pipeAsync(
|
|
72
|
+
(value: number) => value + 1,
|
|
73
|
+
async (value) => value * 2,
|
|
74
|
+
(value) => `${value}`,
|
|
75
|
+
async (value) => value.length,
|
|
76
|
+
(value) => value + 3,
|
|
77
|
+
async (value) => `n:${value}`
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
type PurePipeAsyncSixExpected = (input: number) => Promise<string>;
|
|
81
|
+
export type PipeAsyncPureSixIsStrict = Expect<Equal<typeof purePipeAsyncSix, PurePipeAsyncSixExpected>>;
|
|
82
|
+
|
|
43
83
|
export const pipeAsyncWithSideEffectInput = pipeAsyncSideEffect(
|
|
44
84
|
(value: number) => value + 1,
|
|
45
85
|
async (value) => value * 2,
|
|
@@ -52,3 +92,105 @@ type PipeAsyncExpected = (input: number | SideEffect<any>) => Promise<string | S
|
|
|
52
92
|
export type PipeAsyncAcceptsSideEffectInput = Expect<
|
|
53
93
|
Equal<typeof pipeAsyncWithSideEffectInput, PipeAsyncExpected>
|
|
54
94
|
>;
|
|
95
|
+
|
|
96
|
+
export const pipeAsyncSideEffectSix = pipeAsyncSideEffect(
|
|
97
|
+
(value: number) => value + 1,
|
|
98
|
+
async (value) => value * 2,
|
|
99
|
+
(value) => value + 3,
|
|
100
|
+
async (value) => value - 1,
|
|
101
|
+
(value) => value * 2,
|
|
102
|
+
async (value) => `n:${value}`
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
type PipeAsyncSideEffectSixExpected = (input: number | SideEffect<any>) => Promise<string | SideEffect<any>>;
|
|
106
|
+
export type PipeAsyncSideEffectSixIsStrict = Expect<
|
|
107
|
+
Equal<typeof pipeAsyncSideEffectSix, PipeAsyncSideEffectSixExpected>
|
|
108
|
+
>;
|
|
109
|
+
|
|
110
|
+
export const strictPipeSideEffect = pipeSideEffectStrict(
|
|
111
|
+
(value: number) => value + 1,
|
|
112
|
+
(value) => (value > 1 ? value : SideEffect.of(() => 'LOW' as const)),
|
|
113
|
+
(value) => (value > 2 ? value : SideEffect.of(() => 0 as const))
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
export const strictPipeSideEffectResult = strictPipeSideEffect(1);
|
|
117
|
+
|
|
118
|
+
type StrictSideEffectEffects = EffectUnion<typeof strictPipeSideEffectResult>;
|
|
119
|
+
type StrictSideEffectEffectsExpected = 'LOW' | 0;
|
|
120
|
+
export type PipeSideEffectStrictEffects = Expect<Equal<StrictSideEffectEffects, StrictSideEffectEffectsExpected>>;
|
|
121
|
+
|
|
122
|
+
type StrictSideEffectValue = ValueUnion<typeof strictPipeSideEffectResult>;
|
|
123
|
+
type StrictSideEffectValueExpected = number;
|
|
124
|
+
export type PipeSideEffectStrictValue = Expect<Equal<StrictSideEffectValue, StrictSideEffectValueExpected>>;
|
|
125
|
+
|
|
126
|
+
export const strictPipeSideEffectInput = strictPipeSideEffect(SideEffect.of(() => 'INPUT' as const));
|
|
127
|
+
|
|
128
|
+
type StrictSideEffectInputEffects = EffectUnion<typeof strictPipeSideEffectInput>;
|
|
129
|
+
type StrictSideEffectInputEffectsExpected = 'LOW' | 0 | 'INPUT';
|
|
130
|
+
export type PipeSideEffectStrictInputEffects = Expect<
|
|
131
|
+
Equal<StrictSideEffectInputEffects, StrictSideEffectInputEffectsExpected>
|
|
132
|
+
>;
|
|
133
|
+
|
|
134
|
+
export const strictPipeSideEffectSix = pipeSideEffectStrict(
|
|
135
|
+
(value: number) => value + 1,
|
|
136
|
+
(value) => (value > 2 ? value : SideEffect.of(() => 'LOW' as const)),
|
|
137
|
+
(value) => value + 1,
|
|
138
|
+
(value) => (value > 10 ? value : SideEffect.of(() => 'SMALL' as const)),
|
|
139
|
+
(value) => value * 2,
|
|
140
|
+
(value) => (value > 40 ? value : SideEffect.of(() => 0 as const))
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
export const strictPipeSideEffectSixResult = strictPipeSideEffectSix(1);
|
|
144
|
+
|
|
145
|
+
type StrictSixEffects = EffectUnion<typeof strictPipeSideEffectSixResult>;
|
|
146
|
+
type StrictSixEffectsExpected = 'LOW' | 'SMALL' | 0;
|
|
147
|
+
export type PipeSideEffectStrictSixEffects = Expect<Equal<StrictSixEffects, StrictSixEffectsExpected>>;
|
|
148
|
+
|
|
149
|
+
type StrictSixValue = ValueUnion<typeof strictPipeSideEffectSixResult>;
|
|
150
|
+
type StrictSixValueExpected = number;
|
|
151
|
+
export type PipeSideEffectStrictSixValue = Expect<Equal<StrictSixValue, StrictSixValueExpected>>;
|
|
152
|
+
|
|
153
|
+
export const strictPipeAsyncSideEffect = pipeAsyncSideEffectStrict(
|
|
154
|
+
(value: number) => value + 1,
|
|
155
|
+
async (value) => (value > 1 ? value : SideEffect.of(() => 'LOW' as const)),
|
|
156
|
+
(value) => (value > 2 ? value : SideEffect.of(() => 0 as const))
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
export const strictPipeAsyncSideEffectResult = strictPipeAsyncSideEffect(1);
|
|
160
|
+
|
|
161
|
+
type StrictPipeAsyncResolved = Awaited<typeof strictPipeAsyncSideEffectResult>;
|
|
162
|
+
type StrictPipeAsyncEffects = EffectUnion<StrictPipeAsyncResolved>;
|
|
163
|
+
type StrictPipeAsyncEffectsExpected = 'LOW' | 0;
|
|
164
|
+
export type PipeAsyncSideEffectStrictEffects = Expect<
|
|
165
|
+
Equal<StrictPipeAsyncEffects, StrictPipeAsyncEffectsExpected>
|
|
166
|
+
>;
|
|
167
|
+
|
|
168
|
+
type StrictPipeAsyncValue = ValueUnion<StrictPipeAsyncResolved>;
|
|
169
|
+
type StrictPipeAsyncValueExpected = number;
|
|
170
|
+
export type PipeAsyncSideEffectStrictValue = Expect<
|
|
171
|
+
Equal<StrictPipeAsyncValue, StrictPipeAsyncValueExpected>
|
|
172
|
+
>;
|
|
173
|
+
|
|
174
|
+
export const strictPipeAsyncSideEffectSix = pipeAsyncSideEffectStrict(
|
|
175
|
+
(value: number) => value + 1,
|
|
176
|
+
async (value) => (value > 2 ? value : SideEffect.of(() => 'LOW' as const)),
|
|
177
|
+
(value) => value + 1,
|
|
178
|
+
async (value) => (value > 10 ? value : SideEffect.of(() => 'SMALL' as const)),
|
|
179
|
+
(value) => value * 2,
|
|
180
|
+
async (value) => (value > 40 ? value : SideEffect.of(() => 0 as const))
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
export const strictPipeAsyncSideEffectSixResult = strictPipeAsyncSideEffectSix(1);
|
|
184
|
+
|
|
185
|
+
type StrictAsyncSixResolved = Awaited<typeof strictPipeAsyncSideEffectSixResult>;
|
|
186
|
+
type StrictAsyncSixEffects = EffectUnion<StrictAsyncSixResolved>;
|
|
187
|
+
type StrictAsyncSixEffectsExpected = 'LOW' | 'SMALL' | 0;
|
|
188
|
+
export type PipeAsyncSideEffectStrictSixEffects = Expect<
|
|
189
|
+
Equal<StrictAsyncSixEffects, StrictAsyncSixEffectsExpected>
|
|
190
|
+
>;
|
|
191
|
+
|
|
192
|
+
type StrictAsyncSixValue = ValueUnion<StrictAsyncSixResolved>;
|
|
193
|
+
type StrictAsyncSixValueExpected = number;
|
|
194
|
+
export type PipeAsyncSideEffectStrictSixValue = Expect<
|
|
195
|
+
Equal<StrictAsyncSixValue, StrictAsyncSixValueExpected>
|
|
196
|
+
>;
|
|
@@ -20,4 +20,12 @@ describe('pipeSideEffect', () => {
|
|
|
20
20
|
|
|
21
21
|
expect(fn(effect)).toBe(effect);
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
it('supports zero-arity starts', () => {
|
|
25
|
+
const start = () => 3;
|
|
26
|
+
const addOne = (n: number) => n + 1;
|
|
27
|
+
const fn = pipeSideEffect(start, addOne);
|
|
28
|
+
|
|
29
|
+
expect(fn()).toBe(4);
|
|
30
|
+
});
|
|
23
31
|
});
|
|
@@ -3,6 +3,7 @@ import SideEffect, { isSideEffect } from './sideEffect';
|
|
|
3
3
|
type MaybeSideEffect<T> = T | SideEffect<any>;
|
|
4
4
|
type NonSideEffect<T> = Exclude<T, SideEffect<any>>;
|
|
5
5
|
type UnaryFn<A, R> = (a: A) => MaybeSideEffect<R>;
|
|
6
|
+
type ZeroFn<R> = () => MaybeSideEffect<R>;
|
|
6
7
|
type PipeInput<Fns extends UnaryFn<any, any>[]> = Fns extends [UnaryFn<infer A, any>, ...UnaryFn<any, any>[]]
|
|
7
8
|
? A
|
|
8
9
|
: never;
|
|
@@ -18,6 +19,30 @@ type PipeSideEffect<Fns extends UnaryFn<any, any>[]> = (
|
|
|
18
19
|
input: PipeInput<Fns> | SideEffect<any>
|
|
19
20
|
) => Resolve<PipeOutput<Fns>>;
|
|
20
21
|
|
|
22
|
+
function pipeSideEffect<R>(ab: ZeroFn<R>): () => MaybeSideEffect<R>;
|
|
23
|
+
function pipeSideEffect<B, R>(ab: ZeroFn<B>, bc: UnaryFn<B, R>): () => MaybeSideEffect<R>;
|
|
24
|
+
function pipeSideEffect<B, C, R>(ab: ZeroFn<B>, bc: UnaryFn<B, C>, cd: UnaryFn<C, R>): () => MaybeSideEffect<R>;
|
|
25
|
+
function pipeSideEffect<B, C, D, R>(
|
|
26
|
+
ab: ZeroFn<B>,
|
|
27
|
+
bc: UnaryFn<B, C>,
|
|
28
|
+
cd: UnaryFn<C, D>,
|
|
29
|
+
de: UnaryFn<D, R>
|
|
30
|
+
): () => MaybeSideEffect<R>;
|
|
31
|
+
function pipeSideEffect<B, C, D, E, R>(
|
|
32
|
+
ab: ZeroFn<B>,
|
|
33
|
+
bc: UnaryFn<B, C>,
|
|
34
|
+
cd: UnaryFn<C, D>,
|
|
35
|
+
de: UnaryFn<D, E>,
|
|
36
|
+
ef: UnaryFn<E, R>
|
|
37
|
+
): () => MaybeSideEffect<R>;
|
|
38
|
+
function pipeSideEffect<B, C, D, E, F, R>(
|
|
39
|
+
ab: ZeroFn<B>,
|
|
40
|
+
bc: UnaryFn<B, C>,
|
|
41
|
+
cd: UnaryFn<C, D>,
|
|
42
|
+
de: UnaryFn<D, E>,
|
|
43
|
+
ef: UnaryFn<E, F>,
|
|
44
|
+
fg: UnaryFn<F, R>
|
|
45
|
+
): () => MaybeSideEffect<R>;
|
|
21
46
|
function pipeSideEffect<A, R>(ab: UnaryFn<A, R>): (a: A | SideEffect<any>) => MaybeSideEffect<R>;
|
|
22
47
|
function pipeSideEffect<A, B, R>(
|
|
23
48
|
ab: UnaryFn<A, B>,
|
|
@@ -41,6 +66,14 @@ function pipeSideEffect<A, B, C, D, E, R>(
|
|
|
41
66
|
de: UnaryFn<D, E>,
|
|
42
67
|
ef: UnaryFn<E, R>
|
|
43
68
|
): (a: A | SideEffect<any>) => MaybeSideEffect<R>;
|
|
69
|
+
function pipeSideEffect<A, B, C, D, E, F, R>(
|
|
70
|
+
ab: UnaryFn<A, B>,
|
|
71
|
+
bc: UnaryFn<B, C>,
|
|
72
|
+
cd: UnaryFn<C, D>,
|
|
73
|
+
de: UnaryFn<D, E>,
|
|
74
|
+
ef: UnaryFn<E, F>,
|
|
75
|
+
fg: UnaryFn<F, R>
|
|
76
|
+
): (a: A | SideEffect<any>) => MaybeSideEffect<R>;
|
|
44
77
|
|
|
45
78
|
function pipeSideEffect<Fns extends [UnaryFn<any, any>, ...UnaryFn<any, any>[]]>(...funcs: Fns): PipeSideEffect<Fns>;
|
|
46
79
|
function pipeSideEffect(...funcs: Array<UnaryFn<any, any>>): (input: any) => any;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import pipeSideEffectStrict from './pipeSideEffectStrict';
|
|
3
|
+
import SideEffect from './sideEffect';
|
|
4
|
+
|
|
5
|
+
describe('pipeSideEffectStrict', () => {
|
|
6
|
+
it('short-circuits when SideEffect is returned', () => {
|
|
7
|
+
const effect = new SideEffect(() => 'effect');
|
|
8
|
+
const stop = (_value: number) => effect;
|
|
9
|
+
const after = (_value: number) => _value + 1;
|
|
10
|
+
|
|
11
|
+
const fn = pipeSideEffectStrict((n: number) => n + 1, stop, after);
|
|
12
|
+
const result = fn(1);
|
|
13
|
+
|
|
14
|
+
expect(result).toBe(effect);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns SideEffect inputs without executing the pipeline', () => {
|
|
18
|
+
const effect = new SideEffect(() => 'effect');
|
|
19
|
+
const fn = pipeSideEffectStrict((n: number) => n + 1);
|
|
20
|
+
|
|
21
|
+
expect(fn(effect)).toBe(effect);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import SideEffect, { isSideEffect } from './sideEffect';
|
|
2
|
+
|
|
3
|
+
type AnyFn = (...args: any[]) => any;
|
|
4
|
+
type MaybeSideEffect<T, E> = T | SideEffect<E>;
|
|
5
|
+
type NonSideEffect<T> = Exclude<T, SideEffect<any>>;
|
|
6
|
+
type UnaryFn<A, R> = (a: A) => R;
|
|
7
|
+
type ZeroFn<R> = () => R;
|
|
8
|
+
|
|
9
|
+
type EffectOfReturn<R> = R extends SideEffect<infer E> ? E : never;
|
|
10
|
+
type EffectOfFn<F> = F extends (...args: any[]) => infer R ? EffectOfReturn<R> : never;
|
|
11
|
+
type EffectsOf<Fns extends AnyFn[]> = EffectOfFn<Fns[number]>;
|
|
12
|
+
|
|
13
|
+
type PipeValueOutputUnary<Fns extends UnaryFn<any, any>[]> = Fns extends [UnaryFn<any, infer R>]
|
|
14
|
+
? NonSideEffect<R>
|
|
15
|
+
: Fns extends [UnaryFn<any, infer R>, ...infer Rest]
|
|
16
|
+
? Rest extends [UnaryFn<NonSideEffect<R>, any>, ...UnaryFn<any, any>[]]
|
|
17
|
+
? PipeValueOutputUnary<Rest>
|
|
18
|
+
: never
|
|
19
|
+
: never;
|
|
20
|
+
|
|
21
|
+
type PipeValueOutputStrict<Fns extends [AnyFn, ...AnyFn[]]> =
|
|
22
|
+
Fns extends [ZeroFn<infer R>]
|
|
23
|
+
? NonSideEffect<R>
|
|
24
|
+
: Fns extends [ZeroFn<infer R>, ...infer Rest]
|
|
25
|
+
? Rest extends [UnaryFn<NonSideEffect<R>, any>, ...UnaryFn<any, any>[]]
|
|
26
|
+
? PipeValueOutputUnary<Rest>
|
|
27
|
+
: never
|
|
28
|
+
: Fns extends [UnaryFn<any, any>, ...UnaryFn<any, any>[]]
|
|
29
|
+
? PipeValueOutputUnary<Fns>
|
|
30
|
+
: never;
|
|
31
|
+
|
|
32
|
+
type PipeInputStrict<Fns extends [AnyFn, ...AnyFn[]]> = Fns extends [ZeroFn<any>, ...AnyFn[]]
|
|
33
|
+
? never
|
|
34
|
+
: Fns extends [UnaryFn<infer A, any>, ...AnyFn[]]
|
|
35
|
+
? A
|
|
36
|
+
: never;
|
|
37
|
+
|
|
38
|
+
type Resolve<T> = T extends infer R ? R : never;
|
|
39
|
+
|
|
40
|
+
type PipeSideEffectStrictUnary<Fns extends [AnyFn, ...AnyFn[]]> = {
|
|
41
|
+
(input: PipeInputStrict<Fns>): Resolve<MaybeSideEffect<PipeValueOutputStrict<Fns>, EffectsOf<Fns>>>;
|
|
42
|
+
<EIn>(
|
|
43
|
+
input: PipeInputStrict<Fns> | SideEffect<EIn>
|
|
44
|
+
): Resolve<MaybeSideEffect<PipeValueOutputStrict<Fns>, EffectsOf<Fns> | EIn>>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type PipeSideEffectStrict<Fns extends [AnyFn, ...AnyFn[]]> = Fns extends [ZeroFn<any>, ...AnyFn[]]
|
|
48
|
+
? () => Resolve<MaybeSideEffect<PipeValueOutputStrict<Fns>, EffectsOf<Fns>>>
|
|
49
|
+
: PipeSideEffectStrictUnary<Fns>;
|
|
50
|
+
|
|
51
|
+
function pipeSideEffectStrict<R>(ab: ZeroFn<R>): PipeSideEffectStrict<[ZeroFn<R>]>;
|
|
52
|
+
function pipeSideEffectStrict<B, R>(
|
|
53
|
+
ab: ZeroFn<B>,
|
|
54
|
+
bc: UnaryFn<NonSideEffect<B>, R>
|
|
55
|
+
): PipeSideEffectStrict<[ZeroFn<B>, UnaryFn<NonSideEffect<B>, R>]>;
|
|
56
|
+
function pipeSideEffectStrict<B, C, R>(
|
|
57
|
+
ab: ZeroFn<B>,
|
|
58
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
59
|
+
cd: UnaryFn<NonSideEffect<C>, R>
|
|
60
|
+
): PipeSideEffectStrict<[ZeroFn<B>, UnaryFn<NonSideEffect<B>, C>, UnaryFn<NonSideEffect<C>, R>]>;
|
|
61
|
+
function pipeSideEffectStrict<B, C, D, R>(
|
|
62
|
+
ab: ZeroFn<B>,
|
|
63
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
64
|
+
cd: UnaryFn<NonSideEffect<C>, D>,
|
|
65
|
+
de: UnaryFn<NonSideEffect<D>, R>
|
|
66
|
+
): PipeSideEffectStrict<[
|
|
67
|
+
ZeroFn<B>,
|
|
68
|
+
UnaryFn<NonSideEffect<B>, C>,
|
|
69
|
+
UnaryFn<NonSideEffect<C>, D>,
|
|
70
|
+
UnaryFn<NonSideEffect<D>, R>
|
|
71
|
+
]>;
|
|
72
|
+
function pipeSideEffectStrict<B, C, D, E, R>(
|
|
73
|
+
ab: ZeroFn<B>,
|
|
74
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
75
|
+
cd: UnaryFn<NonSideEffect<C>, D>,
|
|
76
|
+
de: UnaryFn<NonSideEffect<D>, E>,
|
|
77
|
+
ef: UnaryFn<NonSideEffect<E>, R>
|
|
78
|
+
): PipeSideEffectStrict<[
|
|
79
|
+
ZeroFn<B>,
|
|
80
|
+
UnaryFn<NonSideEffect<B>, C>,
|
|
81
|
+
UnaryFn<NonSideEffect<C>, D>,
|
|
82
|
+
UnaryFn<NonSideEffect<D>, E>,
|
|
83
|
+
UnaryFn<NonSideEffect<E>, R>
|
|
84
|
+
]>;
|
|
85
|
+
function pipeSideEffectStrict<B, C, D, E, F, R>(
|
|
86
|
+
ab: ZeroFn<B>,
|
|
87
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
88
|
+
cd: UnaryFn<NonSideEffect<C>, D>,
|
|
89
|
+
de: UnaryFn<NonSideEffect<D>, E>,
|
|
90
|
+
ef: UnaryFn<NonSideEffect<E>, F>,
|
|
91
|
+
fg: UnaryFn<NonSideEffect<F>, R>
|
|
92
|
+
): PipeSideEffectStrict<[
|
|
93
|
+
ZeroFn<B>,
|
|
94
|
+
UnaryFn<NonSideEffect<B>, C>,
|
|
95
|
+
UnaryFn<NonSideEffect<C>, D>,
|
|
96
|
+
UnaryFn<NonSideEffect<D>, E>,
|
|
97
|
+
UnaryFn<NonSideEffect<E>, F>,
|
|
98
|
+
UnaryFn<NonSideEffect<F>, R>
|
|
99
|
+
]>;
|
|
100
|
+
function pipeSideEffectStrict<A, R>(ab: UnaryFn<A, R>): PipeSideEffectStrict<[UnaryFn<A, R>]>;
|
|
101
|
+
function pipeSideEffectStrict<A, B, R>(
|
|
102
|
+
ab: UnaryFn<A, B>,
|
|
103
|
+
bc: UnaryFn<NonSideEffect<B>, R>
|
|
104
|
+
): PipeSideEffectStrict<[UnaryFn<A, B>, UnaryFn<NonSideEffect<B>, R>]>;
|
|
105
|
+
function pipeSideEffectStrict<A, B, C, R>(
|
|
106
|
+
ab: UnaryFn<A, B>,
|
|
107
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
108
|
+
cd: UnaryFn<NonSideEffect<C>, R>
|
|
109
|
+
): PipeSideEffectStrict<[UnaryFn<A, B>, UnaryFn<NonSideEffect<B>, C>, UnaryFn<NonSideEffect<C>, R>]>;
|
|
110
|
+
function pipeSideEffectStrict<A, B, C, D, R>(
|
|
111
|
+
ab: UnaryFn<A, B>,
|
|
112
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
113
|
+
cd: UnaryFn<NonSideEffect<C>, D>,
|
|
114
|
+
de: UnaryFn<NonSideEffect<D>, R>
|
|
115
|
+
): PipeSideEffectStrict<[
|
|
116
|
+
UnaryFn<A, B>,
|
|
117
|
+
UnaryFn<NonSideEffect<B>, C>,
|
|
118
|
+
UnaryFn<NonSideEffect<C>, D>,
|
|
119
|
+
UnaryFn<NonSideEffect<D>, R>
|
|
120
|
+
]>;
|
|
121
|
+
function pipeSideEffectStrict<A, B, C, D, E, R>(
|
|
122
|
+
ab: UnaryFn<A, B>,
|
|
123
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
124
|
+
cd: UnaryFn<NonSideEffect<C>, D>,
|
|
125
|
+
de: UnaryFn<NonSideEffect<D>, E>,
|
|
126
|
+
ef: UnaryFn<NonSideEffect<E>, R>
|
|
127
|
+
): PipeSideEffectStrict<[
|
|
128
|
+
UnaryFn<A, B>,
|
|
129
|
+
UnaryFn<NonSideEffect<B>, C>,
|
|
130
|
+
UnaryFn<NonSideEffect<C>, D>,
|
|
131
|
+
UnaryFn<NonSideEffect<D>, E>,
|
|
132
|
+
UnaryFn<NonSideEffect<E>, R>
|
|
133
|
+
]>;
|
|
134
|
+
function pipeSideEffectStrict<A, B, C, D, E, F, R>(
|
|
135
|
+
ab: UnaryFn<A, B>,
|
|
136
|
+
bc: UnaryFn<NonSideEffect<B>, C>,
|
|
137
|
+
cd: UnaryFn<NonSideEffect<C>, D>,
|
|
138
|
+
de: UnaryFn<NonSideEffect<D>, E>,
|
|
139
|
+
ef: UnaryFn<NonSideEffect<E>, F>,
|
|
140
|
+
fg: UnaryFn<NonSideEffect<F>, R>
|
|
141
|
+
): PipeSideEffectStrict<[
|
|
142
|
+
UnaryFn<A, B>,
|
|
143
|
+
UnaryFn<NonSideEffect<B>, C>,
|
|
144
|
+
UnaryFn<NonSideEffect<C>, D>,
|
|
145
|
+
UnaryFn<NonSideEffect<D>, E>,
|
|
146
|
+
UnaryFn<NonSideEffect<E>, F>,
|
|
147
|
+
UnaryFn<NonSideEffect<F>, R>
|
|
148
|
+
]>;
|
|
149
|
+
|
|
150
|
+
function pipeSideEffectStrict<Fns extends [AnyFn, ...AnyFn[]]>(...funcs: Fns): PipeSideEffectStrict<Fns>;
|
|
151
|
+
function pipeSideEffectStrict(...funcs: Array<(input: any) => any>) {
|
|
152
|
+
return (init?: any) => {
|
|
153
|
+
let acc = init;
|
|
154
|
+
for (const fn of funcs) {
|
|
155
|
+
if (isSideEffect(acc)) {
|
|
156
|
+
return acc;
|
|
157
|
+
}
|
|
158
|
+
acc = fn(acc);
|
|
159
|
+
}
|
|
160
|
+
return acc;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default pipeSideEffectStrict;
|
|
@@ -68,4 +68,22 @@ describe('control (curried)', () => {
|
|
|
68
68
|
expect(applyWhen(5)).toBe(5);
|
|
69
69
|
expect(applyWhen(20)).toBe(21);
|
|
70
70
|
});
|
|
71
|
+
|
|
72
|
+
it('infers types across curried stages', () => {
|
|
73
|
+
const predicate = (value: number) => value > 0;
|
|
74
|
+
const onTrue = (value: number) => value * 2;
|
|
75
|
+
const onFalse = (value: number) => value - 2;
|
|
76
|
+
|
|
77
|
+
const choose = ifElse(predicate)(onTrue)(onFalse);
|
|
78
|
+
expect(choose(3)).toBe(6);
|
|
79
|
+
expect(choose(-1)).toBe(-3);
|
|
80
|
+
|
|
81
|
+
const applyWhen = when(predicate)((value) => value + 1);
|
|
82
|
+
const applyUnless = unless(predicate)((value) => value + 1);
|
|
83
|
+
|
|
84
|
+
expect(applyWhen(2)).toBe(3);
|
|
85
|
+
expect(applyWhen(-2)).toBe(-2);
|
|
86
|
+
expect(applyUnless(2)).toBe(2);
|
|
87
|
+
expect(applyUnless(-2)).toBe(-1);
|
|
88
|
+
});
|
|
71
89
|
});
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import curry from '../composition/curry';
|
|
2
2
|
|
|
3
|
+
type NoInfer<T> = [T][T extends any ? 0 : never];
|
|
4
|
+
|
|
3
5
|
type IfElse = {
|
|
4
6
|
<T, RTrue, RFalse>(
|
|
5
7
|
...args: [predicate: (value: T) => boolean]
|
|
6
8
|
): (onTrue: (value: T) => RTrue) => (onFalse: (value: T) => RFalse) => (value: T) => RTrue | RFalse;
|
|
7
9
|
<T, RTrue, RFalse>(
|
|
8
|
-
...args: [predicate: (value: T) => boolean, onTrue: (value: T) => RTrue]
|
|
10
|
+
...args: [predicate: (value: NoInfer<T>) => boolean, onTrue: (value: T) => RTrue]
|
|
9
11
|
): (onFalse: (value: T) => RFalse) => (value: T) => RTrue | RFalse;
|
|
10
12
|
<T, RTrue, RFalse>(
|
|
11
13
|
...args: [
|
|
12
|
-
predicate: (value: T) => boolean,
|
|
14
|
+
predicate: (value: NoInfer<T>) => boolean,
|
|
13
15
|
onTrue: (value: T) => RTrue,
|
|
14
16
|
onFalse: (value: T) => RFalse
|
|
15
17
|
]
|
|
16
18
|
): (value: T) => RTrue | RFalse;
|
|
17
19
|
<T, RTrue, RFalse>(
|
|
18
20
|
...args: [
|
|
19
|
-
predicate: (value: T) => boolean,
|
|
21
|
+
predicate: (value: NoInfer<T>) => boolean,
|
|
20
22
|
onTrue: (value: T) => RTrue,
|
|
21
23
|
onFalse: (value: T) => RFalse,
|
|
22
24
|
value: T
|
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
import curry from '../composition/curry';
|
|
2
2
|
|
|
3
|
+
type NoInfer<T> = [T][T extends any ? 0 : never];
|
|
3
4
|
type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
|
|
4
5
|
|
|
5
6
|
type Unless = {
|
|
6
7
|
<T>(...args: [predicate: (value: Widen<T>) => boolean]): (
|
|
7
8
|
fn: (value: Widen<T>) => Widen<T>
|
|
8
9
|
) => (value: Widen<T>) => Widen<T>;
|
|
10
|
+
<T, R>(...args: [predicate: (value: Widen<T>) => boolean]): (
|
|
11
|
+
fn: (value: Widen<T>) => R
|
|
12
|
+
) => (value: Widen<T>) => Widen<T> | R;
|
|
9
13
|
<T>(
|
|
10
|
-
...args: [predicate: (value: Widen<T
|
|
14
|
+
...args: [predicate: (value: NoInfer<Widen<T>>) => boolean, fn: (value: Widen<T>) => Widen<T>]
|
|
11
15
|
): (value: Widen<T>) => Widen<T>;
|
|
16
|
+
<T, R>(
|
|
17
|
+
...args: [predicate: (value: NoInfer<Widen<T>>) => boolean, fn: (value: Widen<T>) => R]
|
|
18
|
+
): (value: Widen<T>) => Widen<T> | R;
|
|
12
19
|
<T>(
|
|
13
|
-
...args: [
|
|
20
|
+
...args: [
|
|
21
|
+
predicate: (value: NoInfer<Widen<T>>) => boolean,
|
|
22
|
+
fn: (value: Widen<T>) => Widen<T>,
|
|
23
|
+
value: Widen<T>
|
|
24
|
+
]
|
|
14
25
|
): Widen<T>;
|
|
26
|
+
<T, R>(
|
|
27
|
+
...args: [
|
|
28
|
+
predicate: (value: NoInfer<Widen<T>>) => boolean,
|
|
29
|
+
fn: (value: Widen<T>) => R,
|
|
30
|
+
value: Widen<T>
|
|
31
|
+
]
|
|
32
|
+
): Widen<T> | R;
|
|
15
33
|
};
|
|
16
34
|
|
|
17
35
|
/**
|
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
import curry from '../composition/curry';
|
|
2
2
|
|
|
3
|
+
type NoInfer<T> = [T][T extends any ? 0 : never];
|
|
3
4
|
type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
|
|
4
5
|
|
|
5
6
|
type When = {
|
|
6
7
|
<T>(...args: [predicate: (value: Widen<T>) => boolean]): (
|
|
7
8
|
fn: (value: Widen<T>) => Widen<T>
|
|
8
9
|
) => (value: Widen<T>) => Widen<T>;
|
|
10
|
+
<T, R>(...args: [predicate: (value: Widen<T>) => boolean]): (
|
|
11
|
+
fn: (value: Widen<T>) => R
|
|
12
|
+
) => (value: Widen<T>) => Widen<T> | R;
|
|
9
13
|
<T>(
|
|
10
|
-
...args: [predicate: (value: Widen<T
|
|
14
|
+
...args: [predicate: (value: NoInfer<Widen<T>>) => boolean, fn: (value: Widen<T>) => Widen<T>]
|
|
11
15
|
): (value: Widen<T>) => Widen<T>;
|
|
16
|
+
<T, R>(
|
|
17
|
+
...args: [predicate: (value: NoInfer<Widen<T>>) => boolean, fn: (value: Widen<T>) => R]
|
|
18
|
+
): (value: Widen<T>) => Widen<T> | R;
|
|
12
19
|
<T>(
|
|
13
|
-
...args: [
|
|
20
|
+
...args: [
|
|
21
|
+
predicate: (value: NoInfer<Widen<T>>) => boolean,
|
|
22
|
+
fn: (value: Widen<T>) => Widen<T>,
|
|
23
|
+
value: Widen<T>
|
|
24
|
+
]
|
|
14
25
|
): Widen<T>;
|
|
26
|
+
<T, R>(
|
|
27
|
+
...args: [
|
|
28
|
+
predicate: (value: NoInfer<Widen<T>>) => boolean,
|
|
29
|
+
fn: (value: Widen<T>) => R,
|
|
30
|
+
value: Widen<T>
|
|
31
|
+
]
|
|
32
|
+
): Widen<T> | R;
|
|
15
33
|
};
|
|
16
34
|
|
|
17
35
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import propStrict from './propStrict';
|
|
3
|
+
|
|
4
|
+
describe('propStrict', () => {
|
|
5
|
+
it('gets a property', () => {
|
|
6
|
+
const user = { id: 1, name: 'A' };
|
|
7
|
+
expect(propStrict('name', user)).toBe('A');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('supports currying', () => {
|
|
11
|
+
const user = { name: 'A' };
|
|
12
|
+
const getName = propStrict('name');
|
|
13
|
+
expect(getName(user)).toBe('A');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('throws when property is missing', () => {
|
|
17
|
+
type User = { name: string; age?: number };
|
|
18
|
+
const user: User = { name: 'A' };
|
|
19
|
+
expect(() => propStrict('age', user)).toThrow(/propStrict/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('throws when value is null or undefined', () => {
|
|
23
|
+
const nullUser: { name: string | null } = { name: null };
|
|
24
|
+
expect(() => propStrict('name', nullUser)).toThrow(/propStrict/);
|
|
25
|
+
|
|
26
|
+
const undefinedUser: { name: string | undefined } = { name: undefined };
|
|
27
|
+
expect(() => propStrict('name', undefinedUser)).toThrow(/propStrict/);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import curry from '../composition/curry';
|
|
2
|
+
|
|
3
|
+
type PropStrict = {
|
|
4
|
+
<K extends PropertyKey>(...args: [key: K]): <T extends Record<K, unknown>>(obj: T) => NonNullable<T[K]>;
|
|
5
|
+
<T, K extends keyof T>(...args: [key: K, obj: T]): NonNullable<T[K]>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* propStrict - strict property access (throws on null/undefined)
|
|
10
|
+
*/
|
|
11
|
+
function propStrict<T, K extends keyof T = keyof T>(key: K, obj: T): NonNullable<T[K]> {
|
|
12
|
+
const value = obj?.[key];
|
|
13
|
+
if (value == null) {
|
|
14
|
+
throw new Error(`propStrict: "${String(key)}" is null or undefined`);
|
|
15
|
+
}
|
|
16
|
+
return value as NonNullable<T[K]>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const curriedPropStrict = curry(propStrict) as unknown as PropStrict;
|
|
20
|
+
export default curriedPropStrict;
|