npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mongo_dottype

v1.1.5

Published

TypeScript types for using dot notation in mongo queries

Downloads

92

Readme

mongo_dotType

TypeScript (v4.1+) types for using mongoDB dot notation while querying mongo documents.

How to use

Install

npm install mongo_dottype --save-dev

Use

import { mongoDot_lvl2 } from "mongo_dottype";

interface Users {
    /* ... */
}

// If you don't need array indices for "array element dot notation"
type Users_dotNotation_a = mongoDot_lvl2<Users>;

// If you need some array indices to work, i.e. for the first two elements
type Users_dotNotation_b = mongoDot_lvl2<Users, 0|1>;

// Although allowed, by the TypeScript v4.1.4 type inference for this case is practically not supported (see the bottom section)
type Users_dotNotation_c = mongoDot_lvl2<Users, number>;

Quick example:

import { mongoDot_lvl2 } from "mongo_dottype";

export interface Doc1 {
    prop1: string,
    prop2: {
        sp21:  string,
        sp22?: number,
    },
    prop3: number,
    prop4: {
        sp41: string,
        sp42: number,
    },
    prop5: string[];
};


export type Doc1_mongoDot = mongoDot_lvl2<Doc1, 0|1|2>;
//
// the type resolves to:
//
type Doc1_mongoDot = {
    _id?: number;
    __v?: number;
    prop1?: string;
    prop2?: {
        sp21: string;
        sp22?: number;
    };
    "prop2.sp21"?: string;
    "prop2.sp22"?: number;
    prop3?: number;
    prop4?: {
        sp41: string;
        sp42: number;
    };
    "prop4.sp41"?: string;
    "prop4.sp42"?: number;
    prop5?: string[];
    "prop5.0"?: string;
    "prop5.1"?: string;
    "prop5.2"?: string;
}

Longer example:

export interface Doc2 {
    prop1: string,
    prop2: {
        sp21: string,
        sp22: number,
        sp23: {
            sp23el_prop1: number,
            sp23el_prop2?: string,
        }[],
        sp24?: {
            ssp241: string,
            ssp242?: boolean,
        }
    },
    prop3: number,
    prop4: {
        sp41: string,
        sp42: number,
    },
    prop5: string[];
    prop6: {
        prop6el_prop1: string,
        prop6el_prop2?: number,
    }[];
    prop7?: string[];
};


export type Doc2_mongoDot = mongoDot_lvl2<Doc2, 0|1|2>;
//
// the type resolves to:
//
type Doc2_mongoDot = {
    _id?: number;
    __v?: number;
    prop1?: string;
    prop2?: {
        sp21: string;
        sp22: number;
        sp23: {
            sp23el_prop1: number;
            sp23el_prop2?: string;
        }[];
        sp24?: {
            ssp241: string;
            ssp242?: boolean;
        };
    };
    "prop2.sp21"?: string;
    "prop2.sp22"?: number;
    "prop2.sp23"?: {
        sp23el_prop1: number;
        sp23el_prop2?: string;
    }[];
    "prop2.sp23.0"?: {
        sp23el_prop1: number;
        sp23el_prop2?: string;
    };
    "prop2.sp23.1"?: {
        sp23el_prop1: number;
        sp23el_prop2?: string;
    };
    "prop2.sp23.2"?: {
        sp23el_prop1: number;
        sp23el_prop2?: string;
    };
    "prop2.sp24"?: {
        ssp241: string;
        ssp242?: boolean;
    };
    "prop2.sp24.ssp241"?: string;
    "prop2.sp24.ssp242"?: boolean;
    prop3?: number;
    prop4?: {
        sp41: string;
        sp42: number;
    };
    "prop4.sp41"?: string;
    "prop4.sp42"?: number;
    prop5?: string[];
    "prop5.0"?: string;
    "prop5.1"?: string;
    "prop5.2"?: string;
    prop6?: {
        prop6el_prop1: string;
        prop6el_prop2?: number;
    }[];
    "prop6.0"?: {
        prop6el_prop1: string;
        prop6el_prop2?: number;
    };
    "prop6.0.prop6el_prop1"?: string;
    "prop6.0.prop6el_prop2"?: number;
    "prop6.1"?: {
        prop6el_prop1: string;
        prop6el_prop2?: number;
    };
    "prop6.1.prop6el_prop1"?: string;
    "prop6.1.prop6el_prop2"?: number;
    "prop6.2"?: {
        prop6el_prop1: string;
        prop6el_prop2?: number;
    };
    "prop6.2.prop6el_prop1"?: string;
    "prop6.2.prop6el_prop2"?: number;
    prop7?: string[];
    "prop7.0"?: string;
    "prop7.1"?: string;
    "prop7.2"?: string;
}

