fp-pack 0.1.0 → 0.2.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.
Files changed (67) hide show
  1. package/README.md +27 -7
  2. package/dist/fp-pack.umd.js +1 -1
  3. package/dist/fp-pack.umd.js.map +1 -1
  4. package/dist/implement/async/index.d.ts +1 -0
  5. package/dist/implement/async/index.d.ts.map +1 -1
  6. package/dist/implement/async/pipeAsyncSideEffectStrict.d.ts +57 -0
  7. package/dist/implement/async/pipeAsyncSideEffectStrict.d.ts.map +1 -0
  8. package/dist/implement/async/pipeAsyncSideEffectStrict.mjs +16 -0
  9. package/dist/implement/async/pipeAsyncSideEffectStrict.mjs.map +1 -0
  10. package/dist/implement/composition/from.d.ts +6 -0
  11. package/dist/implement/composition/from.d.ts.map +1 -0
  12. package/dist/implement/composition/from.mjs +7 -0
  13. package/dist/implement/composition/from.mjs.map +1 -0
  14. package/dist/implement/composition/index.d.ts +2 -0
  15. package/dist/implement/composition/index.d.ts.map +1 -1
  16. package/dist/implement/composition/pipe.d.ts +6 -0
  17. package/dist/implement/composition/pipe.d.ts.map +1 -1
  18. package/dist/implement/composition/pipe.mjs.map +1 -1
  19. package/dist/implement/composition/pipe.type-test.d.ts +29 -0
  20. package/dist/implement/composition/pipe.type-test.d.ts.map +1 -1
  21. package/dist/implement/composition/pipeSideEffect.d.ts +6 -0
  22. package/dist/implement/composition/pipeSideEffect.d.ts.map +1 -1
  23. package/dist/implement/composition/pipeSideEffect.mjs.map +1 -1
  24. package/dist/implement/composition/pipeSideEffectStrict.d.ts +53 -0
  25. package/dist/implement/composition/pipeSideEffectStrict.d.ts.map +1 -0
  26. package/dist/implement/composition/pipeSideEffectStrict.mjs +16 -0
  27. package/dist/implement/composition/pipeSideEffectStrict.mjs.map +1 -0
  28. package/dist/implement/control/ifElse.d.ts +4 -3
  29. package/dist/implement/control/ifElse.d.ts.map +1 -1
  30. package/dist/implement/control/ifElse.mjs.map +1 -1
  31. package/dist/implement/control/unless.d.ts +14 -2
  32. package/dist/implement/control/unless.d.ts.map +1 -1
  33. package/dist/implement/control/unless.mjs.map +1 -1
  34. package/dist/implement/control/when.d.ts +14 -2
  35. package/dist/implement/control/when.d.ts.map +1 -1
  36. package/dist/implement/control/when.mjs.map +1 -1
  37. package/dist/implement/object/index.d.ts +1 -0
  38. package/dist/implement/object/index.d.ts.map +1 -1
  39. package/dist/implement/object/propStrict.d.ts +7 -0
  40. package/dist/implement/object/propStrict.d.ts.map +1 -0
  41. package/dist/implement/object/propStrict.mjs +12 -0
  42. package/dist/implement/object/propStrict.mjs.map +1 -0
  43. package/dist/index.mjs +248 -240
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/skills/fp-pack/SKILL.md +1699 -0
  46. package/dist/skills/fp-pack.md +56 -8
  47. package/package.json +1 -1
  48. package/src/implement/async/index.ts +1 -0
  49. package/src/implement/async/pipeAsyncSideEffectStrict.test.ts +23 -0
  50. package/src/implement/async/pipeAsyncSideEffectStrict.ts +140 -0
  51. package/src/implement/composition/from.test.ts +16 -0
  52. package/src/implement/composition/from.ts +8 -0
  53. package/src/implement/composition/index.ts +2 -0
  54. package/src/implement/composition/pipe.test.ts +8 -0
  55. package/src/implement/composition/pipe.ts +12 -0
  56. package/src/implement/composition/pipe.type-test.ts +49 -0
  57. package/src/implement/composition/pipeSideEffect.test.ts +8 -0
  58. package/src/implement/composition/pipeSideEffect.ts +17 -0
  59. package/src/implement/composition/pipeSideEffectStrict.test.ts +23 -0
  60. package/src/implement/composition/pipeSideEffectStrict.ts +134 -0
  61. package/src/implement/control/curried.test.ts +18 -0
  62. package/src/implement/control/ifElse.ts +5 -3
  63. package/src/implement/control/unless.ts +20 -2
  64. package/src/implement/control/when.ts +20 -2
  65. package/src/implement/object/index.ts +1 -0
  66. package/src/implement/object/propStrict.test.ts +29 -0
  67. package/src/implement/object/propStrict.ts +20 -0
