mongoose-tsgen
Advanced tools
Comparing version 8.1.1 to 8.2.0
# CHANGELOG | ||
## 8.2.0 / 2021-05-19 | ||
* Added `mongoose.Query.populate` overload to narrow query typings & `--no-populate-overload` flag to disable the behaviour | ||
* Added new population helper types and type guards `PopulatedDocument`, `IsPopulated` | ||
* Update to Mongoose 5.12.10 | ||
## 8.1.0 / 2021-05-17 | ||
@@ -4,0 +10,0 @@ |
@@ -38,2 +38,4 @@ import mongoose from "mongoose"; | ||
export declare const loadSchemas: (modelsPaths: string[]) => LoadedSchemas; | ||
export declare const addPopulateHelpers: (sourceFile: SourceFile) => void; | ||
export declare const overloadQueryPopulate: (sourceFile: SourceFile) => void; | ||
export declare const createSourceFile: (genPath: string) => SourceFile; | ||
@@ -40,0 +42,0 @@ export declare const generateTypes: ({ sourceFile, schemas, imports, noMongoose }: { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.saveFile = exports.generateTypes = exports.createSourceFile = exports.loadSchemas = exports.registerUserTs = exports.getParseKeyFn = exports.parseSchema = exports.replaceModelTypes = void 0; | ||
exports.saveFile = exports.generateTypes = exports.createSourceFile = exports.overloadQueryPopulate = exports.addPopulateHelpers = exports.loadSchemas = exports.registerUserTs = exports.getParseKeyFn = exports.parseSchema = exports.replaceModelTypes = void 0; | ||
const tslib_1 = require("tslib"); | ||
@@ -15,2 +15,49 @@ const mongoose_1 = tslib_1.__importDefault(require("mongoose")); | ||
const MONGOOSE_IMPORT = `import mongoose from "mongoose";`; | ||
const POPULATE_HELPERS = `/** | ||
* Populate properties on a document type: | ||
* \`\`\` | ||
* import { PopulatedDocument } from "../interfaces/mongoose.gen.ts" | ||
* | ||
* function example(user: PopulatedDocument<UserDocument, "bestFriend">) { | ||
* console.log(user.bestFriend._id) // typescript knows this is populated | ||
* } | ||
* \`\`\` | ||
*/ | ||
export type PopulatedDocument<DocType extends mongoose.Document, T> = T extends keyof DocType ? Omit<DocType, T> & { [ref in T]: Exclude<DocType[T], mongoose.Types.ObjectId> } : DocType; | ||
/** | ||
* Check if a property on a document is populated: | ||
* \`\`\` | ||
* import { IsPopulated } from "../interfaces/mongoose.gen.ts" | ||
* | ||
* if (IsPopulated<UserDocument["bestFriend"]>) { ... } | ||
* \`\`\` | ||
*/ | ||
export function IsPopulated<T>(doc: T | mongoose.Types.ObjectId): doc is T { | ||
return doc instanceof mongoose.Document; | ||
}`; | ||
const QUERY_POPULATE = `/** | ||
* Helper types used by the populate overloads | ||
*/ | ||
type Unarray<T> = T extends Array<infer U> ? U : T; | ||
type Modify<T, R> = Omit<T, keyof R> & R; | ||
/** | ||
* Augment mongoose with Query.populate overloads | ||
*/ | ||
declare module "mongoose" { | ||
interface Query<ResultType, DocType extends Document, THelpers = {}> { | ||
populate<T extends keyof DocType>(path: T, select?: string | any, model?: string | Model<any, THelpers>, match?: any): Query< | ||
ResultType extends Array<DocType> ? Array<PopulatedDocument<Unarray<ResultType>, T>> : (ResultType extends DocType ? PopulatedDocument<Unarray<ResultType>, T> : ResultType), | ||
DocType, | ||
THelpers | ||
> & THelpers; | ||
populate<T extends keyof DocType>(options: Modify<PopulateOptions, { path: T }> | Array<PopulateOptions>): Query< | ||
ResultType extends Array<DocType> ? Array<PopulatedDocument<Unarray<ResultType>, T>> : (ResultType extends DocType ? PopulatedDocument<Unarray<ResultType>, T> : ResultType), | ||
DocType, | ||
THelpers | ||
> & THelpers; | ||
} | ||
}`; | ||
const getObjectDocs = (modelName) => `/** | ||
@@ -613,2 +660,8 @@ * Lean version of ${modelName}Document (type alias of \`${modelName}\`) | ||
}; | ||
exports.addPopulateHelpers = (sourceFile) => { | ||
sourceFile.addStatements("\n" + POPULATE_HELPERS); | ||
}; | ||
exports.overloadQueryPopulate = (sourceFile) => { | ||
sourceFile.addStatements("\n" + QUERY_POPULATE); | ||
}; | ||
exports.createSourceFile = (genPath) => { | ||
@@ -615,0 +668,0 @@ const project = new ts_morph_1.Project(); |
@@ -14,2 +14,3 @@ import { Command, flags } from "@oclif/command"; | ||
"no-mongoose": import("@oclif/parser/lib/flags").IBooleanFlag<boolean>; | ||
"no-populate-overload": import("@oclif/parser/lib/flags").IBooleanFlag<boolean>; | ||
}; | ||
@@ -16,0 +17,0 @@ static args: { |
@@ -53,2 +53,6 @@ "use strict"; | ||
} | ||
await parser.addPopulateHelpers(sourceFile); | ||
if (!flags["no-populate-overload"]) { | ||
await parser.overloadQueryPopulate(sourceFile); | ||
} | ||
cleanupTs === null || cleanupTs === void 0 ? void 0 : cleanupTs(); | ||
@@ -95,3 +99,3 @@ cli_ux_1.default.action.stop(); | ||
char: "o", | ||
description: "[default: ./src/interfaces] Path of output file containing generated typings. If a folder path is passed, the generator will default to creating a `mongoose.gen.ts` file in the specified folder." | ||
description: "[default: ./src/interfaces] Path of output file to write generated typings. If a folder path is passed, the generator will create a `mongoose.gen.ts` file in the specified folder." | ||
}), | ||
@@ -107,2 +111,5 @@ project: command_1.flags.string({ | ||
description: "Don't generate types that reference mongoose (i.e. documents). Replace ObjectId with string." | ||
}), | ||
"no-populate-overload": command_1.flags.boolean({ | ||
description: "Disable augmenting mongoose with Query.populate overloads (the overloads narrow the return type of populated documents queries)." | ||
}) | ||
@@ -109,0 +116,0 @@ }; |
{ | ||
"name": "mongoose-tsgen", | ||
"description": "A Typescript interface generator for Mongoose that works out of the box.", | ||
"version": "8.1.1", | ||
"version": "8.2.0", | ||
"author": "Francesco Virga @francescov1", | ||
@@ -24,3 +24,3 @@ "bin": { | ||
"mkdirp": "^1.0.4", | ||
"mongoose": "^5.12.6", | ||
"mongoose": "^5.12.10", | ||
"prettier": "^2.1.2", | ||
@@ -27,0 +27,0 @@ "strip-json-comments": "^3.1.1", |
@@ -131,25 +131,28 @@ # mongoose-tsgen | ||
OPTIONS | ||
-c, --config=config [default: ./] Path of `mtgen.config.json` or its root folder. CLI flag | ||
options will take precendence over settings in `mtgen.config.json`. | ||
-c, --config=config [default: ./] Path of `mtgen.config.json` or its root folder. CLI flag | ||
options will take precendence over settings in `mtgen.config.json`. | ||
-d, --dry-run Print output rather than writing to file. | ||
-d, --dry-run Print output rather than writing to file. | ||
-h, --help Show CLI help | ||
-h, --help Show CLI help | ||
-i, --imports=import Custom import statements to add to the output file. Useful if you use | ||
third-party types in your mongoose schema definitions. For multiple imports, | ||
specify this flag more than once. | ||
-i, --imports=imports Custom import statements to add to the output file. Useful if you use | ||
third-party types in your mongoose schema definitions. For multiple imports, | ||
specify this flag more than once. | ||
-o, --output=output [default: ./src/interfaces] Path of output file to write generated typings. | ||
If a folder path is passed, the generator will create a `mongoose.gen.ts` file | ||
in the specified folder. | ||
-o, --output=output [default: ./src/interfaces] Path of output file to write generated typings. | ||
If a folder path is passed, the generator will create a `mongoose.gen.ts` file | ||
in the specified folder. | ||
-p, --project=project [default: ./] Path of `tsconfig.json` or its root folder. | ||
-p, --project=project [default: ./] Path of `tsconfig.json` or its root folder. | ||
--debug Print debug information if anything isn't working | ||
--debug Print debug information if anything isn't working | ||
--no-format Disable formatting generated files with prettier. | ||
--no-format Disable formatting generated files with prettier. | ||
--no-mongoose Don't generate types that reference mongoose (i.e. documents). Replace ObjectId with | ||
string. | ||
--no-mongoose Don't generate types that reference mongoose (i.e. documents). Replace ObjectId with | ||
string. | ||
--no-populate-overload Disable augmenting mongoose with Query.populate overloads (the overloads narrow | ||
the return type of populated documents queries). | ||
``` | ||
@@ -178,26 +181,27 @@ | ||
Any field with a `ref` property will be typed as `RefDocument["_id"] | RefDocument`. This allows you to use the same type whether you populate a field or not. When populating a field, you will need to use [Typeguards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) or [Type Assertion](https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions) to tell Typescript that the field is populated: | ||
Any field with a `ref` property will be typed as `RefDocument["_id"] | RefDocument`. As part of the generated file, mongoose will be augmented with `Query.populate` overloads to narrow return types of populated queries (this can be disabled using the `--no-populate-overload` flag). A helper type `IsPopulated` and a type guard function `PopulatedDocument` will also be generated to help with handling populated documents, see usage below: | ||
```typescript | ||
// fetch user with bestFriend populated | ||
const user = await User.findById(uid).populate("bestFriend").exec() | ||
import { IsPopulated, PopulatedDocument } from "../interfaces/mongoose.gen.ts"; | ||
// typescript won't allow this, since `bestFriend` is typed as `UserDocument["_id"] | UserDocument` | ||
console.log(user.bestFriend._id) | ||
// instead use type assertion | ||
const bestFriend = user.bestFriend as UserDocument; | ||
console.log(bestFriend._id); | ||
// or use typeguards | ||
function isPopulated<T>(doc: T | mongoose.Types.ObjectId): doc is T { | ||
return doc instanceof mongoose.Document; | ||
// UserDocument["bestFriend"] = mongoose.Types.ObjectId | UserDocument | ||
function unsafeType(user: UserDocument) { | ||
// type guard | ||
if (IsPopulated(user.bestFriend))) { | ||
// user.bestFriend is confirmed to be populated, typescript will allow accessing its properties now | ||
console.log(user.bestFriend._id) | ||
} | ||
} | ||
if (isPopulated<UserDocument>(user.bestFriend)) { | ||
// user.bestFriend is a UserDocument | ||
// `user` is typed as a UserDocument with `bestFriend` populated | ||
function safeType(user: PopulatedDocument<UserDocument, "bestFriend">) { | ||
console.log(user.bestFriend._id) | ||
} | ||
// due to the `Query.populate` overload, `user` will be typed as `PopulatedDocument<UserDocument, "bestFriend">` | ||
// rather than the usual `UserDocument` | ||
const user = await User.findById(uid).populate("bestFriend").exec() | ||
// completely typesafe | ||
safeType(user) | ||
``` | ||
@@ -349,4 +353,2 @@ | ||
- [ ] Stronger/automatic [populate](https://mongoosejs.com/docs/populate.html) typing (see [Query Population](#query-population)). | ||
- [ ] Add CLI option to type `_id` fields as a string rather than an ObjectId on lean version of documents (see [#7](https://github.com/francescov1/mongoose-tsgen/issues/7)). | ||
- [ ] Cut down node_modules by using peer dependencies (i.e. mongoose) and stripping oclif. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
76687
1246
352
Updatedmongoose@^5.12.10