Deepness and performance

The package provides only with types for subproperties up to 2 levels deep, i.e.:

// from
{
    prop2: {
        sp24: {
            ssp241: string;
        };
    };
}
// to
{
    "prop2.sp24.ssp241"?: string;
}

But you can still define deeper levels yourself (3rd, 4th, maybe 5th) by copying commented definitions from the mongoDotType.type.d.ts file.

These definitions were left commented intentionally though.

Starting with level 3, simply having such high level type alias definition can cause TS compiler to run slower.

Starting with level 4, in some cases TypeScript fails to instantiate a type producing the error: Type instantiation is excessively deep and possibly infinite. ts(2589)

You are unlikely to make level 5 work at all. If you do, more power to you!

Even longer example (using mongoose):

Say you create a mongoose model:

import { Document, model, Schema } from "mongoose";


export interface Doc3 {
  prop1: string,
  prop2: {
      sp21: string,
      sp22?: number,
      sp23: {
          ssp23el_prop1: number,
          ssp23el_prop2?: string,
      }[],
  },
  prop3: number,
  prop4: {
      sp41: string,
      sp42: number,
      sp43: number[];
      sp44: {
          ssp441: number,
          ssp442: string,
          ssp443: {
              sssp4431: string,
              sssp4432: number,
              sssp4433: number,
          },
      }
  },
  prop5: string[];
  prop6: {
      prop6el_prop1: string,
      prop6el_prop2?: number,
      prop6el_prop3?: {
          prop6el_prop3el_prop1: string,
          prop6el_prop3el_prop2: number,
          prop6el_prop3el_prop3: {
              prop6el_prop3el_sp31: number,
              prop6el_prop3el_sp32: string,
              prop6el_prop3el_sp33: string[],
          },
      }[],
  }[];
  prop7?: string[];
};


const Doc3Schema = new Schema({
  prop1: { type: String, required: true, unique: false, },
  prop2: {
      type: {
          sp21: { type: String, required: true, unique: false, },
          sp22: { type: Number, required: false, unique: false, },
          sp23: {
              type: [{
                  ssp23el_prop1: { type: Number, required: true, unique: false, },
                  ssp23el_prop2: { type: String, required: false, unique: false, },
              }],
              required: true, unique: false,
          },
      },
      required: true, unique: false,
  },
  prop3: { type: Number, required: true, unique: false, },
  prop4: {
      type: {
          sp41: { type: String, required: true, unique: false, },
          sp42: { type: Number, required: true, unique: false, },
          sp43: { type: [Number], required: true, unique: false, },
          sp44: {
              type: {
                  ssp441: { type: Number, required: true, unique: false, },
                  ssp442: { type: String, required: true, unique: false, },
                  ssp443: {
                      type: {
                          sssp4431: { type: String, required: true, unique: false, },
                          sssp4432: { type: Number, required: true, unique: false, },
                          sssp4433: { type: Number, required: true, unique: false, },
                      },
                      required: true, unique: false,
                  },
              },
              required: true, unique: false,
          },
      },
      required: true, unique: false,
  },
  prop5: { type: [String], required: true, unique: false, },
  prop6: {
      type: [{
          prop6el_prop1: { type: String, required: true, unique: false, },
          prop6el_prop2: { type: Number, required: false, unique: false, },
          prop6el_prop3: {
              type: [{
                  prop6el_prop3el_prop1: { type: String, required: true, unique: false, },
                  prop6el_prop3el_prop2: { type: Number, required: true, unique: false, },
                  prop6el_prop3el_prop3: {
                      type: {
                          prop6el_prop3el_sp31: { type: Number, required: true, unique: false, },
                          prop6el_prop3el_sp32: { type: String, required: true, unique: false, },
                          prop6el_prop3el_sp33: { type: [String], required: true, unique: false, },
                      },
                      required: true, unique: false,
                  },
              }],
              required: false, unique: false,
          },
      }],
      required: true, unique: false,
  },
  prop7: { type: [String], required: false, unique: true, },
});


export type Doc3_document = Doc3 & Document;

export default model<Doc3_document>('Doc3', Doc3Schema);

Define the type generic for dot notation up to 3 lvl deep:

import { mongoDot_lvl2, obj_defaultDocFields, sub, sublvl0, sublvl1, sublvl2 } from "mongo_dottype";
import { Expand, FlattenUnion_shallow, objNotNever } from "mongo_dottype/dist/extras.type";

/**
* Picks subproperties (& subelements) of 3rd level deep (subsubsub-properties and -elements) with dot notation
* If `arrIndices` is provided, uses all these indices to pick elements of array properties of the given object with dot notation
*/
export type sublvl3<T, arrIndices extends number = never> = objNotNever<sub<sublvl2<T, arrIndices>, arrIndices>>; 
/**
* Converts into a type with subproperties and subelements of up to 3 levels deep with dot notation
* If `arrIndices` is provided, uses all these indices to pick elements of array properties of the given object with dot notation
*/
export type mongoDot_lvl3<T, arrIndices extends number = never> = Expand<FlattenUnion_shallow<obj_defaultDocFields | sublvl0<T, arrIndices> | sublvl1<T, arrIndices> | sublvl2<T, arrIndices> | sublvl3<T, arrIndices>>>;

Then, the recommended usage by default would be the following:

import Doc3Model, { Doc3 } from "./Doc3.model";
import { FilterQuery } from "mongoose";

export async function FindDocs3(filter: FilterQuery<Required<mongoDot_lvl3<Doc3, 0|1|2>>>, select?: string): Promise<Doc3[]> {
    return Doc3Model.find(filter).select(select).exec();
}
import { mongoDot_lvl3 } from "./mongoDot_deeper";
import { FindDocs3 } from "./FindDocs3.ts";


(async () => {
    const DB = await DBconnection;



    ///// Test 1 (OK) /////
    (Doc3Model as Model<mongoDot_lvl3<Doc3, 0> & Document>).updateMany({
        "prop4.sp43.0": {
            $in: [0, 1],
        }
    },
    {
        $push: {
            "prop2.sp23": {
                ssp23el_prop1: 3,
                ssp23el_prop2: 'foobar'
            },
            "prop4.sp43": {
                $each: [2, 3, 4]
            },
        }
    }).exec();
    
    
    
    ///// Test 2 (ERROR) /////
    (Doc3Model as Model<mongoDot_lvl3<Doc3, 0> & Document>).updateMany({
        "prop4.sp43.0": {
            $in: [0, 1],
        }
    },
    {
        $push: {
            "prop2.sp23": {
                ssp23el_prop1: 3,
                ssp23el_prop2: 'foobar'
            },
            "prop4.sp43": {
                $each: [2, 3, "four"] // <-- Type 'string' is not assignable to type 'number'. ts(2322)
            },
        }
    }).exec();
    
    
    
    ///// Test 3 (OK) /////
    FindDocs3({
        "prop6.0.prop6el_prop3.2": {
            $ne: {
                prop6el_prop3el_prop1: "foo",
                prop6el_prop3el_prop2: 100,
                prop6el_prop3el_prop3: {
                    prop6el_prop3el_sp31: 200,
                    prop6el_prop3el_sp32: "BAR",
                    prop6el_prop3el_sp33: [],
                }
            }
        }
    });
    
    
    
    ///// Test 4 (ERROR) /////
    FindDocs3({
        "prop6.0.prop6el_prop3.2": {
            $ne: {
                prop6el_prop3el_prop1: "foo",
                prop6el_prop3el_prop2: 100,
                prop6el_prop3el_prop3: {
                    prop6el_prop3el_sp31: 200,
                    prop6el_prop3el_sp32: [], // <-- Type 'undefined[]' is not assignable to type 'string'. ts(2322)
                    prop6el_prop3el_sp33: [],
                }
            }
        }
    });
    
    
    

    return await DB.disconnect();
})();

Using number type for allowed array indices

This type alias:

type Users_dotNotation = mongoDot_lvl2<Users, number>;

will result in a type with object keys of type `somestring${number}`.

If you explicitly try to use this type literal (`prop.${number}`) to index an object, TSC may produce an error: "`prop.${number}` cannot be used to index type ...". But when calculated for the type aliases provided here, such a situation, instead of an error, "silently" produces either type never or, weirdly enough, undefined in place of an object that you're trying to index.

By the time I'm writing it, using number practically doesn't work. Although the TS calculations seem to work out correctly, the key part, the type checking, simply isn't supported for type literals as object keys by now. You can play around with the PickSubs_dotNotation type, as this is where (almost) all the magic happens. I will be submitting a GitHub issue to TypeScript regarding usage of somestring${number} for indexing objects.