├── test-proxy ├── .meteor │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .id │ ├── .finished-upgraders │ ├── packages │ └── versions ├── .gitignore └── package.json ├── .meteorignore ├── tests ├── tests.coffee ├── functions.js └── tests.js ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── comment-issue.yml │ └── testsuite.yml ├── CHANGELOG.md ├── LICENSE ├── .versions ├── package.js ├── .gitignore ├── collection-extensions.js ├── CONTRIBUTING.md └── README.md /test-proxy/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /test-proxy/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@3.0.2 2 | -------------------------------------------------------------------------------- /test-proxy/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/ -------------------------------------------------------------------------------- /.meteorignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | test-proxy/ 3 | .github/ 4 | -------------------------------------------------------------------------------- /test-proxy/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /test-proxy/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | o5tc2073mwjv.kuqj9kkd4gz 8 | -------------------------------------------------------------------------------- /tests/tests.coffee: -------------------------------------------------------------------------------- 1 | { CollectionExtensions } = require('meteor/lai:collection-extensions') 2 | { assert } = require 'chai' 3 | { clearExtension } = require './functions' 4 | 5 | describe 'coffeescript', () -> 6 | it 'child class - Can create child classes with wrapped constructor in Coffeescript', () -> 7 | arr = [] 8 | 9 | extension = () -> 10 | arr.push 1 11 | 12 | CollectionExtensions.addExtension extension 13 | 14 | class ChildMongoCollection extends Mongo.Collection 15 | constructor: -> 16 | arr.push 2 17 | 18 | new ChildMongoCollection 19 | 20 | assert.equal arr[0], 1 21 | assert.equal arr[1], 2 22 | clearExtension extension 23 | -------------------------------------------------------------------------------- /test-proxy/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.4.0-remove-old-dev-bundle-link 15 | 1.4.1-add-shell-server-package 16 | 1.4.3-split-account-service-packages 17 | 1.5-add-dynamic-import-package 18 | 1.7-split-underscore-from-meteor-base 19 | 1.8.3-split-jquery-from-blaze 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | **Describe the solution you'd like** 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Versions (please complete the following information):** 31 | - Meteor version: [e.g. 1.8.2] 32 | - Browser: [e.g. Firefox, Chrome, Safari] 33 | - Version: [e.g. 1.0.0] 34 | 35 | **Additional context** 36 | 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This changelog has started with development of Version 1.0 4 | 5 | 6 | ## 1.0 7 | 8 | ### Functionality 9 | - BREAKING! 10 | - Meteor 3.0 compatibility for package and dependencies 11 | - moved all code to at least ES6 12 | - export `CollectionExtensions` as module, removed from global scope 13 | - Extensions are not bound via `.apply`, since this would not 14 | work with arrow functions, the new extension format is passing the 15 | collection as first argument! 16 | - addPrototype uses `Object.defineProperty`, allows for 17 | [property descriptors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 18 | 19 | ### Tests 20 | - dropped `ongoworks:security` from tests 21 | - dropped `lai:document-methods` from tests 22 | - dropped `cfs:*` from tests 23 | - these dropped packages are currently out of reach for us; 24 | additionally, they are mostly deprecated, and we intend to 25 | include them in the future in case they get updated or transferred 26 | - added/improved tests 27 | -------------------------------------------------------------------------------- /tests/functions.js: -------------------------------------------------------------------------------- 1 | import { CollectionExtensions } from 'meteor/lai:collection-extensions' 2 | 3 | export const insert = async function (collection) { 4 | const doc = { 5 | title: 'Buy groceries', 6 | createdAt: new Date('1/1/2014'), 7 | assignedTo: [{ 8 | user: 'lindsey', 9 | assignedOn: new Date('1/1/2014') 10 | }], 11 | some: { 12 | weirdly: 'nested object', 13 | that: 'I uncreatively', 14 | came: 'up with because', 15 | iwas: 'running out of creativity' 16 | }, 17 | tags: ['critical', 'yum'], 18 | done: false 19 | } 20 | 21 | return collection.insertAsync(doc) 22 | } 23 | 24 | export const inst = async function (collection) { 25 | return collection.findOneAsync() 26 | } 27 | 28 | export const clearExtension = function (extension) { 29 | const extensions = CollectionExtensions._extensions 30 | const indexOfExtension = extensions.indexOf(extension) 31 | 32 | if (indexOfExtension > -1) { 33 | extensions.splice(indexOfExtension, 1) 34 | } 35 | } 36 | 37 | export const asyncTimeout = (ms) => new Promise((resolve) => { 38 | setTimeout(() => resolve(), ms) 39 | }) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - Today Richard Lai; Meteor Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test-proxy/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base@1.5.2 # Packages every Meteor app needs to have 8 | mobile-experience@1.1.2 # Packages for a great mobile UX 9 | mongo@2.0.1 # The database Meteor supports right now 10 | static-html@1.3.3 # Define static page content in .html files 11 | reactive-var@1.0.13 # Reactive variable for tracker 12 | tracker@1.3.4 # Meteor's client-side reactive programming library 13 | 14 | standard-minifier-css@1.9.3 # CSS minifier run for production mode 15 | standard-minifier-js@3.0.0 # JS minifier run for production mode 16 | es5-shim@4.8.1 # ECMAScript 5 compatibility for older browsers 17 | ecmascript@0.16.9 # Enable ECMAScript2015+ syntax in app code 18 | typescript@5.4.3 # Enable TypeScript syntax in .ts and .tsx modules 19 | shell-server@0.6.0 # Server-side component of the `meteor shell` command 20 | aldeed:collection2@4.0.3! -------------------------------------------------------------------------------- /.github/workflows/comment-issue.yml: -------------------------------------------------------------------------------- 1 | name: Add immediate comment on new issues 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | createComment: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Create Comment 12 | uses: peter-evans/create-or-update-comment@v1.4.2 13 | with: 14 | issue-number: ${{ github.event.issue.number }} 15 | body: | 16 | Thank you for submitting this issue! 17 | 18 | We, the Members of Meteor Community Packages take every issue seriously. 19 | Our goal is to provide long-term lifecycles for packages and keep up 20 | with the newest changes in Meteor and the overall NodeJs/JavaScript ecosystem. 21 | 22 | However, we contribute to these packages mostly in our free time. 23 | Therefore, we can't guarantee your issues to be solved within certain time. 24 | 25 | If you think this issue is trivial to solve, don't hesitate to submit 26 | a pull request, too! We will accompany you in the process with reviews and hints 27 | on how to get development set up. 28 | 29 | Please also consider sponsoring the maintainers of the package. 30 | If you don't know who is currently maintaining this package, just leave a comment 31 | and we'll let you know 32 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | accounts-base@3.0.1 2 | aldeed:collection2@4.0.1 3 | aldeed:simple-schema@1.13.1 4 | allow-deny@2.0.0 5 | babel-compiler@7.11.0 6 | babel-runtime@1.5.2 7 | base64@1.0.13 8 | binary-heap@1.0.12 9 | boilerplate-generator@2.0.0 10 | callback-hook@1.6.0 11 | check@1.4.2 12 | coffeescript@1.0.1 13 | core-runtime@1.0.0 14 | dburles:mongo-collection-instances@1.0.0 15 | ddp@1.4.2 16 | ddp-client@3.0.1 17 | ddp-common@1.4.4 18 | ddp-rate-limiter@1.2.2 19 | ddp-server@3.0.1 20 | diff-sequence@1.1.3 21 | dynamic-import@0.7.4 22 | ecmascript@0.16.9 23 | ecmascript-runtime@0.8.2 24 | ecmascript-runtime-client@0.12.2 25 | ecmascript-runtime-server@0.11.1 26 | ejson@1.1.4 27 | facts-base@1.0.2 28 | fetch@0.1.5 29 | geojson-utils@1.0.12 30 | id-map@1.2.0 31 | inter-process-messaging@0.1.2 32 | lai:collection-extensions@1.0.0 33 | local-test:lai:collection-extensions@1.0.0 34 | localstorage@1.2.1 35 | logging@1.3.5 36 | meteor@2.0.1 37 | meteortesting:browser-tests@1.7.0 38 | meteortesting:mocha@3.2.0 39 | meteortesting:mocha-core@8.3.1-rc300.1 40 | minimongo@2.0.1 41 | modern-browsers@0.1.11 42 | modules@0.20.1 43 | modules-runtime@0.13.2 44 | mongo@2.0.1 45 | mongo-decimal@0.1.4-beta300.7 46 | mongo-dev-server@1.1.1 47 | mongo-id@1.0.9 48 | npm-mongo@4.17.4 49 | ordered-dict@1.2.0 50 | promise@1.0.0 51 | raix:eventemitter@1.0.0 52 | random@1.2.2 53 | rate-limit@1.1.2 54 | react-fast-refresh@0.2.9 55 | reactive-var@1.0.13 56 | reload@1.3.2 57 | retry@1.1.1 58 | routepolicy@1.1.2 59 | socket-stream-client@0.5.3 60 | tracker@1.3.4 61 | typescript@5.4.3 62 | underscore@1.6.4 63 | url@1.3.3 64 | webapp@2.0.1 65 | webapp-hashing@1.1.2 66 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | /* eslint-env meteor */ 2 | 3 | Package.describe({ 4 | name: 'lai:collection-extensions', 5 | version: '1.0.0', 6 | // Brief, one-line summary of the package. 7 | summary: 'Safely and easily extend the Mongo.Collection constructor with custom functionality.', 8 | // URL to the Git repository containing the source code for this package. 9 | git: 'https://github.com/Meteor-Community-Packages/meteor-collection-extensions.git', 10 | // By default, Meteor will default to using README.md for documentation. 11 | // To avoid submitting documentation, set this field to null. 12 | documentation: 'README.md' 13 | }) 14 | 15 | Package.onUse(function (api) { 16 | api.versionsFrom(['2.3', '2.8.0', '3.0.1']) 17 | 18 | api.use([ 19 | 'ecmascript', 20 | 'mongo' 21 | ]) 22 | 23 | api.use(['accounts-base'], ['client', 'server'], { weak: true }) 24 | 25 | api.mainModule('collection-extensions.js') 26 | }) 27 | 28 | Package.onTest(function (api) { 29 | api.versionsFrom(['2.3', '2.8.0', '3.0.1']) 30 | api.use([ 31 | 'ecmascript', 32 | 'coffeescript', 33 | 'accounts-base', 34 | 'tracker', 35 | 'mongo', 36 | 'aldeed:simple-schema@1.13.1', 37 | 'meteortesting:mocha@3.2.0', 38 | // 'matb33:collection-hooks@1.4.0-beta300.0', 39 | // 'ongoworks:security@1.0.1', 40 | // 'cfs:standard-packages', 41 | // 'cfs:gridfs', 42 | 'aldeed:collection2@4.0.0', 43 | 'dburles:mongo-collection-instances@1.0.0', 44 | // 'lai:collection-extensions@1.0.0', 45 | ]) 46 | api.addFiles([ 47 | 'tests/functions.js', 48 | 'tests/tests.js', 49 | 'tests/tests.coffee' 50 | ]) 51 | }) 52 | -------------------------------------------------------------------------------- /test-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-proxy", 3 | "private": true, 4 | "scripts": { 5 | "start": "meteor run", 6 | "setup": "mkdir -p packages && ln -sfn ../../ ./packages/meteor-collection-extensions", 7 | "lint": "standardx -v ../ | snazzy", 8 | "lint:fix": "standardx -v --fix ../ | snazzy", 9 | "test": "METEOR_PACKAGE_DIRS='../:../../' TEST_BROWSER_DRIVER=puppeteer meteor test-packages --once --raw-logs --driver-package meteortesting:mocha ../", 10 | "test:watch": "METEOR_PACKAGE_DIRS='../:../../' TEST_BROWSER_DRIVER=puppeteer TEST_WATCH=1 meteor test-packages --raw-logs --driver-package meteortesting:mocha ../" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.23.7", 14 | "meteor-node-stubs": "^1.2.7" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.23.6", 18 | "@babel/eslint-parser": "^7.23.3", 19 | "chai": "^4.3.10", 20 | "eslint-config-standard": "^16.0.3", 21 | "puppeteer": "^23.1.0", 22 | "snazzy": "^9.0.0", 23 | "standardx": "^7.0.0" 24 | }, 25 | "babel": {}, 26 | "standardx": { 27 | "globals": [ 28 | "AutoForm", 29 | "arrayTracker", 30 | "globalDefaultTemplate", 31 | "defaultTypeTemplates", 32 | "deps" 33 | ], 34 | "ignore": [ 35 | "**/test-proxy/" 36 | ] 37 | }, 38 | "eslintConfig": { 39 | "parser": "@babel/eslint-parser", 40 | "parserOptions": { 41 | "sourceType": "module", 42 | "allowImportExportEverywhere": false 43 | }, 44 | "rules": { 45 | "brace-style": [ 46 | "error", 47 | "stroustrup", 48 | { 49 | "allowSingleLine": true 50 | } 51 | ] 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test-proxy/.meteor/versions: -------------------------------------------------------------------------------- 1 | aldeed:collection2@4.0.3 2 | aldeed:simple-schema@1.13.1 3 | allow-deny@2.0.0 4 | autoupdate@2.0.0 5 | babel-compiler@7.11.0 6 | babel-runtime@1.5.2 7 | base64@1.0.13 8 | binary-heap@1.0.12 9 | blaze-tools@2.0.0 10 | boilerplate-generator@2.0.0 11 | caching-compiler@2.0.0 12 | caching-html-compiler@2.0.0 13 | callback-hook@1.6.0 14 | check@1.4.2 15 | core-runtime@1.0.0 16 | ddp@1.4.2 17 | ddp-client@3.0.1 18 | ddp-common@1.4.4 19 | ddp-server@3.0.1 20 | diff-sequence@1.1.3 21 | dynamic-import@0.7.4 22 | ecmascript@0.16.9 23 | ecmascript-runtime@0.8.2 24 | ecmascript-runtime-client@0.12.2 25 | ecmascript-runtime-server@0.11.1 26 | ejson@1.1.4 27 | es5-shim@4.8.1 28 | facts-base@1.0.2 29 | fetch@0.1.5 30 | geojson-utils@1.0.12 31 | hot-code-push@1.0.5 32 | html-tools@2.0.0 33 | htmljs@2.0.1 34 | id-map@1.2.0 35 | inter-process-messaging@0.1.2 36 | launch-screen@2.0.1 37 | logging@1.3.5 38 | meteor@2.0.1 39 | meteor-base@1.5.2 40 | minifier-css@2.0.0 41 | minifier-js@3.0.0 42 | minimongo@2.0.1 43 | mobile-experience@1.1.2 44 | mobile-status-bar@1.1.1 45 | modern-browsers@0.1.11 46 | modules@0.20.1 47 | modules-runtime@0.13.2 48 | mongo@2.0.1 49 | mongo-decimal@0.1.4-beta300.7 50 | mongo-dev-server@1.1.1 51 | mongo-id@1.0.9 52 | npm-mongo@4.17.4 53 | ordered-dict@1.2.0 54 | promise@1.0.0 55 | raix:eventemitter@1.0.0 56 | random@1.2.2 57 | react-fast-refresh@0.2.9 58 | reactive-var@1.0.13 59 | reload@1.3.2 60 | retry@1.1.1 61 | routepolicy@1.1.2 62 | shell-server@0.6.0 63 | socket-stream-client@0.5.3 64 | spacebars-compiler@2.0.0 65 | standard-minifier-css@1.9.3 66 | standard-minifier-js@3.0.0 67 | static-html@1.3.3 68 | templating-tools@2.0.0 69 | tracker@1.3.4 70 | typescript@5.4.3 71 | underscore@1.6.4 72 | webapp@2.0.1 73 | webapp-hashing@1.1.2 74 | -------------------------------------------------------------------------------- /.github/workflows/testsuite.yml: -------------------------------------------------------------------------------- 1 | # the test suite runs the tests (headless, server+client) for multiple Meteor releases 2 | name: Test suite 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | lint: 11 | name: Javascript standard lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: setup node 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | 22 | - name: cache dependencies 23 | uses: actions/cache@v4 24 | with: 25 | path: ~/.npm 26 | key: ${{ runner.os }}-node-20-${{ hashFiles('**/package-lock.json') }} 27 | restore-keys: | 28 | ${{ runner.os }}-node-20- 29 | 30 | - run: cd test-proxy && npm ci && npm run setup && npm run lint 31 | 32 | test: 33 | name: Meteor package tests 34 | needs: [lint] 35 | runs-on: ubuntu-latest 36 | strategy: 37 | matrix: 38 | meteorRelease: 39 | - '3.0' 40 | steps: 41 | - name: Checkout code 42 | uses: actions/checkout@v4 43 | 44 | - name: Install Node.js 45 | uses: actions/setup-node@v4 46 | with: 47 | node-version: 20 48 | 49 | - name: Setup meteor ${{ matrix.meteorRelease }} 50 | uses: meteorengineer/setup-meteor@v1 51 | with: 52 | meteor-release: ${{ matrix.meteorRelease }} 53 | 54 | - name: cache dependencies 55 | uses: actions/cache@v4 56 | with: 57 | path: ~/.npm 58 | key: ${{ runner.os }}-node-20-${{ hashFiles('**/package-lock.json') }} 59 | restore-keys: | 60 | ${{ runner.os }}-node-20- 61 | 62 | - name: run tests 63 | run: | 64 | cd test-proxy 65 | npm ci 66 | npm run setup 67 | npm run test 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # IDE specific 107 | .idea 108 | .vscode 109 | .atom -------------------------------------------------------------------------------- /collection-extensions.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Mongo } from 'meteor/mongo' 3 | 4 | // The collection extensions namespace 5 | export const CollectionExtensions = {} // eslint-disable-line no-global-assign 6 | 7 | // Stores all the collection extensions 8 | CollectionExtensions._extensions = [] 9 | 10 | // This is where you would add custom functionality to 11 | // Mongo.Collection/Meteor.Collection 12 | CollectionExtensions.addExtension = function (customFunction) { 13 | if (typeof customFunction !== 'function') { 14 | throw new Meteor.Error( 15 | 'collection-extension-wrong-argument', 16 | 'You must pass a function into CollectionExtensions.addExtension().') 17 | } 18 | CollectionExtensions._extensions.push(customFunction) 19 | // If Meteor.users exists, apply the extension right away 20 | if (typeof Meteor.users !== 'undefined') { 21 | applyExtension(Meteor.users, customFunction, ['users']) 22 | .catch(e => console.error(e)) 23 | } 24 | } 25 | 26 | // Utility function to add a prototype function to your 27 | // Meteor/Mongo.Collection object 28 | CollectionExtensions.addPrototype = function (name, customFunction, options = {}) { 29 | if (typeof name !== 'string') { 30 | throw new Meteor.Error( 31 | 'collection-extension-wrong-argument', 32 | 'You must pass a string as the first argument into CollectionExtensions.addPrototype().') 33 | } 34 | if (typeof customFunction !== 'function') { 35 | throw new Meteor.Error( 36 | 'collection-extension-wrong-argument', 37 | 'You must pass a function as the second argument into CollectionExtensions.addPrototype().') 38 | } 39 | 40 | const target = 41 | typeof Mongo !== 'undefined' 42 | ? Mongo.Collection 43 | : Meteor.Collection 44 | const descriptor = { 45 | value: customFunction, 46 | configurable: !!options.configurable, 47 | enumerable: !!options.enumerable, 48 | writable: !!options.writable 49 | } 50 | Object.defineProperty(target.prototype, name, descriptor) 51 | } 52 | 53 | // This is used to reassign the prototype of unfortunately 54 | // and unstoppably already instantiated Mongo instances 55 | // i.e. Meteor.users 56 | function reassignCollectionPrototype (instance, constr) { 57 | const hasSetPrototypeOf = typeof Object.setPrototypeOf === 'function' 58 | 59 | if (!constr) constr = typeof Mongo !== 'undefined' ? Mongo.Collection : Meteor.Collection 60 | 61 | // __proto__ is not available in < IE11 62 | // Note: Assigning a prototype dynamically has performance implications 63 | if (hasSetPrototypeOf) { 64 | Object.setPrototypeOf(instance, constr.prototype) 65 | } else if (instance.__proto__) { // eslint-disable-line no-proto 66 | // eslint-disable-next-line no-proto 67 | instance.__proto__ = constr.prototype 68 | } 69 | } 70 | 71 | // This monkey-patches the Collection constructor 72 | // This code is the same monkey-patching code 73 | // that matb33:collection-hooks uses, which works pretty nicely 74 | function wrapCollection (ns, as) { 75 | const constructor = as.Collection 76 | const proto = ns.Collection.prototype 77 | 78 | ns.Collection = function (...args) { 79 | const collection = this 80 | const extensions = { 81 | onError: e => console.error(e), 82 | onComplete: () => {} 83 | } 84 | 85 | const extIndex = args.findIndex(entry => entry && typeof entry === 'object' && hasProp(entry, 'extensions')) 86 | if (extIndex > -1) { 87 | const options = args[extIndex] 88 | extensions.onError = options.extensions.onError ?? extensions.onError 89 | extensions.onComplete = options.extensions.onComplete ?? extensions.onComplete 90 | delete options.extensions 91 | } 92 | 93 | const ret = constructor.apply(collection, args) 94 | // This is where all the collection extensions get processed 95 | // We can only catch the Promise, since there is no async constructor 96 | // so we can't await here 97 | processCollectionExtensions(collection, args) 98 | .then(() => extensions.onComplete(collection)) 99 | .catch(e => extensions.onError(e, collection)) 100 | return ret 101 | } 102 | 103 | ns.Collection.prototype = proto 104 | ns.Collection.prototype.constructor = ns.Collection 105 | 106 | for (const prop in constructor) { 107 | if (hasProp(constructor, prop)) { 108 | ns.Collection[prop] = constructor[prop] 109 | } 110 | } 111 | } 112 | 113 | const hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop) 114 | 115 | /** 116 | * Applies every registered extension to the given collection. 117 | * @param collection 118 | * @param args 119 | * @returns {Promise} 120 | */ 121 | async function processCollectionExtensions (collection, args = []) { 122 | for (const ext of CollectionExtensions._extensions) { 123 | await applyExtension(collection, ext, args) 124 | } 125 | } 126 | 127 | /** 128 | * Applies a single extension to a collection 129 | * @param collection 130 | * @param fn 131 | * @param args 132 | * @returns {Promise} 133 | */ 134 | async function applyExtension (collection, fn, args = []) { 135 | // eslint-disable-next-line no-useless-call 136 | await fn.call(null, collection, ...args) 137 | } 138 | 139 | if (typeof Mongo !== 'undefined') { 140 | wrapCollection(Mongo, Mongo) 141 | Meteor.Collection = Mongo.Collection 142 | } else { 143 | wrapCollection(Meteor, Meteor) 144 | } 145 | 146 | if (typeof Meteor.users !== 'undefined') { 147 | // Ensures that Meteor.users instanceof Mongo.Collection 148 | reassignCollectionPrototype(Meteor.users) 149 | } 150 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | > Any contribution to this repository is highly appreciated! 4 | 5 | 6 | 7 | 8 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 9 | 10 | - [Introduction](#introduction) 11 | - [Setup development env](#setup-development-env) 12 | - [Clone project and create a new branch to work on](#clone-project-and-create-a-new-branch-to-work-on) 13 | - [Initialize test app](#initialize-test-app) 14 | - [Development toolchain](#development-toolchain) 15 | - [Linter](#linter) 16 | - [Tests](#tests) 17 | - [Once](#once) 18 | - [Watch](#watch) 19 | - [Coverage](#coverage) 20 | - [Open a pull request](#open-a-pull-request) 21 | - [Code review process](#code-review-process) 22 | - [Questions](#questions) 23 | - [Credits](#credits) 24 | 25 | 26 | 27 | ## Introduction 28 | 29 | First, thank you for considering contributing to this project! 30 | It's people like you that make the open source community such a great community! 😊 31 | 32 | We welcome any type of contribution, not only code. You can help with 33 | 34 | - **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open) 35 | - **Marketing**: writing blog posts, howto's, printing stickers, ... 36 | - **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ... 37 | - **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them. 38 | - **Money**: we [welcome financial contributions](https://github.com/https://github.com/Meteor-Community-Packages). 39 | 40 | ## Setup development env 41 | 42 | ### Clone project and create a new branch to work on 43 | 44 | First, clone this repository and create a new branch to work on. 45 | Branch names should follow the GitFlow standard and start with a descriptive prefix of their intended outcome, for example: 46 | 47 | - `feature/` for features 48 | - `fix/` for general fixes 49 | 50 | Then the name of the branch should describe the purpose of the branch and potentially reference the issue number it is solving. 51 | 52 | ```shell 53 | $ git clone git@github.com:Meteor-Community-Packages/meteor-collection-extensions.git 54 | $ cd meteor-collection-extensions 55 | $ git checkout -b fix/some-issue 56 | ``` 57 | 58 | ### Initialize test app 59 | 60 | We use a proxy Meteor application to run our tests and handle coverage etc. 61 | This app contains several npm scripts to provide the complete toolchain that is required 62 | for your development and testing needs. 63 | 64 | The setup is very easy. Go into the `test-proxy` directory, install dependencies and link 65 | the package: 66 | 67 | ```shell 68 | $ cd test-proxy 69 | $ meteor npm install 70 | $ meteor npm run setup # this is important for the tools to work! 71 | ``` 72 | 73 | ## Development toolchain 74 | 75 | The `test-proxy` comes with some builtin scripts you can utilize during your development. 76 | They will also be picked up by our CI during pull requests. 77 | Therefore, it's a good call for you, that if they pass or fail, the CI will do so, too. 78 | 79 | **Note: all tools require the npm `setup` script has been executed at least once!** 80 | 81 | ### Linter 82 | 83 | We use `standard` as our linter. You can run either the linter or use it's autofix feature for 84 | the most common issues: 85 | 86 | ```shell 87 | # in test-proxy 88 | $ meteor npm run lint # show only outputs 89 | $ meteor npm run lint:fix # with fixes + outputs 90 | ``` 91 | 92 | ### Tests 93 | 94 | We provide three forms of tests: once, watch, coverage 95 | 96 | #### Once 97 | 98 | Simply runs the test suite once, without coverage collection: 99 | 100 | ```shell 101 | $ meteor npm run test 102 | ``` 103 | 104 | #### Watch 105 | 106 | Runs the test suite in watch mode, good to use during active development, where your changes 107 | are picked up automatically to re-run the tests: 108 | 109 | ```shell 110 | $ meteor npm run test:watch 111 | ``` 112 | 113 | #### Coverage 114 | 115 | Runs the test suite once, including coverage report generation. 116 | Generates an html and json report output. 117 | 118 | ```shell 119 | $ meteor npm run test:coverage 120 | $ meteor npm run report # summary output in console 121 | ``` 122 | 123 | If you want to watch the HTML output to find (un)covered lines, open 124 | the file at `test-proxy/.coverage/index.html` in your browser. 125 | 126 | ## Open a pull request 127 | 128 | If you open a pull request, please make sure the following requirements are met: 129 | 130 | - the `lint` script is passing 131 | - the `test` script is passing 132 | - your contribution is on point and solves one issue (not multiple) 133 | - your commit messages are descriptive and informative 134 | - complex changes are documented in the code with comments or jsDoc-compatible documentation 135 | 136 | Please understand, that there will be a review process and your contribution 137 | might require changes before being merged. This is entirely to ensure quality and is 138 | never used as a personal offense. 139 | 140 | 141 | ## Code review process 142 | 143 | The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in 144 | smaller chunks that are easier to review and merge. 145 | It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? 146 | 147 | ## Questions 148 | 149 | If you have any questions, [create an issue](https://github.com/Meteor-Community-Packages/meteor-simple-schema/issues) 150 | (protip: do a quick search first to see if someone else didn't ask the same question before!). 151 | 152 | ## Credits 153 | 154 | Thank you to all the people who have already contributed to this project: 155 | 156 | 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Meteor Collection Extensions 2 | 3 | [![Meteor Community Package](https://img.shields.io/badge/Meteor-Package-green?logo=meteor&logoColor=white)](https://meteor.com) 4 | [![Test suite](https://github.com/Meteor-Community-Packages/meteor-collection-extensions/actions/workflows/testsuite.yml/badge.svg)](https://github.com/Meteor-Community-Packages/meteor-collection-extensions/actions/workflows/testsuite.yml) 5 | [![CodeQL](https://github.com/Meteor-Community-Packages/meteor-collection-extensions/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Meteor-Community-Packages/meteor-collection-extensions/actions/workflows/github-code-scanning/codeql) 6 | [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 7 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 8 | 9 | > Harry Adel has [integrated](https://github.com/meteor/meteor/pull/13830) this package into MeteorJS core starting with version 3.4. We recommend that you switch to the now official approach. 10 | 11 | This package gives you utility functions to extend your `Mongo.Collection` instances in (hopefully) the safest, 12 | easiest and coolest way. 13 | If you want to create a package that monkey-patches the `Mongo.Collection` constructor, you'll need this package. I am striving for this package to be a third-party official way of monkey-patching `Mongo.Collection` until, 14 | well, Meteor decides to create a core functionality to properly extend it. 15 | 16 | ## Breaking changes in 1.0.0, please keep reading! 17 | 18 | - Starting with v. 1.0.0, this package requires Meteor >= 3.0! 19 | - The module needs to be imported **explicitly**, it's not a global anymore! 20 | - All extensions will have to use `collection` as first param, instead of `this`: 21 | 22 | ```js 23 | import { CollectionExtensions } from './collection-extensions' 24 | 25 | CollectionExtensions.addExtension(async (collection, name, options) => { 26 | // ... your extension code 27 | }) 28 | ``` 29 | 30 | > This implies extensions have to be updated accordingly 31 | 32 | Furthermore, **extensions may not be applied immediately**. 33 | This is, since extensions are applied during the call of the `Mongo.Collection` 34 | constructor. However, constructors [cannot be async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor#syntax). 35 | We can't therefore be sure, when (async) extensions have been applied, without using a callback: 36 | 37 | ```js 38 | const extensions = { 39 | onComplete: () => {}, 40 | onError: e => console.error(e) 41 | } 42 | // exentsions options will not be passed to 43 | // the original collection constructor! 44 | const MyDocs = new Mongo.Collection('myDocs', { extensions }) 45 | ``` 46 | 47 | This is unfortunate, but a tradeoff between determinism and 48 | compatibility. 49 | 50 | - `addProperty` now uses `Object.definedProperty` under the hood 51 | and allows to pass in property descriptors as third argument, while 52 | the function is always required 53 | 54 | ### Background info 55 | 56 | With the changes of Meteor 3.0 moving to full async, 57 | we now have to resolve the Promises, returned by 58 | Mongo.Collection methods (`insertAsync` etc.). 59 | 60 | 61 | ## Installation 62 | 63 | ``` 64 | meteor add lai:collection-extensions 65 | ``` 66 | 67 | ## Why 68 | 69 | Meteor gives you no easy way to extend the `Mongo.Collection` object, and therefore 70 | package publishers who want to extend its functionality resort 71 | to monkey-patching the `Mongo.Collection` constructor, and sometimes it's not done right. This package seeks to centralize one well-done monkey-patch with the ability to hook into the constructor as many times as possible. See my code. 72 | 73 | I am hoping for all collection-extending package authors to to use this to end the package compatibility issues. In order for this to happen, I will fork major packages like `matb33:collection-hooks`, `sewdn:collection-behaviours` and refactor the code to use this utility package, and run their test suites. 74 | If you want to help, that would be awesome. 75 | 76 | ## API 77 | 78 | #### CollectionExtensions.addExtension(async fn ([collection, name, options]) {}) 79 | 80 | Pass in a (async or standard) function where the arguments are the same as that of when instantiating `Mongo.Collection`. 81 | In addition, you may access the collection instance by using `this`. 82 | 83 | __Very Important:__ You need to make sure your extensions are added before you instantiate your `Mongo.Collection`s or your extensions will not work. Most likely you will only use this when building a custom package. 84 | 85 | #### CollectionExtensions.addPrototype(name, fn (...) {}) 86 | 87 | Pass in the name of the prototype function as well as the function. Yes, I know you can simply just do `Mongo.Collection.prototype.myPrototypeFunction = function (...) {}`, which is fine. One of the things that this function does differently is to check whether you're in an older version of Meteor, in which `Mongo.Collection` doesn't exist but rather `Meteor.Collection` does. __Note:__ If you are a package author that adds/modifies prototypes on the `Mongo.Collection`, this is not so critical for you to use unless you really want backwards-compatibility. 88 | 89 | ## Examples 90 | 91 | The following code recreates [this section of code](https://github.com/dburles/mongo-collection-instances/blob/master/mongo-instances.js#L2-L17) of the `dburles:mongo-collection-instances` using `CollectionExtensions.addExtension(fn)` thereby eliminating the need to monkey-patch the `Mongo.Collection` constructor: 92 | 93 | ```js 94 | import {CollectionExtensions } from 'meteor/lai:collection-extensions' 95 | 96 | const instances = []; 97 | 98 | CollectionExtensions.addExtension((collection, name, options) => { 99 | instances.push({ 100 | name: name, 101 | instance: collection, 102 | options: options 103 | }); 104 | }); 105 | ``` 106 | 107 | The following code overrides `ìnsert` to use `insertAsync` under the hood. 108 | 109 | ```js 110 | import {CollectionExtensions } from 'meteor/lai:collection-extensions' 111 | 112 | CollectionExtensions.addPrototype('insert', async function (doc) { 113 | return this.insertAsync(doc) 114 | }) 115 | ``` 116 | 117 | ## Todo 118 | 119 | Integrate this package into the following packages and test them: 120 | 121 | * [x] [`matb33:collection-hooks`](https://github.com/matb33/meteor-collection-hooks/) ([Refactored and tested with 100% success](https://github.com/rclai/meteor-collection-hooks/tree/collection-extensions)) 122 | * [x] [`dburles:mongo-collection-instances`](https://github.com/dburles/mongo-collection-instances) ([Refactored and tested with 100% success](https://github.com/rclai/mongo-collection-instances/tree/collection-extensions)) ([Now merged into master!](https://github.com/dburles/mongo-collection-instances/commit/7f90911b6a7117cfc62e40b200a0437ea9fb5961)) 123 | * [ ] [`sewdn:collection-behaviours`](https://github.com/Sewdn/meteor-collection-behaviours/) (He didn't write tests but [here's the forked refactor](https://github.com/rclai/meteor-collection-behaviours/tree/collection-extensions) anyway in case you wanted to test) 124 | 125 | Create tests. 126 | 127 | ## Contributing 128 | 129 | If you are interested in using this package in your package, or if you want me to test (or if YOU want to test it yourself) integrating this into your package let me know and I will add it to the Todo list. 130 | 131 | Feedback is welcome. 132 | 133 | ## Future 134 | 135 | Add control over the execution order of each collection extension somehow. 136 | Track collection instances that were prematurely instantiated and apply extensions on demand. 137 | Get Travis CI installed. 138 | 139 | ## License 140 | 141 | MIT, See [license file](./LICENSE) 142 | 143 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { Meteor } from 'meteor/meteor' 3 | import { Mongo } from 'meteor/mongo' 4 | import { assert } from 'chai' 5 | import { Random } from 'meteor/random' 6 | import { insert, inst, clearExtension, asyncTimeout } from './functions' 7 | import SimpleSchema from 'meteor/aldeed:simple-schema' 8 | import { CollectionExtensions } from 'meteor/lai:collection-extensions' 9 | import 'meteor/aldeed:collection2/static' 10 | 11 | const randomName = name => `${name}${Random.id(6)}` 12 | const createCollection = (name, options) => new Mongo.Collection(name, options) 13 | // no extensions should apply to this one 14 | const UnaffectedCollection = createCollection(randomName('unaffected')) 15 | 16 | Meteor.users.allow({ 17 | insert: () => true 18 | }) 19 | 20 | describe('CollectionExtensions', function () { 21 | describe('addExtension', function () { 22 | let extension 23 | 24 | afterEach(() => { 25 | if (extension) { 26 | clearExtension(extension) 27 | } 28 | }) 29 | 30 | it('works alongside dburles:mongo-collection-instances', async function () { 31 | const name = randomName('mongoCollectionInstances') 32 | const Todos = createCollection(name) 33 | 34 | const todosInstance = Mongo.Collection.get(name) 35 | 36 | assert.equal(Todos, todosInstance) 37 | assert.instanceOf(todosInstance, Mongo.Collection) 38 | assert.instanceOf(todosInstance, Meteor.Collection) 39 | 40 | if (Meteor.isServer) { 41 | await insert(todosInstance) 42 | 43 | let todo = await inst(todosInstance) 44 | await todosInstance.updateAsync(todo._id, { 45 | $set: { 46 | title: 'Pick up more stuff' 47 | } 48 | }) 49 | 50 | todo = await inst(todosInstance) 51 | assert.equal(todo.title, 'Pick up more stuff') 52 | } 53 | }) 54 | 55 | // XXX: ongoworks:security is currently out of our reach 56 | // XXX: and we should remove comments, once it's part of MCP 57 | // it('works alongside ongoworks:security', async function () { 58 | // const name = randomName('todos') 59 | // const Todos = createCollection(name) 60 | // 61 | // if (Meteor.isServer) { 62 | // Todos.permit(['insertAsync', 'updateAsync', 'removeAsync']).apply() 63 | // 64 | // await insert(Todos) 65 | // 66 | // let todo = await inst(Todos) 67 | // await Todos.updateAsync(todo._id, { 68 | // $set: { 69 | // title: 'Pick up more stuff' 70 | // } 71 | // }) 72 | // 73 | // todo = await inst(Todos) 74 | // assert.equal(todo.title, 'Pick up more stuff') 75 | // } else { 76 | // assert.equal(Todos.permit, undefined) 77 | // } 78 | // }) 79 | 80 | it('works alongside aldeed:collection2', async function () { 81 | const name = randomName('collection2') 82 | const Todos = createCollection(name) 83 | Todos.attachSchema(new SimpleSchema({ 84 | title: { 85 | type: String 86 | } 87 | }, { 88 | clean: { 89 | autoConvert: false, 90 | filter: false 91 | } 92 | })) 93 | 94 | try { 95 | await Todos.insertAsync({ foo: 'bar' }) 96 | } catch (e) { 97 | const message = `foo is not allowed by the schema in ${name} insertAsync` 98 | assert.equal(e.message, message) 99 | } 100 | }) 101 | 102 | // TODO: uncomment, once collection-hooks is upgraded to 3.0 103 | // it('works alongside matb33:collection-hooks', async function () { 104 | // const Todos = createCollection(randomName('todos')) 105 | // 106 | // Todos.after.update(function () { 107 | // assert.equal(true, true) 108 | // }) 109 | // 110 | // await insert(Todos) 111 | // 112 | // let todo = await inst(Todos) 113 | // await Todos.updateAsync(todo._id, { 114 | // $set: { 115 | // title: 'Pick up more stuff' 116 | // } 117 | // }) 118 | // 119 | // todo = await inst(Todos) 120 | // assert.equal(todo.title, 'Pick up more stuff') 121 | // }) 122 | 123 | // TODO: cfs* are deprecated, check if this can be dropped safely 124 | // it('works alongside cfs:standard-packages + cfs:gridfs', async function () { 125 | // const Todos = createCollection(randomName('todos')) 126 | // const imagesName = randomName('images') 127 | // const createFs = name => new FS.Collection(name, { 128 | // stores: [new FS.Store.GridFS(name)] 129 | // }) 130 | // 131 | // createFs(imagesName) 132 | // 133 | // await insert(Todos) 134 | // 135 | // let todo = await inst(Todos) 136 | // await Todos.updateAsync(todo._id, { 137 | // $set: { 138 | // title: 'Pick up more stuff' 139 | // } 140 | // }) 141 | // 142 | // todo = await inst(Todos) 143 | // assert.equal(todo.title, 'Pick up more stuff') 144 | // }) 145 | 146 | it('inheritance - Shows the db-functions as properties of the prototype', function () { 147 | const Todos = createCollection(randomName('propsCheck')) 148 | const keys = Object.keys(Mongo.Collection.prototype) 149 | assert.instanceOf(Todos, Mongo.Collection) 150 | ;[ 151 | '_maybeSetUpReplication', 152 | 'countDocuments', 153 | 'estimatedDocumentCount', 154 | '_getFindSelector', 155 | '_getFindOptions', 156 | 'find', 157 | 'findOneAsync', 158 | 'findOne', 159 | 'insert', 160 | 'insertAsync', 161 | 'updateAsync', 162 | 'update', 163 | 'removeAsync', 164 | 'remove', 165 | '_isRemoteCollection', 166 | 'upsertAsync', 167 | 'upsert', 168 | 'createIndexAsync', 169 | 'createIndex', 170 | 'dropIndexAsync', 171 | 'dropCollectionAsync', 172 | 'createCappedCollectionAsync', 173 | 'rawCollection', 174 | 'rawDatabase', 175 | 'allow', 176 | 'deny', 177 | '_defineMutationMethods', 178 | '_updateFetch', 179 | '_isInsecure', 180 | '_validatedInsert', 181 | '_validatedUpdate', 182 | '_validatedRemove', 183 | '_callMutatorMethod' 184 | ].forEach(key => assert.include(keys, key)) 185 | }) 186 | 187 | it('instanceof - matches Mongo.Collection', function () { 188 | const collectionName = randomName('instanceOfMongo') 189 | const Test = createCollection(collectionName) 190 | assert.instanceOf(Test, Mongo.Collection) 191 | }) 192 | 193 | it('instanceof - Meteor.Collection matches Mongo.Collection', function () { 194 | const collectionName = randomName('instanceOfMeteor') 195 | const Test = new Meteor.Collection(collectionName) 196 | assert.instanceOf(Test, Mongo.Collection) 197 | }) 198 | 199 | it('instanceof - Meteor.users matches (Mongo/Meteor).Collection', function () { 200 | assert.instanceOf(Meteor.users, Mongo.Collection) 201 | assert.instanceOf(Meteor.users, Meteor.Collection) 202 | }) 203 | 204 | it('instanceof - Mongo.Collection === Mongo.Collection.prototype.constructor', function () { 205 | assert.equal(Mongo.Collection, Mongo.Collection.prototype.constructor) 206 | assert.equal(Meteor.Collection, Mongo.Collection.prototype.constructor) 207 | assert.equal(Meteor.Collection, Meteor.Collection.prototype.constructor) 208 | }) 209 | 210 | it('functionality - Add a collection extension that manipulates a data structure in a standard sync function', async function () { 211 | const registered = new Set() 212 | extension = function countArray (collection) { 213 | registered.add(collection._name) 214 | } 215 | 216 | CollectionExtensions.addExtension(extension) 217 | 218 | // immediately apply to users 219 | assert.isTrue(registered.has('users')) 220 | 221 | // apply to local collection 222 | assert.isFalse(registered.has(null)) 223 | createCollection(null) 224 | await asyncTimeout(25) 225 | assert.isTrue(registered.has(null)) 226 | 227 | // apply to named collection 228 | const name = randomName('simpleArray') 229 | assert.isFalse(registered.has(name)) 230 | createCollection(name) 231 | await asyncTimeout(25) 232 | assert.isTrue(registered.has(name)) 233 | }) 234 | 235 | it('functionality - Add a collection extension that adds initial documents in an async arrow function', async function () { 236 | const COUNT = 3 237 | const prop = randomName('prop') 238 | extension = async (collection) => { 239 | for (let i = 0; i < COUNT; i++) { 240 | const doc = { [prop]: i } 241 | await collection.insertAsync(doc) 242 | } 243 | } 244 | 245 | CollectionExtensions.addExtension(extension) 246 | 247 | const testCollection = async (collection, expectedCount) => { 248 | await asyncTimeout(200) 249 | const cursor = collection.find({ [prop]: { $exists: true } }) 250 | const count = await cursor.countAsync() 251 | assert.equal(count, expectedCount, collection._name) 252 | cursor.forEach(function (doc, index) { 253 | assert.equal(doc[prop], index) 254 | }) 255 | await asyncTimeout(100) 256 | } 257 | 258 | await testCollection(createCollection(null), COUNT) 259 | await testCollection(UnaffectedCollection, 0) 260 | 261 | if (Meteor.isServer) { 262 | // TODO: we need to get this running on the client 263 | await testCollection(Meteor.users, COUNT) 264 | await testCollection(createCollection(randomName('initialDocs')), COUNT) 265 | } 266 | }) 267 | 268 | it('supports a callback that execs when extensions have completed', done => { 269 | extension = () => { 270 | } 271 | CollectionExtensions.addExtension(extension) 272 | const name = randomName('onComplete') 273 | const extensions = { 274 | onError: e => done(e), 275 | onComplete: (collection) => { 276 | assert.equal(collection._name, name) 277 | done() 278 | } 279 | } 280 | createCollection(name, { extensions }) 281 | }) 282 | 283 | it('catches errors and forwards them in an optional callback', done => { 284 | extension = () => { 285 | throw new Error('This is an expected error callback') 286 | } 287 | CollectionExtensions.addExtension(extension) 288 | const name = randomName('onError') 289 | const extensions = { 290 | onError: (e, collection) => { 291 | assert.equal(e.message, 'This is an expected error callback') 292 | assert.equal(collection._name, name) 293 | done() 294 | } 295 | } 296 | createCollection(name, { extensions }) 297 | }) 298 | }) 299 | 300 | describe('addPrototype', function () { 301 | it('allows to register a prototype function', async () => { 302 | CollectionExtensions.addPrototype('count', async function () { 303 | return this.find().countAsync() 304 | }) 305 | const name = randomName('proto') 306 | const collection = createCollection(name) 307 | assert.equal(await collection.count(), 0) 308 | 309 | // expect not being enumerable by default 310 | assert.isFalse(Object.keys(collection).includes('count')) 311 | }) 312 | it('allows to override proto functions', async () => { 313 | CollectionExtensions.addPrototype('insert', async function (doc) { 314 | return this.insertAsync(doc) 315 | }) 316 | const name = randomName('proto') 317 | const collection = createCollection(name) 318 | assert.equal(await collection.count(), 0) 319 | 320 | if (Meteor.isServer) { 321 | const promise = collection.insert({ foo: 1 }) 322 | assert.isTrue(typeof promise.then === 'function') 323 | await promise 324 | 325 | assert.equal(await collection.count(), 1) 326 | } 327 | }) 328 | }) 329 | }) 330 | --------------------------------------------------------------------------------