@@ -0,0 +1,134 @@
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<A, R>(ab: UnaryFn<A, R>): PipeSideEffectStrict<[UnaryFn<A, R>]>;
86
+ function pipeSideEffectStrict<A, B, R>(
87
+ ab: UnaryFn<A, B>,
88
+ bc: UnaryFn<NonSideEffect<B>, R>
89
+ ): PipeSideEffectStrict<[UnaryFn<A, B>, UnaryFn<NonSideEffect<B>, R>]>;
90
+ function pipeSideEffectStrict<A, B, C, R>(
91
+ ab: UnaryFn<A, B>,
92
+ bc: UnaryFn<NonSideEffect<B>, C>,
93
+ cd: UnaryFn<NonSideEffect<C>, R>
94
+ ): PipeSideEffectStrict<[UnaryFn<A, B>, UnaryFn<NonSideEffect<B>, C>, UnaryFn<NonSideEffect<C>, R>]>;
95
+ function pipeSideEffectStrict<A, B, C, D, R>(
96
+ ab: UnaryFn<A, B>,
97
+ bc: UnaryFn<NonSideEffect<B>, C>,
98
+ cd: UnaryFn<NonSideEffect<C>, D>,
99
+ de: UnaryFn<NonSideEffect<D>, R>
100
+ ): PipeSideEffectStrict<[
101
+ UnaryFn<A, B>,
102
+ UnaryFn<NonSideEffect<B>, C>,
103
+ UnaryFn<NonSideEffect<C>, D>,
104
+ UnaryFn<NonSideEffect<D>, R>
105
+ ]>;
106
+ function pipeSideEffectStrict<A, B, C, D, E, R>(
107
+ ab: UnaryFn<A, B>,
108
+ bc: UnaryFn<NonSideEffect<B>, C>,
109
+ cd: UnaryFn<NonSideEffect<C>, D>,
110
+ de: UnaryFn<NonSideEffect<D>, E>,
111
+ ef: UnaryFn<NonSideEffect<E>, R>
112
+ ): PipeSideEffectStrict<[
113
+ UnaryFn<A, B>,
114
+ UnaryFn<NonSideEffect<B>, C>,
115
+ UnaryFn<NonSideEffect<C>, D>,
116
+ UnaryFn<NonSideEffect<D>, E>,
117
+ UnaryFn<NonSideEffect<E>, R>
118
+ ]>;
119
+
120
+ function pipeSideEffectStrict<Fns extends [AnyFn, ...AnyFn[]]>(...funcs: Fns): PipeSideEffectStrict<Fns>;
121
+ function pipeSideEffectStrict(...funcs: Array<(input: any) => any>) {
122
+ return (init?: any) => {
123
+ let acc = init;
124
+ for (const fn of funcs) {
125
+ if (isSideEffect(acc)) {
126
+ return acc;
127
+ }
128
+ acc = fn(acc);
129
+ }
130
+ return acc;
131
+ };
132
+ }
133
+
134
+ 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>) => boolean, fn: (value: Widen<T>) => 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: [predicate: (value: Widen<T>) => boolean, fn: (value: Widen<T>) => Widen<T>, value: Widen<T>]
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>) => boolean, fn: (value: Widen<T>) => 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: [predicate: (value: Widen<T>) => boolean, fn: (value: Widen<T>) => Widen<T>, value: Widen<T>]
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,4 +1,5 @@
1
1
  export { default as prop } from './prop';
2
+ export { default as propStrict } from './propStrict';
2
3
  export { default as propOr } from './propOr';
3
4
  export { default as path } from './path';
4
5
  export { default as pathOr } from './pathOr';
@@ -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;