├── .eslintignore ├── .nvmrc ├── .gitignore ├── .babelrc ├── .github ├── dependabot.yml └── workflows │ └── publish.yml ├── .editorconfig ├── .eslintrc ├── package.json ├── src ├── mongo.d.ts └── mongo.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /lib 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.19.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /lib 3 | **/.DS_Store 4 | .idea 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "minify"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.js] 15 | indent_brace_style = 1TBS 16 | spaces_around_operators = true 17 | quote_type = single 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@babel/eslint-parser", 3 | "extends": [ 4 | "airbnb" 5 | ], 6 | "rules": { 7 | "comma-dangle": ["error", "never"], 8 | "import/no-unresolved": "off", 9 | "no-console": "off", 10 | "global-require": "off", 11 | "no-underscore-dangle": "off", 12 | "no-use-before-define": ["error", { "functions": false }] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish npm package 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '14' 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | 23 | - name: Build package 24 | run: npm run build 25 | 26 | - name: Publish package to GitHub Package Registry 27 | run: echo //npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }} > .npmrc && npm publish --registry=https://npm.pkg.github.com 28 | 29 | - name: Publish package to NPM 30 | run: echo //registry.npmjs.org/:_authToken=${{ secrets.NPM_BOTTY_TOKEN }} > .npmrc && npm publish --registry=https://registry.npmjs.org 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@codesignal/meteor-protomongo", 3 | "version": "3.2.0", 4 | "description": "Extends meteor/mongo with handy async methods", 5 | "main": "lib/mongo.js", 6 | "types": "lib/mongo.d.ts", 7 | "author": "CodeSignal", 8 | "scripts": { 9 | "eslint": "eslint --max-warnings 0 --ignore-path .gitignore --ext .js --ext .jsx src/", 10 | "build": "npm run build:clean && npm run build:lib && cp -f src/mongo.d.ts lib/mongo.d.ts", 11 | "prepublishOnly": "npm run build", 12 | "build:clean": "rimraf lib", 13 | "build:lib": "babel src -d lib" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/codesignal/meteor-protomongo" 18 | }, 19 | "devDependencies": { 20 | "@babel/cli": "^7.17.6", 21 | "@babel/core": "^7.17.9", 22 | "@babel/eslint-parser": "^7.17.0", 23 | "@babel/plugin-transform-spread": "^7.16.7", 24 | "@babel/preset-env": "^7.16.11", 25 | "@types/meteor": "^2.9.6", 26 | "babel-preset-minify": "^0.5.1", 27 | "eslint": "^8.13.0", 28 | "eslint-config-airbnb": "^19.0.0", 29 | "eslint-plugin-import": "^2.26.0", 30 | "eslint-plugin-jsx-a11y": "^6.5.1", 31 | "eslint-plugin-react": "^7.29.4", 32 | "rimraf": "^5.0.0" 33 | }, 34 | "files": [ 35 | "/lib" 36 | ], 37 | "license": "MIT", 38 | "homepage": "https://github.com/CodeSignal/meteor-protomongo", 39 | "keywords": [ 40 | "meteor", 41 | "mongo", 42 | "async" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /src/mongo.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@codesignal/meteor-protomongo' { 2 | type Mongo = typeof import('meteor/mongo').Mongo; 3 | export function extendCollection(Mongo: Mongo): void; 4 | } 5 | 6 | declare module 'meteor/mongo' { 7 | module Mongo { 8 | interface Collection { 9 | updateManyAsync( 10 | selector: Selector | ObjectID | string, 11 | modifier: Modifier, 12 | options?: { 13 | /** True to insert a document if no matching documents are found. */ 14 | upsert?: boolean | undefined; 15 | /** 16 | * Used in combination with MongoDB [filtered positional operator](https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/) to specify which elements to 17 | * modify in an array field. 18 | */ 19 | arrayFilters?: { [identifier: string]: any }[] | undefined; 20 | }, 21 | callback?: Function 22 | ): Promise; updateManyAsync(selector: any, modifier: any, options: any): Promise; 23 | 24 | aggregate( 25 | stages: Array, 26 | options?: any 27 | ): { 28 | toArray: () => Promise>; 29 | }; 30 | 31 | /** 32 | * @deprecated Use ensureIndexAsync instead. 33 | */ 34 | ensureIndex( 35 | keys: { [key: string]: number | string } | string, 36 | options?: { [key: string]: any } 37 | ): void; 38 | /** 39 | * @deprecated Use ensureNoIndexAsync instead. 40 | */ 41 | ensureNoIndex(keys: { [key: string]: number | string } | string): void; 42 | 43 | ensureIndexAsync( 44 | keys: { [key: string]: number | string } | string, 45 | options?: { [key: string]: any } 46 | ): Promise; 47 | ensureNoIndexAsync(keys: { [key: string]: number | string } | string): Promise; 48 | 49 | getIndexes(): Promise; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/mongo.js: -------------------------------------------------------------------------------- 1 | const ERROR_CODES = Object.freeze({ 2 | indexOptionsConflict: 85 3 | }); 4 | 5 | function extendCollection(Mongo) { 6 | if (!Mongo) { 7 | throw new Error('Mongo object must exist'); 8 | } 9 | 10 | if (!Mongo.Collection || typeof Mongo.Collection !== 'function') { 11 | throw new Error('Mongo.Collection must be a function/class'); 12 | } 13 | 14 | Object.assign(Mongo.Collection.prototype, { 15 | updateManyAsync(selector, modifier, options) { 16 | return this.updateAsync(selector, modifier, { 17 | ...options, 18 | multi: true 19 | }); 20 | }, 21 | 22 | getIndexes() { 23 | return this.rawCollection().indexes(); 24 | }, 25 | 26 | ensureIndex(selector, options) { 27 | try { 28 | this.createIndex(selector, options); 29 | } catch (error) { 30 | if (error?.code === ERROR_CODES.indexOptionsConflict) { 31 | /** 32 | * If index already exists with different options, remove old version and re-create: 33 | * https://docs.mongodb.com/manual/reference/command/createIndexes/#considerations 34 | */ 35 | this.ensureNoIndex(selector); 36 | this.createIndex(selector, options); 37 | return; 38 | } 39 | 40 | throw error; 41 | } 42 | }, 43 | 44 | async ensureIndexAsync(selector, options) { 45 | try { 46 | await this.createIndexAsync(selector, options); 47 | } catch (error) { 48 | if (error?.code === ERROR_CODES.indexOptionsConflict) { 49 | /** 50 | * If index already exists with different options, remove old version and re-create: 51 | * https://docs.mongodb.com/manual/reference/command/createIndexes/#considerations 52 | */ 53 | await this.ensureNoIndexAsync(selector); 54 | await this.createIndexAsync(selector, options); 55 | return; 56 | } 57 | 58 | throw error; 59 | } 60 | }, 61 | 62 | ensureNoIndex(selector) { 63 | try { 64 | this._dropIndex(selector); 65 | } catch (error) { 66 | if (error.codeName !== 'IndexNotFound') { 67 | console.error(error, 'Error when calling ensureNoIndex'); 68 | } 69 | } 70 | }, 71 | 72 | async ensureNoIndexAsync(selector) { 73 | try { 74 | await this.rawCollection().dropIndex(selector); 75 | } catch (error) { 76 | if (error.codeName !== 'IndexNotFound') { 77 | console.error(error, 'Error when calling ensureNoIndex'); 78 | } 79 | } 80 | }, 81 | 82 | aggregate(pipeline, options) { 83 | return this.rawCollection().aggregate(pipeline, options); 84 | } 85 | }); 86 | } 87 | 88 | module.exports = { 89 | extendCollection 90 | }; 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | @codesignal/meteor-protomongo
[![](http://img.shields.io/npm/dm/@codesignal/meteor-protomongo.svg?style=flat)](https://www.npmjs.com/package/@codesignal/meteor-protomongo) [![npm version](https://badge.fury.io/js/%40codesignal%2Fmeteor-protomongo.svg)](https://www.npmjs.com/package/@codesignal/meteor-protomongo) 2 | = 3 | 4 | **Monkey-patches to `meteor/mongo`.** 5 | 6 | *Helpful for working with indexes and transitioning away from Fibers.* 7 | 8 | **Update, November 2022:** 9 | 10 | Meteor 2.8 updated the core `meteor/mongo` package to add `*Async` versions of Mongo methods by default. See: https://github.com/meteor/meteor/pull/11605 11 | 12 | Since the new core methods have identical signatures to the ones previously provided by this package, the 3.x release of this package has been updated to remove the overlapping methods. 13 | 14 | Now, the package only includes helpers related to working with indexes or aggregation. 15 | 16 | ## Description 17 | 18 | This package extends the `Collection` prototype from `meteor/mongo` with a few handy helpers related to indexes and aggregation. It's intended for use only with projects built with [Meteor](https://www.meteor.com/). 19 | 20 | ### API 21 | 22 | #### Collection Prototype 23 | 24 | ```js 25 | Collection.updateManyAsync(selector, modifier, ?options); 26 | ``` 27 | 28 | Same as `Collection.updateAsync`; passes `{ multi: true }` in addition to the passed `options`. 29 | 30 | ```js 31 | Collection.getIndexes(); 32 | ``` 33 | 34 | Returns a Promise that is resolved with an array of indexes for this collection. For example, you might see this for a users collection with only an index on ID: 35 | ```js 36 | [{ v: 2, key: { _id: 1 }, name: '_id_', ns: 'meteor.users' }] 37 | ``` 38 | 39 | ```js 40 | Collection.ensureIndex(selector, options); 41 | ``` 42 | 43 | Ensures an index exists. Similar to the built-in `createIndex`, but handles the case where the index already exists with different options by removing and re-adding the index with the new options. To ensure your database has the same indexes across different environments, you might want to add `ensureIndex` calls to `Meteor.startup`. 44 | 45 | ```js 46 | Collection.ensureIndexAsync(selector, options); 47 | ``` 48 | 49 | Similar to `ensureIndex`, but returns a Promise that is resolved when the index is created. This method is compatible with Meteor 3.0. 50 | 51 | ```js 52 | Collection.ensureNoIndex(selector); 53 | ``` 54 | 55 | The reverse of `ensureIndex`. You might want to call this in `Meteor.startup` to make sure an index has been removed in all of your deployed environments. 56 | 57 | ```js 58 | Collection.ensureNoIndexAsync(selector); 59 | ``` 60 | 61 | Similar to `ensureNoIndex`, but returns a Promise that is resolved when the index is dropped. This method is compatible with Meteor 3.0. 62 | 63 | ```js 64 | Collection.aggregate(pipeline, ?options); 65 | ``` 66 | 67 | Exposes the [aggregate method](https://www.mongodb.com/docs/manual/reference/method/db.collection.aggregate/). This removes the need to use `rawCollection()` every time you want to aggregate. 68 | 69 | ## Install 70 | 71 | ```bash 72 | npm install @codesignal/meteor-protomongo 73 | ``` 74 | 75 | After the package is installed, add the following few lines in a file that's going to be loaded on startup: 76 | ```js 77 | import { Mongo } from 'meteor/mongo'; 78 | import ProtoMongo from '@codesignal/meteor-protomongo'; 79 | 80 | ProtoMongo.extendCollection(Mongo); 81 | ``` 82 | 83 | ## Building Locally 84 | 85 | After checking out this repo, run... 86 | 87 | ```sh 88 | npm install 89 | npm run build 90 | ``` 91 | 92 | To do local checks: 93 | ```sh 94 | npm run eslint 95 | ``` 96 | 97 | ## Contributing 98 | 99 | If you'd like to make a contribution, please open a pull request in [package repository](https://github.com/CodeSignal/meteor-protomongo). 100 | 101 | If you want (and have the right permissions) to publish a new version, please follow the instructions below: 102 | * Make sure you have [NPM](https://www.npmjs.com/) account and are a member of [codesignal](https://www.npmjs.com/org/codesignal) organization; 103 | * Follow instructions from [npm docs](https://docs.npmjs.com/getting-started/publishing-npm-packages) to set up NPM user in your local environment; 104 | * Update package version and push changes to git by running these commands: `npm version `, `git push origin master`, `git push --tags`; 105 | * Publish the updated package with `npm publish --access public`. 106 | 107 | ## License 108 | 109 | MIT 110 | --------------------------------------------------------------------------------