reliant-type 2.1.3 → 2.1.5

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.
@@ -705,9 +705,52 @@ export class FieldPrecompilers {
705
705
 
706
706
  /**
707
707
  * Precompile function field types (fn(() => void), etc.)
708
- * ENHANCED: Implements function wrapping to validate arguments and return values at runtime.
708
+ * ENHANCED: Parses signature once at compilation time and enforces arity check at validation time.
709
709
  */
710
710
  static precompileFunction(functionType: string): CompiledFieldValidator {
711
+ // 1. Static Parsing (Done once)
712
+ const type = functionType.endsWith("?")
713
+ ? functionType.slice(0, -1)
714
+ : functionType;
715
+ const signature = type.slice(3, -1); // remove fn( and )
716
+ const arrowIndex = signature.lastIndexOf("=>");
717
+
718
+ let argDefs: string[] = [];
719
+ let returnPart = "any";
720
+ let minRequiredArgs = 0;
721
+ let hasRest = false;
722
+
723
+ if (arrowIndex !== -1) {
724
+ const argsPart = signature.slice(0, arrowIndex).trim();
725
+ returnPart = signature.slice(arrowIndex + 2).trim();
726
+
727
+ const cleanArgsPart =
728
+ argsPart.startsWith("(") && argsPart.endsWith(")")
729
+ ? argsPart.slice(1, -1)
730
+ : argsPart;
731
+
732
+ if (cleanArgsPart && cleanArgsPart !== "()") {
733
+ argDefs = cleanArgsPart.split(",").map((s) => s.trim());
734
+
735
+ // Calculate minimum required arguments
736
+ for (const def of argDefs) {
737
+ const parts = def.split(":");
738
+ const namePart = parts[0].trim();
739
+ let typePart = parts.slice(1).join(":").trim() || "any";
740
+ if (typePart === "TYPE") typePart = "any";
741
+
742
+ const isRest = namePart.startsWith("...");
743
+ const isOptional = namePart.endsWith("?") || typePart.endsWith("?");
744
+
745
+ if (isRest) hasRest = true;
746
+ if (!isRest && !isOptional) {
747
+ minRequiredArgs++;
748
+ }
749
+ }
750
+ }
751
+ }
752
+
753
+ // 2. Validator (Runs on safeParse)
711
754
  const validator = (value: any): SchemaValidationResult => {
712
755
  if (typeof value !== "function") {
713
756
  return {
@@ -718,80 +761,69 @@ export class FieldPrecompilers {
718
761
  };
719
762
  }
720
763
 
721
- // Extract signature: fn((args) => returnType)
722
- const type = functionType.endsWith("?")
723
- ? functionType.slice(0, -1)
724
- : functionType;
725
- const signature = type.slice(3, -1); // remove fn( and )
726
-
727
- const arrowIndex = signature.lastIndexOf("=>");
728
- if (arrowIndex === -1) {
729
- return { success: true, errors: [], warnings: [], data: value };
764
+ // ARITY CHECK: Validate argument count immediately
765
+ // Note: value.length excludes rest parameters and default values
766
+ if (!hasRest && value.length < minRequiredArgs) {
767
+ return {
768
+ success: false,
769
+ errors: [
770
+ {
771
+ message: `Function signature mismatch. Expected at least ${minRequiredArgs} arguments, but received a function with ${value.length}.`,
772
+ path: [],
773
+ code: "INVALID_FUNCTION_ARITY",
774
+ expected: `${minRequiredArgs} arguments`,
775
+ received: `${value.length} arguments`,
776
+ receivedType: "function",
777
+ },
778
+ ],
779
+ warnings: [],
780
+ data: undefined,
781
+ };
730
782
  }
731
783
 
732
- const argsPart = signature.slice(0, arrowIndex).trim();
733
- const returnPart = signature.slice(arrowIndex + 2).trim();
734
-
735
- // Clean argsPart: remove outer parentheses if present
736
- const cleanArgsPart =
737
- argsPart.startsWith("(") && argsPart.endsWith(")")
738
- ? argsPart.slice(1, -1)
739
- : argsPart;
740
-
741
784
  // Create a wrapped function that validates on call
742
785
  const ReliantFunction = (...args: any[]) => {
743
786
  // 1. Validate Arguments
744
- if (cleanArgsPart && cleanArgsPart !== "()") {
745
- const argDefs = cleanArgsPart.split(",").map((s) => s.trim());
746
-
747
- for (let i = 0; i < argDefs.length; i++) {
748
- const def = argDefs[i];
749
- // Parse definition: name?: type or ...name: type
750
- const parts = def.split(":");
751
- const namePart = parts[0].trim();
752
- let typePart = parts.slice(1).join(":").trim() || "any";
753
-
754
- // Handle TYPE placeholder as any
755
- if (typePart === "TYPE") typePart = "any";
756
-
757
- const isRest = namePart.startsWith("...");
758
- const isOptional = namePart.endsWith("?") || typePart.endsWith("?");
759
-
760
- // Check for missing required arguments
761
- // We check if the index exists in args. explicitly passing undefined is allowed if the type allows it,
762
- // but not passing it at all is a missing argument error for required params.
763
- if (!isRest && !isOptional && i >= args.length) {
764
- throw new Error(
765
- `[ReliantType] Missing required argument at index ${i} ('${namePart}'). Expected ${typePart}.`
766
- );
767
- }
787
+ for (let i = 0; i < argDefs.length; i++) {
788
+ const def = argDefs[i];
789
+ const parts = def.split(":");
790
+ const namePart = parts[0].trim();
791
+ let typePart = parts.slice(1).join(":").trim() || "any";
792
+ if (typePart === "TYPE") typePart = "any";
793
+
794
+ const isRest = namePart.startsWith("...");
795
+ const isOptional = namePart.endsWith("?") || typePart.endsWith("?");
796
+
797
+ // Check for missing required arguments
798
+ if (!isRest && !isOptional && i >= args.length) {
799
+ throw new Error(
800
+ `[ReliantType] Missing required argument at index ${i} ('${namePart}'). Expected ${typePart}.`
801
+ );
802
+ }
768
803
 
769
- if (isRest) {
770
- const restType = typePart.endsWith("[]")
771
- ? typePart.slice(0, -2)
772
- : typePart;
773
- const restArgs = args.slice(i);
774
- const restValidator = FieldPrecompilers.parseAndCompile(restType);
775
- for (const restArg of restArgs) {
776
- const res = restValidator(restArg);
777
- if (!res.success) {
778
- throw new Error(
779
- `[ReliantType] Argument validation failed for rest parameter at index ${i}: ${res.errors[0].message}`
780
- );
781
- }
804
+ if (isRest) {
805
+ const restType = typePart.endsWith("[]")
806
+ ? typePart.slice(0, -2)
807
+ : typePart;
808
+ const restArgs = args.slice(i);
809
+ const restValidator = FieldPrecompilers.parseAndCompile(restType);
810
+ for (const restArg of restArgs) {
811
+ const res = restValidator(restArg);
812
+ if (!res.success) {
813
+ throw new Error(
814
+ `[ReliantType] Argument validation failed for rest parameter at index ${i}: ${res.errors[0].message}`
815
+ );
782
816
  }
783
- break; // Rest is always last
784
- } else {
785
- // If argument is provided (or undefined but present), validate it
786
- if (i < args.length) {
787
- const argValidator =
788
- FieldPrecompilers.parseAndCompile(typePart);
789
- const res = argValidator(args[i]);
790
- if (!res.success) {
791
- throw new Error(
792
- `[ReliantType] Argument validation failed for argument at index ${i} ('${namePart}'): ${res.errors[0].message}`
793
- );
794
- }
817
+ }
818
+ break;
819
+ } else {
820
+ if (i < args.length) {
821
+ const argValidator = FieldPrecompilers.parseAndCompile(typePart);
822
+ const res = argValidator(args[i]);
823
+ if (!res.success) {
824
+ throw new Error(
825
+ `[ReliantType] Argument validation failed for argument at index ${i} ('${namePart}'): ${res.errors[0].message}`
826
+ );
795
827
  }
796
828
  }
797
829
  }
@@ -802,9 +834,7 @@ export class FieldPrecompilers {
802
834
 
803
835
  // 3. Validate Return Value
804
836
  if (returnPart && returnPart !== "void") {
805
- // Handle TYPE placeholder as any
806
837
  const returnType = returnPart === "TYPE" ? "any" : returnPart;
807
-
808
838
  if (returnType !== "any") {
809
839
  const returnValidator =
810
840
  FieldPrecompilers.parseAndCompile(returnType);
@@ -816,7 +846,6 @@ export class FieldPrecompilers {
816
846
  }
817
847
  }
818
848
  }
819
-
820
849
  return result;
821
850
  };
822
851
 
@@ -44,7 +44,7 @@ export class SecurityValidators {
44
44
  // security schema with more comprehensive protections
45
45
  this.ajv.addSchema(
46
46
  {
47
- $id: "https://nehonix.space/lib/v/reliant-type",
47
+ $id: "https://nehonix.com/lib/v/reliant-type",
48
48
  type: ["object", "array", "string", "number", "boolean", "null"],
49
49
  definitions: {
50
50
  secureObject: {