simpl-schema

repository·main·Indexed 20 days ago

https://github.com/longshotlabs/simpl-schema

A TypeScript-based object validation library supporting CommonJS and ESM. It provides schema validation, cleaning (type conversion and property removal), and specialized support for MongoDB update modifier objects. Features include shorthand and longhand schema definitions, subschemas, dot-notation for nested structures, and advanced data transformation via autoValue.

Tokens
13.5K
Snippets
47
Records
59
Agent score
68%

What's inside simpl-schema

  1. Define schemas using shorthand and longhand syntax

    main

    SimpleSchema supports three ways to define schema structures:

    1. Shorthand: Map a property name directly to a type (e.g., name: String). This ensures the property is present and matches the type.
    2. Longhand: Use an object to define a type along with additional rules like max, optional, or defaultValue (e.g., name: { type: String, max: 40 }).
    3. Mixed: Combine both syntaxes within a single schema definition.

    Special Shorthand Rules:

    • Regex: Setting a key to a regular expression automatically treats the type as String with a regEx rule.
    • Arrays: Setting a key to an array of a type (e.g., [String]) is shorthand for an array type where every element must match that type.
    import SimpleSchema from "simpl-schema";
    
    // Shorthand
    const shorthandSchema = new SimpleSchema({
      name: String,
      age: SimpleSchema.Integer,
      registered: Boolean,
      exp: /foo/,
      friends: [String],
    });
    
    // Longhand
    const longhandSchema = new SimpleSchema({
      name: {
        type: String,
        max: 40,
      },
      age: {
        type: SimpleSchema.Integer,
        optional: true,
      },
      registered: {
        type: Boolean,
        defaultValue: false,
      },
    });
  2. Use dot notation and $ for nested objects and arrays

    main

    SimpleSchema uses MongoDB-style dot notation to define rules for nested structures:

    • Nested Objects: Use "parent.child" to define properties within an object.
    • Arrays: Use the $ operator to define rules for elements within an array (e.g., "arrayName.$" for the items, and "arrayName.$.property" for properties of objects inside that array).
    import SimpleSchema from "simpl-schema";
    
    // Nested Objects
    const objectSchema = new SimpleSchema({
      mailingAddress: Object,
      "mailingAddress.street": String,
      "mailingAddress.city": String,
    });
    
    // Arrays of Objects
    const arraySchema = new SimpleSchema({
      addresses: {
        type: Array,
        minCount: 1,
        maxCount: 4,
      },
      "addresses.$": Object,
      "addresses.$.street": String,
      "addresses.$.city": String,
    });
  3. Use autoValue for complex data transformations

    main

    The autoValue option is a powerful feature used during the .clean() process to transform or set values. It accepts a function that can return a new value, a special MongoDB modifier (like {$inc: 1}), or undefined (to keep the original value).

    The this context in autoValue

    Inside an autoValue function, this provides access to:

    • this.isSet: Whether the field is currently set.
    • this.value: The current value of the field.
    • this.key: The schema key being processed.
    • this.obj: The full object being cleaned.
    • this.isModifier: True if running on a MongoDB update document.
    • this.operator: The MongoDB operator (e.g., "$set") if isModifier is true.
    • this.field(name): Returns info (isSet, value, operator) about another field.
    • this.parentField(name) / this.siblingField(name): Accesses parent or sibling field info.
    • this.unset(): Prevents the original value from being used (useful if you want to remove a field).

    Important Behaviors

    • Order Matters: autoValue functions run from least nested to most nested. If one field's autoValue depends on another, ensure the dependency is defined earlier in the schema.
    • Unset: If you return undefined, the original value is kept. To remove a value, you must call this.unset().
  4. How validation contexts work in SimpleSchema

    main

    A validation context provides methods for validating and checking the status of an object. There are four ways to perform validation:

    1. Throwaway context: Uses schema.validate(). It throws an Error for the first validation error found.
    2. Unnamed context: Uses schema.newContext(). It does not throw errors and is not persisted.
    3. Named context: Uses schema.namedContext('name'). It does not throw errors and is automatically persisted by name, allowing you to reuse the context.
    4. Default context: Uses schema.namedContext(). This is equivalent to schema.namedContext('default') and does not throw errors.

    Use a named context when you want to rely on the context's methods across different parts of your application.

    import SimpleSchema from "simpl-schema";
    
    const schema = new SimpleSchema({
      name: String,
    });
    
    // Obtain a named context for persistence
    const userFormValidationContext = schema.namedContext("userForm");
    
    // Obtain an unnamed context (not persisted)
    const myValidationContext = schema.newContext();
  5. Manage field requirement and optionality

    main

    By default, all keys in a SimpleSchema are required.

    Making fields optional

    • Set optional: true on a specific field.
    • Set requiredByDefault: false in the schema options to make all fields optional unless explicitly marked with required: true.

    Requiredness logic

    • Arrays: A required Array must exist, but an empty array [] is considered valid. Use minCount: 1 if an empty array is not allowed.
    • Array Items: For array items (defined using the .$ syntax), if optional: true, null values are valid. If required, null items fail validation.
    • Nested Objects: If a parent object is optional and not present, its required children do not trigger validation errors. However, if the parent is present, all its required children must be present.
    • MongoDB Updates: When validating MongoDB modifier objects, attempts to unset or set a required key to null will result in validation errors.
    const schema = new SimpleSchema(
      {
        optionalProp: String,
        requiredProp: { type: String, required: true },
      },
      { requiredByDefault: false }
    );
  6. Enable Debug Mode

    main

    Set SimpleSchema.debug = true before creating named validation contexts. This causes all named validation contexts to automatically log invalid key errors to the browser console, which is useful for debugging failed validations during development.

    SimpleSchema.debug = true;
  7. Install simpl-schema via npm

    main

    Install the simpl-schema package using npm. Note that the package name is spelled without an 'e' (it is simpl-schema, not simple-schema).

    npm install simpl-schema
  8. How SimpleSchema handles subschemas and blackbox keys

    main

    SimpleSchema supports nested object validation through subschemas and 'blackbox' keys.

    Subschemas: When a field's type is another SimpleSchema instance, validation descends into that subschema.

    Blackbox Keys: A key marked as blackbox: true tells SimpleSchema to treat the entire object as an opaque blob. While the object itself is validated as an object, its internal properties are not checked against the schema. This is useful for storing arbitrary metadata or third-party data.

    Key Utilities:

    • nearestSimpleSchemaInstance(key): Returns the SimpleSchema instance that actually defines the given key and the relative key within that schema.
    • allowsKey(key): Checks if a key (including dot-notation nested keys) is permitted by the schema, respecting blackbox boundaries.
  9. Understand how defaultValue and autoValue interact

    main

    SimpleSchema allows you to define a defaultValue for a field, which is internally converted into an autoValue function.

    Key behaviors:

    • Automatic Conversion: If you provide defaultValue, SimpleSchema creates an autoValue that runs during validation/cleaning.
    • Precedence: If both autoValue and defaultValue are provided, defaultValue is ignored (and a warning is logged) unless the autoValue is explicitly marked as a default (isDefault: true).
    • Array Item Restriction: You cannot use defaultValue on array item definitions (fields ending in .$).
    • Smart Defaults: The generated default value logic is aware of MongoDB operators. It will not apply a default value during $pull operations or when $pushing an object into an array of objects, preventing accidental data corruption in complex updates.
  10. Use autoValue functions to set default values

    main

    An autoValue function allows you to automatically calculate or set a field's value during the validation/cleaning process.

    Inside an autoValue function, this provides an AutoValueContext which includes:

    • field(name): Returns a FieldInfo object for a sibling field.
    • parentField(): Returns a FieldInfo object for the parent field.
    • siblingField(name): Returns a FieldInfo object for a sibling field.
    • isUpsert: Boolean indicating if the operation is an upsert.
    • unset(): A function to remove the field from the object.
    • value: The current value of the field.
    • key: The name of the current field.
    • isSet: Whether the field is being set.
    • operator: The MongoDB operator being used (if applicable).

    You can also mark a function as isDefault: true to indicate it should only run when the value is missing.

    const schema = new SimpleSchema({
      createdAt: {
        type: Date,
        autoValue(obj) {
          if (this.isSet) {
            return this.unset();
          }
          return new Date();
        }
      }
    });
  11. Make a field conditionally required

    main

    To make a field required only under certain conditions, set optional: true and implement a custom validation function. The function must check the current state and return SimpleSchema.ErrorTypes.REQUIRED if the condition is met but the value is missing.

    Example Logic:

    1. Check the condition (e.g., value of another field).
    2. For inserts: Check if the value is null or "".
    3. For updates: Check $set (null/empty), $unset, or $rename operators.
    {
      field: {
        type: String,
        optional: true,
        custom: function () {
          let shouldBeRequired = this.field('saleType').value === 1;
    
          if (shouldBeRequired) {
            // inserts
            if (!this.operator) {
              if (!this.isSet || this.value === null || this.value === "") return SimpleSchema.ErrorTypes.REQUIRED;
            }
            // updates
            else if (this.isSet) {
              if (this.operator === "$set" && this.value === null || this.value === "") return SimpleSchema.ErrorTypes.REQUIRED;
              if (this.operator === "$unset") return SimpleSchema.ErrorTypes.REQUIRED;
              if (this.operator === "$rename") return SimpleSchema.ErrorTypes.REQUIRED;
            }
          }
        }
      }
    }
  12. Validate a MongoDB update document

    main

    SimpleSchema supports validating MongoDB update documents (modifier objects). When calling .validate(), pass { modifier: true } in the options object to tell SimpleSchema to look inside operators like $set.

    import SimpleSchema from "simpl-schema";
    
    const validationContext = new SimpleSchema({
      name: String,
    }).newContext();
    
    validationContext.validate(
      {
        $set: {
          name: 2,
        },
      },
      { modifier: true }
    );
    
    console.log(validationContext.isValid());
    console.log(validationContext.validationErrors());