Merge branch 'devel' into doc-release-option

This commit is contained in:
Jan Dvorak
2024-10-29 21:36:43 +01:00
committed by GitHub
249 changed files with 8026 additions and 7522 deletions

View File

@@ -79,7 +79,7 @@ run_save_node_bin: &run_save_node_bin
build_machine_environment: &build_machine_environment
# Specify that we want an actual machine (ala Circle 1.0), not a Docker image.
docker:
- image: meteor/circleci:2023.12.1-android-34-node-18
- image: meteor/circleci:2024.09.11-android-34-node-20
resource_class: large
environment:
# This multiplier scales the waitSecs for selftests.
@@ -345,6 +345,9 @@ jobs:
if [ -f ./tmp/test-groups/2.txt ]; then TEST_GROUP=$(<./tmp/test-groups/2.txt); elif [ -f ./tmp/test-groups/0.txt ]; then TEST_GROUP=XXXXX; else TEST_GROUP='^co[n-z]'; fi
echo $TEST_GROUP;
eval $PRE_TEST_COMMANDS;
export PATH="/home/circleci/.sdkman/candidates/gradle/8.7/bin:${PATH}"
java --version
gradle --version
./meteor self-test \
"$TEST_GROUP" \
--retries ${METEOR_SELF_TEST_RETRIES} \
@@ -750,7 +753,7 @@ jobs:
Docs:
docker:
# This Node version should match that in the meteor/docs CircleCI config.
- image: meteor/circleci:2023.12.1-android-34-node-20
- image: meteor/circleci:2024.09.11-android-34-node-20
resource_class: large
environment:
CHECKOUT_METEOR_DOCS: /home/circleci/test_docs
@@ -759,7 +762,13 @@ jobs:
- run:
name: Cloning "meteor" Repository's current branch
command: |
git clone --branch $CIRCLE_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
if [[ -n "$CIRCLE_PULL_REQUEST" ]]; then
PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | sed 's|.*/pull/\([0-9]*\)|\1|')
PR_BRANCH=$(curl -s https://api.github.com/repos/meteor/meteor/pulls/$PR_NUMBER | jq -r .head.ref)
git clone --branch $PR_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
else
git clone --branch $CIRCLE_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
fi
# Run almost the same steps the meteor/docs repository runs, minus deploy.
- run:
name: Generating Meteor documentation for JSDoc testing

12
.envrc
View File

@@ -15,8 +15,8 @@ function @meteor {
"$ROOT_DIR/meteor" "$@"
}
function @test-package {
@meteor test-packages "$@" --exclude-archs=web.browser.legacy,web.cordova
function @get-ready {
@meteor --get-ready
}
function @test-packages {
@@ -36,10 +36,18 @@ function @generate-dev-bundle {
"$ROOT_DIR/scripts/generate-dev-bundle.sh"
}
function @init-submodule {
git submodule update --init --recursive
}
#################
# Documentation #
#################
function @docs-start {
npm run docs:dev --prefix "$ROOT_DIR/v3-docs/docs"
}
function @docs-migration-start {
npm run docs:dev --prefix "$ROOT_DIR/v3-docs/v3-migration-docs"
}

4
.github/labeler.yml vendored
View File

@@ -115,6 +115,10 @@ Project:Dynamic Import:
Project:Docs:
- docs/**/*
- v3-docs/**/*
Project:Guide:
- guide/**/*
github_actions:
- ./github/**/*

View File

@@ -6,11 +6,10 @@ on:
- opened
- reopened
- synchronize
branches:
- release-3.0
push:
branches:
- release-3.0
- devel
- 2.x.x
env:
METEOR_PRETTY_OUTPUT: 0

View File

@@ -72,7 +72,7 @@ Current Core Team:
### Tracking project work
Right now, the best place to track the work being done on Meteor is to take a look at the latest release milestone [here](https://github.com/meteor/meteor/milestones). Also, the [Meteor Roadmap](https://docs.meteor.com/roadmap.html) contains high-level information on the current priorities of the project.
Right now, the best place to track the work being done on Meteor is to take a look at the latest release milestone [here](https://github.com/meteor/meteor/milestones). Also, the [Meteor Roadmap](https://docs.meteor.com/about/roadmap.html) contains high-level information on the current priorities of the project.
## Reporting a bug in Meteor
<a name="reporting-bug"></a>
@@ -134,7 +134,7 @@ for more details on proposing changes to core code.
Feature requests are tracked in the [Discussions](https://github.com/meteor/meteor/discussions).
Meteor is a big project with [many sub-projects](https://github.com/meteor/meteor/tree/devel/packages).
Community is welcome to help in all the sub-projects. We use our [roadmap](https://docs.meteor.com/roadmap.html) to communicate the high-level features we're currently prioritizing.
Community is welcome to help in all the sub-projects. We use our [roadmap](https://docs.meteor.com/about/roadmap.html) to communicate the high-level features we're currently prioritizing.
Every additional feature adds a maintenance cost in addition to its value. This
cost starts with the work of writing the feature or reviewing a community pull
@@ -207,7 +207,7 @@ For more information about how to work with Meteor core, take a look at the [Dev
### Proposing your change
You'll have the best chance of getting a change into core if you can build consensus in the community for it or if it is listed in the [roadmap](https://docs.meteor.com/roadmap.html). Start by creating a well specified Discussion [here](https://github.com/meteor/meteor/discussions).
You'll have the best chance of getting a change into core if you can build consensus in the community for it or if it is listed in the [roadmap](https://docs.meteor.com/about/roadmap.html). Start by creating a well specified Discussion [here](https://github.com/meteor/meteor/discussions).
Help drive discussion and advocate for your feature on the Github ticket (and perhaps the forums). The higher the demand for the feature and the greater the clarity of it's specification will determine the likelihood of a core contributor prioritizing your feature by flagging it with the `ready` label.

View File

@@ -2,4 +2,4 @@
This content was moved to [history.md](./docs/history.md).
Previously the changelog was available to be edited here but it was always published in [https://docs.meteor.com/changelog.html](https://docs.meteor.com/changelog.html).
Previously the changelog was available to be edited here but it was always published in [https://docs.meteor.com/history.html](https://docs.meteor.com/history.html).

View File

@@ -10,8 +10,10 @@
[![Travis CI Status](https://api.travis-ci.com/meteor/meteor.svg?branch=devel)](https://app.travis-ci.com/github/meteor/meteor)
[![CircleCI Status](https://circleci.com/gh/meteor/meteor.svg?style=svg)](https://app.circleci.com/pipelines/github/meteor/meteor?branch=devel)
[![built with Meteor](https://img.shields.io/badge/Meteor-3.0.1-green?logo=meteor&logoColor=white)](https://meteor.com)
[![built with Meteor](https://img.shields.io/badge/Meteor-3.0.3-green?logo=meteor&logoColor=white)](https://meteor.com)
![node-current](https://img.shields.io/node/v/meteor)
![Discord](https://img.shields.io/discord/1247973371040239676)
![Twitter Follow](https://img.shields.io/twitter/follow/meteorjs?style=social)
</div>
@@ -22,7 +24,7 @@ Meteor is an **ultra-simple** environment for building **modern** web applicatio
<hr>
- [Official Website](https://www.meteor.com)
- [Installation](https://www.meteor.com/developers/install)
- [Installation](https://docs.meteor.com/about/install.html)
- [Documentation](https://docs.meteor.com/#/full/)
<hr>
@@ -49,7 +51,7 @@ Use the same code whether youre developing for web, iOS, Android, or desktop
How about trying a tutorial to get started with your favorite technology?
| [<img align="left" width="25" src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg"> React](https://react-tutorial.meteor.com/) |
| [<img align="left" width="25" src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg"> React](https://docs.meteor.com/tutorials/react/) |
| - |
| [<img align="left" width="25" src="https://progsoft.net/images/blaze-css-icon-3e80acb3996047afd09f1150f53fcd78e98c1e1b.png"> Blaze](https://blaze-tutorial.meteor.com/) |
| [<img align="left" width="25" src="https://vuejs.org/images/logo.png"> Vue](https://vue-tutorial.meteor.com/) |
@@ -84,7 +86,7 @@ meteor
* Deploy on [Meteor Cloud](https://www.meteor.com/cloud)
* Discuss on [Forums](https://forums.meteor.com/)
* Join the Meteor community Slack by clicking this [invite link](https://join.slack.com/t/meteor-community/shared_invite/enQtODA0NTU2Nzk5MTA3LWY5NGMxMWRjZDgzYWMyMTEyYTQ3MTcwZmU2YjM5MTY3MjJkZjQ0NWRjOGZlYmIxZjFlYTA5Mjg4OTk3ODRiOTc).
* Join the Meteor Discord by clicking this [invite link](https://discord.gg/hZkTCaVjmT).
* Announcement list. Subscribe in the [footer](https://www.meteor.com/).
@@ -105,4 +107,4 @@ To uninstall Meteor:
rm -rf ~/.meteor
sudo rm /usr/local/bin/meteor
```
To find more information about installation, [read here](https://docs.meteor.com/install.html#uninstall).
To find more information about installation, [read here](https://docs.meteor.com/about/install.html#uninstall).

View File

@@ -1,18 +0,0 @@
hexo.extend.filter.register('after_render:html', function(html) {
const scriptTag = `
<script async
src="https://widget.kapa.ai/kapa-widget.bundle.js"
data-website-id="64051b0e-d79f-4fe7-b3ca-ff5c84075693"
data-project-name="Meteor"
data-project-color="#101926"
data-project-logo="https://avatars.githubusercontent.com/u/789528?s=200&v=4”
data-modal-disclaimer=“This is a custom LLM for answering questions about Meteor. Answers are based on the contents of the docs, answered forum posts, YouTube videos and GitHub issues. Please note that answers are generated by AI and may not be fully accurate, so please use your best judgement."
></script>
`.trim();
if (html.indexOf('</body>') !== -1) {
return html.replace('</body>', scriptTag + '</body>');
}
return html;
});

View File

@@ -0,0 +1,35 @@
/* global hexo */
hexo.extend.filter.register('after_render:html', function (str) {
const warningMessage = `
<div class="warning-banner">
<p>
⚠️ You're browsing the documentation for an old version of Meteor.js.
Check out the <a href="https://docs.meteor.com" target="_blank">v3 docs</a> and <a href="https://v3-migration-docs.meteor.com/" target="_blank">migration guide</a>.
</p>
</div>
`;
const css = `
<style>
.warning-banner {
text-align: center;
background-color: #fff3cd;
border: 1px solid #ffeeba;
color: #856404;
margin-bottom: 20px;
}
.warning-banner a {
color: #0056b3;
text-decoration: underline;
}
.warning-banner a:hover {
color: #003d82;
}
</style>
`;
const injectedContent = css + warningMessage;
return str.replace(/<div class="content">/, `<div class="content" data-injected>${injectedContent}`);
});

View File

@@ -837,6 +837,7 @@ const handle = cursor.observeChanges({
setTimeout(() => handle.stop(), 5000);
```
{% apibox "Mongo.getCollection" %}
{% apibox "Mongo.ObjectID" %}
`Mongo.ObjectID` follows the same API as the [Node MongoDB driver

View File

@@ -2,7 +2,7 @@
title: Docs
---
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3.0, currently in its Release Candidate version, runs on Node.js v20. For more information, please consult our [migration guide](https://guide.meteor.com/3.0-migration.html).
> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/).
<!-- XXX: note that this content is somewhat duplicated on the guide, and should be updated in parallel -->
<h2 id="what-is-meteor">What is Meteor?</h2>

View File

@@ -8,7 +8,7 @@ You need to install the Meteor command line tool to create, run, and manage your
<h3 id="prereqs-node">Node.js version</h3>
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3.0, currently in its Release Candidate version, runs on Node.js v20. For more information, please consult our [migration guide](https://guide.meteor.com/3.0-migration.html).
> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/).
- Node.js version >= 10 and <= 14 is required.
- We recommend you using [nvm](https://github.com/nvm-sh/nvm) or [Volta](https://volta.sh/) for managing Node.js versions.

View File

@@ -104,7 +104,7 @@ This example from the Todos app defines a schema with a few simple rules:
3. We specify the `incompleteCount` is a number, which on insertion is set to `0` if not otherwise specified.
4. We specify that the `userId`, which is optional, must be a string that looks like the ID of a user document.
We're using the SimpleSchema for Meteor related funcitonality, like IDs, but we encourage you to create custom regEx expressions for security reasons, for fields like `email` or `name`. Check out the [Simple Schema docs](https://github.com/longshotlabs/simpl-schema#regex) for more information.
We're using the SimpleSchema for Meteor related functionality, like IDs, but we encourage you to create custom regEx expressions for security reasons, for fields like `email` or `name`. Check out the [Simple Schema docs](https://github.com/longshotlabs/simpl-schema#regex) for more information.
We attach the schema to the namespace of `Lists` directly, which allows us to check objects against this schema directly whenever we want, such as in a form or [Method](methods.html). In the [next section](#schemas-on-write) we'll see how to use this schema automatically when writing to the collection.

View File

@@ -3,7 +3,7 @@ title: Introduction
description: This is the guide for using Meteor, a full-stack JavaScript platform for developing modern web and mobile applications.
---
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3.0, currently in its Release Candidate version, runs on Node.js v20. For more information, please consult our [migration guide](https://guide.meteor.com/3.0-migration.html).
> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/).
<!-- XXX: note that this content is somewhat duplicated on the docs, and should be updated in parallel -->
<h2 id="what-is-meteor">What is Meteor?</h2>

View File

@@ -171,35 +171,35 @@ updateText.run.call({ userId: 'abcd' }, {
As you can see, this approach to calling Methods results in a better development workflow - you can more easily deal with the different parts of the Method separately and test your code without having to deal with Meteor internals. But this approach requires you to write a lot of boilerplate on the Method definition side.
<h3 id="validated-method">Advanced Methods with mdg:validated-method</h3>
<h3 id="jam-method">Advanced Methods with jam:method</h3>
To alleviate some of the boilerplate that's involved in correct Method definitions, we've published a wrapper package called `mdg:validated-method` that does most of this for you. Here's the same Method as above, but defined with the package:
To alleviate some of the boilerplate that's involved in correct Method definitions, you can use a package called `jam:method` that does most of this for you. Here's the same Method as above, but defined with the package:
```js
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { createMethod } from 'meteor/jam:method';
export const updateText = new ValidatedMethod({
export const updateText = createMethod({
name: 'todos.updateText',
validate: new SimpleSchema({
schema: new SimpleSchema({
todoId: { type: String },
newText: { type: String }
}).validator(),
run({ todoId, newText }) {
const todo = Todos.findOne(todoId);
}),
async run({ todoId, newText }) {
const todo = await Todos.findOneAsync(todoId);
if (!todo.editableBy(this.userId)) {
throw new Meteor.Error('todos.updateText.unauthorized',
'Cannot edit todos in a private list that is not yours');
}
Todos.update(todoId, {
Todos.updateAsync(todoId, {
$set: { text: newText }
});
}
});
```
You call it the same way you call the advanced Method above, but the Method definition is significantly simpler. We believe this style of Method lets you clearly see the important parts - the name of the Method sent over the wire, the format of the expected arguments, and the JavaScript namespace by which the Method can be referenced. Validated methods only accept a single argument and a callback function.
You call it the same way you call the advanced Method above, but the Method definition is significantly simpler. We believe this style of Method lets you clearly see the important parts - the name of the Method sent over the wire, the format of the expected arguments, and the JavaScript namespace by which the Method can be referenced.
<h2 id="errors">Error handling</h2>
@@ -227,17 +227,13 @@ When the server was not able to complete the user's desired action because of a
When a Method call fails because the arguments are of the wrong type, it's good to throw a `ValidationError`. This works like `Meteor.Error`, but is a custom constructor that enforces a standard error format that can be read by different form and validation libraries. In particular, if you are calling this Method from a form, throwing a `ValidationError` will make it possible to display nice error messages next to particular fields in the form.
When you use `mdg:validated-method` with `simpl-schema` as demonstrated above, this type of error is thrown for you.
Read more about the error format in the [`mdg:validation-error` docs](https://atmospherejs.com/mdg/validation-error).
<h3 id="handling-errors">Handling errors</h3>
When you call a Method, any errors thrown by it will be returned in the callback. At this point, you should identify which error type it is and display the appropriate message to the user. In this case, it is unlikely that the Method will throw a `ValidationError` or an internal server error, so we will only handle the unauthorized error:
```js
// Call the Method
updateText.call({
updateText({
todoId: '12345',
newText: 'This is a todo item.'
}, (err, res) => {
@@ -261,7 +257,7 @@ We'll talk about how to handle the `ValidationError` in the section on forms bel
<h3 id="throw-stub-exceptions">Errors in Method simulation</h3>
When a Method is called, it usually runs twice---once on the client to simulate the result for Optimistic UI, and again on the server to make the actual change to the database. This means that if your Method throws an error, it will likely fail on the client _and_ the server. For this reason, `ValidatedMethod` turns on undocumented option in Meteor to avoid calling the server-side implementation if the simulation throws an error.
When a Method is called, it usually runs twice---once on the client to simulate the result for Optimistic UI, and again on the server to make the actual change to the database. This means that if your Method throws an error, it will likely fail on the client _and_ the server. For this reason, `jam:method` turns on [an option](https://github.com/jamauro/method#options-for-meteorapplyasync) in Meteor to avoid calling the server-side implementation if the simulation throws an error.
While this behavior is good for saving server resources in cases where a Method will certainly fail, it's important to make sure that the simulation doesn't throw an error in cases where the server Method would have succeeded (for example, if you didn't load some data on the client that the Method needs to do the simulation properly). In this case, you can wrap server-side-only logic in a block that checks for a method simulation:
@@ -283,13 +279,13 @@ const amountRegEx = /^\d*\.(\d\d)?$/;
// This Method encodes the form validation requirements.
// By defining them in the Method, we do client and server-side
// validation in one place.
export const insert = new ValidatedMethod({
export const insert = createMethod({
name: 'Invoices.methods.insert',
validate: new SimpleSchema({
schema: new SimpleSchema({
email: { type: String, regEx: emailRegEx },
description: { type: String, min: 5 },
amount: { type: String, regEx: amountRegEx }
}).validator(),
}),
run(newInvoice) {
// In here, we can be sure that the newInvoice argument is
// validated.
@@ -299,7 +295,7 @@ export const insert = new ValidatedMethod({
'Must be logged in to create an invoice.');
}
Invoices.insert(newInvoice)
Invoices.insertAsync(newInvoice)
}
});
```
@@ -355,7 +351,7 @@ Template.Invoices_newInvoice.events({
amount: event.target.amount.value
};
insert.call(data, (err, res) => {
insert(data, (err, res) => {
if (err) {
if (err.error === 'validation-error') {
// Initialize error object
@@ -434,9 +430,9 @@ If we defined this Method in client and server code, as all Methods should be, a
The client enters a special mode where it tracks all changes made to client-side collections, so that they can be rolled back later. When this step is complete, the user of your app sees their UI update instantly with the new content of the client-side database, but the server hasn't received any data yet.
If an exception is thrown from the Method simulation, then by default Meteor ignores it and continues to step (2). If you are using `ValidatedMethod` or pass a special `throwStubExceptions` option to `Meteor.apply`, then an exception thrown from the simulation will stop the server-side Method from running at all.
If an exception is thrown from the Method simulation, then by default Meteor ignores it and continues to step (2). If you are using `jam:method` or pass a special `throwStubExceptions` [option](https://github.com/jamauro/method#options-for-meteorapplyasync) to `Meteor.apply`, then an exception thrown from the simulation will stop the server-side Method from running at all.
The return value of the Method simulation is discarded, unless the `returnStubValue` option is passed when calling the Method, in which case it is returned to the Method caller. ValidatedMethod passes this option by default.
The return value of the Method simulation is discarded, unless the `returnStubValue` option is passed when calling the Method, in which case it is returned to the Method caller. `jam:method` passes this option by default.
<h4 id="lifecycle-ddp-message">2. A `method` DDP message is sent to the server</h4>

View File

@@ -80,9 +80,9 @@ Meteor.methods({
If someone comes along and passes a non-ID selector like `{}`, they will end up deleting the entire collection.
<h3 id="validated-method">mdg:validated-method</h3>
<h3 id="jam-method">jam:method</h3>
To help you write good Methods that exhaustively validate their arguments, we've written a wrapper package for Methods that enforces argument validation. Read more about how to use it in the [Methods article](methods.html#validated-method). The rest of the code samples in this article will assume that you are using this package. If you aren't, you can still apply the same principles but the code will look a little different.
To help you write good Methods that exhaustively validate their arguments, you can use a community package for Methods that enforces argument validation. Read more about how to use it in the [Methods article](methods.html#jam-method). The rest of the code samples in this article will assume that you are using this package. If you aren't, you can still apply the same principles but the code will look a little different.
<h3 id="user-id-client">Don't pass userId from the client</h3>
@@ -200,7 +200,8 @@ if (Meteor.isServer) {
This will make every Method only callable 5 times per second per connection. This is a rate limit that shouldn't be noticeable by the user at all, but will prevent a malicious script from totally flooding the server with requests. You will need to tune the limit parameters to match your app's needs.
If you're using validated methods, there's an available [ddp-rate-limiter-mixin](https://github.com/nlhuykhang/ddp-rate-limiter-mixin).
If you're using `jam:method`, it comes with built in [rate-limiting](https://github.com/jamauro/method#rate-limiting).
<h2 id="publications">Publications</h2>

2
meteor
View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
BUNDLE_VERSION=20.15.1.1
BUNDLE_VERSION=20.18.0.1
# OS Check. Put here because here is where we download the precompiled

View File

@@ -9,7 +9,7 @@ var packageJson = {
private: true,
dependencies: {
promise: "8.1.0",
"@meteorjs/reify": "0.24.0",
"@meteorjs/reify": "0.25.3",
"@babel/parser": "7.17.0",
"@types/underscore": "1.11.4",
underscore: "1.13.6",

View File

@@ -10,13 +10,13 @@ var packageJson = {
dependencies: {
// Explicit dependency because we are replacing it with a bundled version
// and we want to make sure there are no dependencies on a higher version
npm: "10.7.0",
npm: "10.8.2",
pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8",
"node-gyp": "9.4.0",
"@mapbox/node-pre-gyp": "1.0.11",
typescript: "5.4.5",
"@meteorjs/babel": "7.19.0-beta.3",
"@meteorjs/reify": "0.24.0",
typescript: "5.6.2",
"@meteorjs/babel": "7.20.0",
"@meteorjs/reify": "0.25.3",
// So that Babel can emit require("@babel/runtime/helpers/...") calls.
"@babel/runtime": "7.15.3",
// For backwards compatibility with isopackets that still depend on

View File

@@ -1,6 +1,6 @@
{
"name": "@meteorjs/babel",
"version": "7.20.0-beta.5",
"version": "7.20.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -786,9 +786,9 @@
}
},
"@meteorjs/reify": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.2.tgz",
"integrity": "sha512-mkaSPyzovKf86wSA4ouCmXUQkASA8qNCXp71/Tbm0tD/bpiaja3measRB1HPA+yLXq9Xq3+8GLh8ytJu98cwIQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.3.tgz",
"integrity": "sha512-OVtWOLNvonGwA9Uowzp18q6L2Z3V/kPItS1bNyJMryfXFnosM2O0Hm3pYcxRfP36/0tc1BCiV3dA8yrr8RgMUA==",
"requires": {
"acorn": "^8.8.1",
"magic-string": "^0.25.3",
@@ -797,9 +797,9 @@
},
"dependencies": {
"semver": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
}
}
},
@@ -809,9 +809,9 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg=="
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg=="
},
"ansi-colors": {
"version": "3.2.3",

View File

@@ -1,7 +1,7 @@
{
"name": "@meteorjs/babel",
"author": "Meteor <dev@meteor.com>",
"version": "7.20.0-beta.5",
"version": "7.20.0",
"license": "MIT",
"type": "commonjs",
"description": "Babel wrapper package for use with Meteor",
@@ -42,7 +42,7 @@
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.17.0",
"@babel/types": "^7.17.0",
"@meteorjs/reify": "0.25.2",
"@meteorjs/reify": "0.25.3",
"babel-preset-meteor": "^7.10.0",
"babel-preset-minify": "^0.5.1",
"convert-source-map": "^1.6.0",

View File

@@ -1,80 +1,106 @@
## Meteor Installer
Node.js <=14.x and npm <=6.x is recommended.
Install Meteor by running:
```bash
npm install -g meteor
```
[Read more](https://www.meteor.com/developers/install)
### Meteor version relationship
| NPM Package | Meteor Official Release |
|-------------|-------------------------|
| 3.0.1 | 3.0.1 |
| 3.0.0 | 3.0 |
| 2.16.0 | 2.16.0 |
| 2.15.0 | 2.15.0 |
| 2.14.0 | 2.14.0 |
| 2.13.3 | 2.13.3 |
| 2.13.1 | 2.13.1 |
| 2.13.0 | 2.13.0 |
| 2.12.1 | 2.12.0 |
| 2.12.0 | 2.12.0 |
| 2.11.0 | 2.11.0 |
| 2.10.0 | 2.10.0 |
| 2.9.1 | 2.9.1 |
| 2.9.0 | 2.9.0 |
| 2.8.2 | 2.8.1 |
| 2.8.1 | 2.8.1 |
| 2.8.0 | 2.8.0 |
| 2.7.5 | 2.7.3 |
| 2.7.4 | 2.7.3 |
| 2.7.3 | 2.7.2 |
| 2.7.2 | 2.7.1 |
| 2.7.1 | 2.7 |
| 2.7.0 | 2.7 |
| 2.6.2 | 2.6.1 |
| 2.6.1 | 2.6 |
| 2.6.0 | 2.6 |
| 2.5.9 | 2.5.8 |
| 2.5.8 | 2.5.7 |
| 2.5.7 | 2.5.6 |
| 2.5.6 | 2.5.5 |
| 2.5.5 | 2.5.4 |
| 2.5.4 | 2.5.3 |
| 2.5.3 | 2.5.2 |
| 2.5.2 | 2.5.1 |
| 2.5.1 | 2.5.1 |
| 2.5.0 | 2.5 |
| 2.4.1 | 2.4 |
| 2.4.0 | 2.4 |
| 2.3.7 | 2.3.6 |
| 2.3.6 | 2.3.5 |
| 2.3.5 | 2.3.5 |
| 2.3.4 | 2.3.4 |
| 2.3.3 | 2.3.2 |
| 2.3.2 | 2.3.1 |
| 2.3.1 | 2.2.1 |
### Important note
This npm package is not Meteor itself, it is just an installer. You should not include it as a dependency in your project. If you do, your deployment is going to be broken.
### Path management
By default, the Meteor installer adds its install path (by default, `~/.meteor/`) to your PATH by updating either your `.bashrc`, `.bash_profile`, or `.zshrc` as appropriate. To disable this behavior, install Meteor by running:
```bash
npm install -g meteor --ignore-meteor-setup-exec-path
```
(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`)
### Proxy configuration
Setting the `https_proxy` or `HTTPS_PROXY` environment variable to a valid proxy URL will cause the
downloader to use the configured proxy to retrieve the Meteor files.
## Meteor Installer
### Recommended Versions
- For Meteor 2 (Legacy)
- Use Node.js 14.x
- Use npm 6.x
- For Meteor 3
- Use Node.js 20.x or higher
- Use npm 9.x or higher
### Installation
To install Meteor, run the following command:
```bash
npx meteor
```
It will install Meteor's latest version, alternatively you can install a specific version by running:
```bash
npx meteor@<version>
```
This command will execute the Meteor installer without adding it permanently to your global npm packages.
For more information, visit:
- [Meteor 2 Installation Guide (Legacy)](https://v2-docs.meteor.com/install.html)
- [**Meteor 3 Installation Guide**](https://v3-docs.meteor.com/about/install.html)
### Important Note
This npm package is not the Meteor framework itself; it is just an installer. Do not include it as a dependency in your project, as doing so may break your deployment.
### Path Management
By default, the Meteor installer adds its install path (by default, `~/.meteor/`) to your PATH by updating either your `.bashrc`, `.bash_profile`, or `.zshrc` as appropriate. To disable this behavior, install Meteor by running:
```bash
npm install -g meteor --ignore-meteor-setup-exec-path
```
(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`)
### Proxy Configuration
Set the `https_proxy` or `HTTPS_PROXY` environment variable to a valid proxy URL to download Meteor files through the configured proxy.
### Meteor Version Compatibility
| NPM Package | Meteor Official Release |
|-------------|-------------------------|
| 3.0.4 | 3.0.4 |
| 3.0.3 | 3.0.3 |
| 3.0.2 | 3.0.2 |
| 3.0.1 | 3.0.1 |
| 3.0.0 | 3.0 |
| 2.16.0 | 2.16.0 |
| 2.15.0 | 2.15.0 |
| 2.14.0 | 2.14.0 |
| 2.13.3 | 2.13.3 |
| 2.13.1 | 2.13.1 |
| 2.13.0 | 2.13.0 |
| 2.12.1 | 2.12.0 |
| 2.12.0 | 2.12.0 |
| 2.11.0 | 2.11.0 |
| 2.10.0 | 2.10.0 |
| 2.9.1 | 2.9.1 |
| 2.9.0 | 2.9.0 |
| 2.8.2 | 2.8.1 |
| 2.8.1 | 2.8.1 |
| 2.8.0 | 2.8.0 |
| 2.7.5 | 2.7.3 |
| 2.7.4 | 2.7.3 |
| 2.7.3 | 2.7.2 |
| 2.7.2 | 2.7.1 |
| 2.7.1 | 2.7 |
| 2.7.0 | 2.7 |
| 2.6.2 | 2.6.1 |
| 2.6.1 | 2.6 |
| 2.6.0 | 2.6 |
| 2.5.9 | 2.5.8 |
| 2.5.8 | 2.5.7 |
| 2.5.7 | 2.5.6 |
| 2.5.6 | 2.5.5 |
| 2.5.5 | 2.5.4 |
| 2.5.4 | 2.5.3 |
| 2.5.3 | 2.5.2 |
| 2.5.2 | 2.5.1 |
| 2.5.1 | 2.5.1 |
| 2.5.0 | 2.5 |
| 2.4.1 | 2.4 |
| 2.4.0 | 2.4 |
| 2.3.7 | 2.3.6 |
| 2.3.6 | 2.3.5 |
| 2.3.5 | 2.3.5 |
| 2.3.4 | 2.3.4 |
| 2.3.3 | 2.3.2 |
| 2.3.2 | 2.3.1 |
| 2.3.1 | 2.2.1 |

View File

@@ -1,7 +1,7 @@
const os = require('os');
const path = require('path');
const METEOR_LATEST_VERSION = '3.0.1';
const METEOR_LATEST_VERSION = '3.0.4';
const sudoUser = process.env.SUDO_USER || '';
function isRoot() {
return process.getuid && process.getuid() === 0;

View File

@@ -5,7 +5,7 @@ const Seven = require('node-7z');
const { resolve, dirname } = require('path');
const tar = require('tar');
const { isMac } = require('./config.js');
const { isLinux } = require('./config.js');
function extractWith7Zip(tarPath, destination, onProgress) {
return new Promise((resolve, reject) => {
@@ -49,7 +49,7 @@ function createSymlinks(symlinks, baseDir) {
function extractWithNativeTar(tarPath, destination) {
child_process.execSync(
`tar -xf "${tarPath}" ${
!isMac() ? `--checkpoint-action=ttyout="#%u: %T \r"` : ``
isLinux() ? `--checkpoint-action=ttyout="#%u: %T \r"` : ``
} -C "${destination}"`,
{
cwd: process.cwd(),

View File

@@ -211,8 +211,19 @@ function download() {
}
if (isWindows()) {
decompress();
return;
const hasNativeTar = fs.existsSync(
path.resolve('C:/Windows/System32', 'tar.exe'),
);
if (hasNativeTar) {
// tar works exactly the same as it's bsdtar counterpart on UNIX so continue
console.log(
'Native binary for tar is available on this version of Windows.',
);
console.log('Switching to the native tar.exe binary on Windows.');
} else {
decompress();
return;
}
}
fs.writeFileSync(startedPath, 'Meteor install started');
@@ -302,7 +313,9 @@ async function setup() {
async function setupExecPath() {
if (isWindows()) {
// set for the current session and beyond
child_process.execSync(`setx path "${meteorPath}/;%path%`);
child_process.execSync(
`powershell -c "$path = (Get-Item 'HKCU:\\Environment').GetValue('Path', '', 'DoNotExpandEnvironmentNames'); [Environment]::SetEnvironmentVariable('PATH', \\"${meteorPath};$path\\", 'User');"`,
);
return;
}
const exportCommand = `export PATH=${meteorPath}:$PATH`;

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +1,30 @@
{
"name": "meteor",
"version": "3.0.1",
"description": "Install Meteor",
"main": "install.js",
"scripts": {
"install": "node cli.js install"
},
"author": "zodern",
"license": "MIT",
"type": "commonjs",
"dependencies": {
"7zip-bin": "^5.2.0",
"cli-progress": "^3.11.1",
"https-proxy-agent": "^5.0.1",
"node-7z": "^2.1.2",
"node-downloader-helper": "^2.1.9",
"rimraf": "^3.0.2",
"semver": "^7.3.7",
"tar": "^6.1.11",
"tmp": "^0.2.1"
},
"bin": {
"meteor-installer": "cli.js"
},
"engines": {
"node": ">=20.x",
"npm": ">=10.x"
}
}
{
"name": "meteor",
"version": "3.0.4",
"description": "Install Meteor",
"main": "install.js",
"scripts": {
"install": "node cli.js install"
},
"author": "zodern",
"license": "MIT",
"type": "commonjs",
"dependencies": {
"7zip-bin": "^5.2.0",
"cli-progress": "^3.11.1",
"https-proxy-agent": "^5.0.1",
"node-7z": "^2.1.2",
"node-downloader-helper": "^2.1.9",
"rimraf": "^6.0.1",
"semver": "^7.3.7",
"tar": "^6.1.11",
"tmp": "^0.2.1"
},
"bin": {
"meteor-installer": "cli.js"
},
"engines": {
"node": ">=20.x",
"npm": ">=10.x"
}
}

View File

@@ -1,12 +1,12 @@
{
"name": "meteor-node-stubs",
"version": "1.2.9",
"version": "1.2.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meteor-node-stubs",
"version": "1.2.9",
"version": "1.2.10",
"bundleDependencies": [
"@meteorjs/crypto-browserify",
"assert",
@@ -41,7 +41,7 @@
"console-browserify": "^1.2.0",
"constants-browserify": "^1.0.0",
"domain-browser": "^4.23.0",
"elliptic": "^6.5.4",
"elliptic": "^6.5.7",
"events": "^3.3.0",
"https-browserify": "^1.0.0",
"os-browserify": "^0.3.0",
@@ -55,7 +55,7 @@
"string_decoder": "^1.3.0",
"timers-browserify": "^2.0.12",
"tty-browserify": "0.0.1",
"url": "^0.11.3",
"url": "^0.11.4",
"util": "^0.12.5",
"vm-browserify": "^1.1.2"
},
@@ -361,14 +361,20 @@
"inBundle": true
},
"node_modules/call-bind": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.1",
"set-function-length": "^1.1.1"
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -452,17 +458,21 @@
}
},
"node_modules/define-data-property": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
@@ -522,10 +532,11 @@
}
},
"node_modules/elliptic": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz",
"integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==",
"version": "6.5.7",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz",
"integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"bn.js": "^4.11.9",
"brorand": "^1.1.0",
@@ -542,6 +553,29 @@
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"inBundle": true
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -586,16 +620,21 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -633,12 +672,13 @@
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.2"
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -907,10 +947,14 @@
}
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -1082,12 +1126,13 @@
"inBundle": true
},
"node_modules/qs": {
"version": "6.11.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"inBundle": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -1181,15 +1226,18 @@
"inBundle": true
},
"node_modules/set-function-length": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.1",
"get-intrinsic": "^1.2.1",
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -1215,14 +1263,19 @@
}
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
"call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -1278,13 +1331,17 @@
"inBundle": true
},
"node_modules/url": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz",
"integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==",
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
"integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
"inBundle": true,
"license": "MIT",
"dependencies": {
"punycode": "^1.4.1",
"qs": "^6.11.2"
"qs": "^6.12.3"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/util": {

View File

@@ -2,7 +2,7 @@
"name": "meteor-node-stubs",
"author": "Ben Newman <ben@meteor.com>",
"description": "Stub implementations of Node built-in modules, a la Browserify",
"version": "1.2.9",
"version": "1.2.10",
"main": "index.js",
"license": "MIT",
"homepage": "https://github.com/meteor/meteor/blob/devel/npm-packages/meteor-node-stubs/README.md",
@@ -18,7 +18,7 @@
"console-browserify": "^1.2.0",
"constants-browserify": "^1.0.0",
"domain-browser": "^4.23.0",
"elliptic": "^6.5.4",
"elliptic": "^6.5.7",
"events": "^3.3.0",
"https-browserify": "^1.0.0",
"os-browserify": "^0.3.0",
@@ -32,7 +32,7 @@
"string_decoder": "^1.3.0",
"timers-browserify": "^2.0.12",
"tty-browserify": "0.0.1",
"url": "^0.11.3",
"url": "^0.11.4",
"util": "^0.12.5",
"vm-browserify": "^1.1.2"
},

View File

@@ -2,9 +2,9 @@
"lockfileVersion": 4,
"dependencies": {
"@types/node": {
"version": "20.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
"integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="
"version": "22.7.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ=="
},
"@types/notp": {
"version": "2.0.5",
@@ -32,14 +32,14 @@
"integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA=="
},
"tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
}
}
}

View File

@@ -14,7 +14,7 @@ Accounts._check2faEnabled = user => {
};
Accounts._is2faEnabledForUser = async () => {
const user = await Meteor.user();
const user = await Meteor.userAsync();
if (!user) {
throw new Meteor.Error('no-logged-user', 'No user logged in.');
}
@@ -36,7 +36,7 @@ Accounts._isTokenValid = (secret, code) => {
Meteor.methods({
async generate2faActivationQrCode(appName) {
check(appName, String);
const user = await Meteor.user();
const user = await Meteor.userAsync();
if (!user) {
throw new Meteor.Error(
@@ -74,7 +74,7 @@ Meteor.methods({
},
async enableUser2fa(code) {
check(code, String);
const user = await Meteor.user();
const user = await Meteor.userAsync();
if (!user) {
throw new Meteor.Error(400, 'No user logged in.');

View File

@@ -1,37 +1,37 @@
Package.describe({
version: '3.0.0',
version: "3.0.1",
summary:
'Package used to enable two factor authentication through OTP protocol',
"Package used to enable two factor authentication through OTP protocol",
});
Npm.depends({
'node-2fa': '2.0.3',
'qrcode-svg': '1.1.0',
"node-2fa": "2.0.3",
"qrcode-svg": "1.1.0",
});
Package.onUse(function(api) {
api.use(['accounts-base'], ['client', 'server']);
Package.onUse(function (api) {
api.use(["accounts-base"], ["client", "server"]);
// Export Accounts (etc.) to packages using this one.
api.imply('accounts-base', ['client', 'server']);
api.imply("accounts-base", ["client", "server"]);
api.use('ecmascript');
api.use('check', 'server');
api.use("ecmascript");
api.use("check", "server");
api.addFiles(['2fa-client.js'], 'client');
api.addFiles(['2fa-server.js'], 'server');
api.addFiles(["2fa-client.js"], "client");
api.addFiles(["2fa-server.js"], "server");
});
Package.onTest(function(api) {
Package.onTest(function (api) {
api.use([
'accounts-base',
'accounts-password',
'ecmascript',
'tinytest',
'random',
'accounts-2fa',
"accounts-base",
"accounts-password",
"ecmascript",
"tinytest",
"random",
"accounts-2fa",
]);
api.mainModule('server_tests.js', 'server');
api.mainModule('client_tests.js', 'client');
api.mainModule("server_tests.js", "server");
api.mainModule("client_tests.js", "client");
});

View File

@@ -47,7 +47,7 @@ export namespace Accounts {
profile?: Meteor.UserProfile | undefined;
},
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
): string;
): Promise<string>;
function createUserAsync(
options: {
@@ -113,23 +113,23 @@ export namespace Accounts {
oldPassword: string,
newPassword: string,
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
): void;
): Promise<void>;
function forgotPassword(
options: { email?: string | undefined },
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
): void;
): Promise<void>;
function resetPassword(
token: string,
newPassword: string,
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
): void;
): Promise<void>;
function verifyEmail(
token: string,
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
): void;
): Promise<void>;
function onEmailVerificationLink(callback: Function): void;
@@ -143,11 +143,11 @@ export namespace Accounts {
function logout(
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
): void;
): Promise<void>;
function logoutOtherClients(
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
): void;
): Promise<void>;
type PasswordSignupField = 'USERNAME_AND_EMAIL' | 'USERNAME_AND_OPTIONAL_EMAIL' | 'USERNAME_ONLY' | 'EMAIL_ONLY';
type PasswordlessSignupField = 'USERNAME_AND_EMAIL' | 'EMAIL_ONLY';
@@ -179,9 +179,9 @@ export interface EmailTemplates {
export namespace Accounts {
var emailTemplates: EmailTemplates;
function addEmail(userId: string, newEmail: string, verified?: boolean): void;
function addEmailAsync(userId: string, newEmail: string, verified?: boolean): Promise<void>;
function removeEmail(userId: string, email: string): void;
function removeEmail(userId: string, email: string): Promise<void>;
function onCreateUser(
func: (options: { profile?: {} | undefined }, user: Meteor.User) => void
@@ -190,35 +190,35 @@ export namespace Accounts {
function findUserByEmail(
email: string,
options?: { fields?: Mongo.FieldSpecifier | undefined }
): Meteor.User | null | undefined;
): Promise<Meteor.User | null | undefined>;
function findUserByUsername(
username: string,
options?: { fields?: Mongo.FieldSpecifier | undefined }
): Meteor.User | null | undefined;
): Promise<Meteor.User | null | undefined>;
function sendEnrollmentEmail(
userId: string,
email?: string,
extraTokenData?: Record<string, unknown>,
extraParams?: Record<string, unknown>
): void;
): Promise<void>;
function sendResetPasswordEmail(
userId: string,
email?: string,
extraTokenData?: Record<string, unknown>,
extraParams?: Record<string, unknown>
): void;
): Promise<void>;
function sendVerificationEmail(
userId: string,
email?: string,
extraTokenData?: Record<string, unknown>,
extraParams?: Record<string, unknown>
): void;
): Promise<void>;
function setUsername(userId: string, newUsername: string): void;
function setUsername(userId: string, newUsername: string): Promise<void>;
function setPasswordAsync(
userId: string,

View File

@@ -362,18 +362,19 @@ export class AccountsClient extends AccountsCommon {
// Note that we need to call this even if _suppressLoggingIn is true,
// because it could be matching a _setLoggingIn(true) from a
// half-completed pre-reconnect login method.
this._setLoggingIn(false);
if (error || !result) {
error = error || new Error(
`No result from call to ${options.methodName}`
);
loginCallbacks({ error });
this._setLoggingIn(false);
return;
}
try {
options.validateResult(result);
} catch (e) {
loginCallbacks({ error: e });
this._setLoggingIn(false);
return;
}
@@ -381,13 +382,15 @@ export class AccountsClient extends AccountsCommon {
this.makeClientLoggedIn(result.id, result.token, result.tokenExpires);
// use Tracker to make we sure have a user before calling the callbacks
Tracker.autorun(async function (computation) {
Tracker.autorun(async (computation) => {
const user = await Tracker.withComputation(computation, () =>
Meteor.userAsync(),
);
if (user) {
loginCallbacks({ loginDetails: result })
loginCallbacks({ loginDetails: result });
this._setLoggingIn(false);
computation.stop();
}
});
@@ -399,7 +402,7 @@ export class AccountsClient extends AccountsCommon {
this.connection.applyAsync(
options.methodName,
options.methodArguments,
{ wait: true, onResultReceived: onResultReceived },
{ wait: true, onResultReceived },
loggedInAndDataReadyCallback);
}

View File

@@ -1,4 +1,5 @@
import {Accounts} from "meteor/accounts-base";
import { AccountsClient } from './accounts_client';
const username = 'jsmith';
const password = 'password';
@@ -343,3 +344,25 @@ Tinytest.addAsync('accounts - storage',
});
}
});
Tinytest.addAsync('accounts - should only start subscription when connected', async function (test) {
const { conn, messages, cleanup } = await captureConnectionMessagesClient(test);
const acc = new AccountsClient({
connection: conn,
})
acc.callLoginMethod()
await Meteor._sleepForMs(100);
// The sub call needs to come right after `connect` since this is when `status().connected` gets to be true and
// not after `connected` as it is based on the socket connection status.
const expectedMessages = ['connect', 'method', 'sub', 'connected', 'updated', 'result', 'ready']
const parsedMessages = messages.map(m => m.msg).filter(Boolean).filter(m => m !== 'added')
test.equal(parsedMessages, expectedMessages)
cleanup()
});

View File

@@ -19,6 +19,8 @@ const VALID_CONFIG_KEYS = [
'loginTokenExpirationHours',
'tokenSequenceLength',
'clientStorage',
'ddpUrl',
'connection',
];
/**
@@ -37,8 +39,7 @@ export class AccountsCommon {
// Validate config options keys
for (const key of Object.keys(options)) {
if (!VALID_CONFIG_KEYS.includes(key)) {
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
throw new Meteor.Error(`Accounts.config: Invalid key: ${key}`);
console.error(`Accounts.config: Invalid key: ${key}`);
}
}
@@ -163,6 +164,14 @@ export class AccountsCommon {
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
*/
user(options) {
if (Meteor.isServer) {
console.warn([
"`Meteor.user()` is deprecated on the server side.",
" To fetch the current user record on the server,",
" use `Meteor.userAsync()` instead.",
].join("\n"));
}
const self = this;
const userId = self.userId();
const findOne = (...args) => Meteor.isClient
@@ -284,8 +293,7 @@ export class AccountsCommon {
// Validate config options keys
for (const key of Object.keys(options)) {
if (!VALID_CONFIG_KEYS.includes(key)) {
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
throw new Meteor.Error(`Accounts.config: Invalid key: ${key}`);
console.error(`Accounts.config: Invalid key: ${key}`);
}
}

View File

@@ -154,7 +154,7 @@ if (Meteor.isClient) {
.then((id) => {
// 4. the server should still return an id after connection
test.isTrue(Meteor.status().connected);
test.isTrue(id);
test.isTrue(id, 'userId exists in the server after reconnect');
})
.finally(done);

View File

@@ -422,7 +422,7 @@ export class AccountsServer extends AccountsCommon {
)
);
methodInvocation.setUserId(userId);
await methodInvocation.setUserId(userId);
return {
id: userId,
@@ -664,7 +664,7 @@ export class AccountsServer extends AccountsCommon {
await accounts.destroyToken(this.userId, token);
}
await accounts._successfulLogout(this.connection, this.userId);
this.setUserId(null);
await this.setUserId(null);
};
// Generates a new login token with the same expiration as the
@@ -1519,11 +1519,11 @@ export class AccountsServer extends AccountsCommon {
}
_handleError = (msg, throwError = true, errorCode = 403) => {
const isErrorAmbiguous = this._options.ambiguousErrorMessages ?? Meteor.isProduction;
const isErrorAmbiguous = this._options.ambiguousErrorMessages ?? true;
const error = new Meteor.Error(
errorCode,
isErrorAmbiguous
? "Something went wrong. Please check your credentials."
? 'Something went wrong. Please check your credentials.'
: msg
);
if (throwError) {

View File

@@ -10,14 +10,6 @@ Meteor.methods({
}
});
// XXX it'd be cool to also test that the right thing happens if options
// *are* validated, but Accounts._options is global state which makes this hard
// (impossible?)
Tinytest.add(
'accounts - config - validates keys',
test => test.throws(() => Accounts.config({ foo: "bar" }))
);
Tinytest.addAsync('accounts - config - token lifetime', async test => {
const { loginExpirationInDays } = Accounts._options;
Accounts._options.loginExpirationInDays = 2;
@@ -487,28 +479,28 @@ Tinytest.addAsync(
Accounts._options = {};
// test the field is included by default
let user = await Meteor.user();
let user = await Meteor.userAsync();
test.isNotUndefined(user[ignoreFieldName], 'included by default');
// test the field is excluded
Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } });
user = await Meteor.user();
user = await Meteor.userAsync();
test.isUndefined(user[ignoreFieldName], 'excluded');
user = await Meteor.user({});
user = await Meteor.userAsync({});
test.isUndefined(user[ignoreFieldName], 'excluded {}');
// test the field can still be retrieved if required
user = await Meteor.user({ fields: { [ignoreFieldName]: 1 } });
user = await Meteor.userAsync({ fields: { [ignoreFieldName]: 1 } });
test.isNotUndefined(user[ignoreFieldName], 'field can be retrieved');
test.isUndefined(user.username, 'field can be retrieved username');
// test a combined negative field specifier
user = await Meteor.user({ fields: { username: 0 } });
user = await Meteor.userAsync({ fields: { username: 0 } });
test.isUndefined(user[ignoreFieldName], 'combined field selector');
test.isUndefined(user.username, 'combined field selector username');
// test an explicit request for the full user object
user = await Meteor.user({ fields: {} });
user = await Meteor.userAsync({ fields: {} });
test.isNotUndefined(user[ignoreFieldName], 'full selector');
test.isNotUndefined(user.username, 'full selector username');
@@ -516,7 +508,7 @@ Tinytest.addAsync(
// Test that a custom field gets retrieved properly
Accounts.config({ defaultFieldSelector: { [customField]: 1 } });
user = await Meteor.user()
user = await Meteor.userAsync()
test.isNotUndefined(user[customField]);
test.isUndefined(user.username);
test.isUndefined(user[ignoreFieldName]);
@@ -922,4 +914,3 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter',
// cleanup
await Meteor.users.removeAsync(u1.id);
});

View File

@@ -1,67 +1,67 @@
Package.describe({
summary: 'A user account system',
version: '3.0.0',
summary: "A user account system",
version: "3.0.3",
});
Package.onUse(api => {
api.use('ecmascript', ['client', 'server']);
api.use('ddp-rate-limiter');
api.use('localstorage', 'client');
api.use('tracker', 'client');
api.use('check', 'server');
api.use('random', ['client', 'server']);
api.use('ejson', 'server');
api.use('callback-hook', ['client', 'server']);
api.use('reactive-var', 'client');
api.use('url', ['client', 'server']);
Package.onUse((api) => {
api.use("ecmascript", ["client", "server"]);
api.use("ddp-rate-limiter");
api.use("localstorage", "client");
api.use("tracker", "client");
api.use("check", "server");
api.use("random", ["client", "server"]);
api.use("ejson", "server");
api.use("callback-hook", ["client", "server"]);
api.use("reactive-var", "client");
api.use("url", ["client", "server"]);
// needed for getting the currently logged-in user and handling reconnects
api.use('ddp', ['client', 'server']);
api.use("ddp", ["client", "server"]);
// need this because of the Meteor.users collection but in the future
// we'd probably want to abstract this away
api.use('mongo', ['client', 'server']);
api.use("mongo", ["client", "server"]);
// If the 'blaze' package is loaded, we'll define some helpers like
// {{currentUser}}. If not, no biggie.
api.use('blaze', 'client', { weak: true });
api.use("blaze", "client", { weak: true });
// Allow us to detect 'autopublish', and publish some Meteor.users fields if
// it's loaded.
api.use('autopublish', 'server', { weak: true });
api.use("autopublish", "server", { weak: true });
api.use('oauth-encryption', 'server', { weak: true });
api.use("oauth-encryption", "server", { weak: true });
// Though this "Accounts" symbol is the only official Package export for
// the accounts-base package, modules that import accounts-base will
// have access to anything added to the exports object of the main
// module, including AccountsClient and AccountsServer (those symbols
// just won't be automatically imported as "global" variables).
api.export('Accounts');
api.export("Accounts");
// These main modules import all the other modules that comprise the
// accounts-base package, and define exports that will be accessible to
// modules that import the accounts-base package.
api.mainModule('server_main.js', 'server');
api.mainModule('client_main.js', 'client');
api.mainModule("server_main.js", "server");
api.mainModule("client_main.js", "client");
api.addAssets('accounts-base.d.ts', 'server');
api.addAssets("accounts-base.d.ts", "server");
});
Package.onTest(api => {
Package.onTest((api) => {
api.use([
'accounts-base',
'ecmascript',
'tinytest',
'random',
'test-helpers',
'oauth-encryption',
'ddp',
'accounts-password',
'accounts-2fa',
"accounts-base",
"ecmascript",
"tinytest",
"random",
"test-helpers",
"oauth-encryption",
"ddp",
"accounts-password",
"accounts-2fa",
]);
api.addFiles('accounts_tests_setup.js', 'server');
api.mainModule('server_tests.js', 'server');
api.mainModule('client_tests.js', 'client');
api.addFiles("accounts_tests_setup.js", "server");
api.mainModule("server_tests.js", "server");
api.mainModule("client_tests.js", "client");
});

View File

@@ -67,9 +67,9 @@
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"debug": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="
},
"delegates": {
"version": "1.0.0",
@@ -178,9 +178,9 @@
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node-addon-api": {
"version": "3.2.1",
@@ -233,9 +233,9 @@
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"semver": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
},
"set-blocking": {
"version": "2.0.0",

View File

@@ -150,10 +150,12 @@ const getVerifyEmailToken = (email, test, expect) => {
}));
};
const loggedIn = (test, expect) => expect((error) => {
const loggedIn = (test, expect) => {
return expect(async (error) => {
test.equal(error, undefined);
test.isTrue(Meteor.user());
test.isTrue(await Meteor.user());
});
};
testAsyncMulti("accounts emails - verify email flow", [
function (test, expect) {
@@ -169,7 +171,7 @@ testAsyncMulti("accounts emails - verify email flow", [
loggedIn(test, expect));
},
async function (test, expect) {
const u = await Meteor.user();
const u = await Meteor.userAsync();
test.equal(u.emails.length, 1);
test.equal(u.emails[0].address, this.email);
test.isFalse(u.emails[0].verified);
@@ -187,11 +189,10 @@ testAsyncMulti("accounts emails - verify email flow", [
}));
},
function (test, expect) {
Accounts.verifyEmail(verifyEmailToken,
loggedIn(test, expect));
Accounts.verifyEmail(verifyEmailToken, loggedIn(test, expect));
},
async function (test, expect) {
const u = await Meteor.user();
async function (test) {
const u = await Meteor.userAsync();
test.equal(u.emails.length, 1);
test.equal(u.emails[0].address, this.email);
@@ -201,7 +202,7 @@ testAsyncMulti("accounts emails - verify email flow", [
Accounts.connection.call(
"addEmailForTestAndVerify", this.anotherEmail,
expect(async (error, result) => {
const u = await Meteor.user();
const u = await Meteor.userAsync();
test.isFalse(error);
test.equal(u.emails.length, 2);
@@ -232,7 +233,7 @@ testAsyncMulti("accounts emails - verify email flow", [
Accounts.connection.call(
"addEmailForTestAndVerify", this.anotherEmailCaps,
expect(async (error, result) => {
const u = await Meteor.user();
const u = await Meteor.userAsync();
test.isFalse(error);
test.equal(u.emails.length, 3);
test.equal(u.emails[2].address, this.anotherEmailCaps);
@@ -255,7 +256,7 @@ testAsyncMulti("accounts emails - verify email flow", [
loggedIn(test, expect));
},
async function (test, expect) {
const u = await Meteor.user();
const u = await Meteor.userAsync();
test.equal(u.emails[2].address, this.anotherEmailCaps);
test.isTrue(u.emails[2].verified);

View File

@@ -1,49 +1,49 @@
Package.describe({
summary: 'Password support for accounts',
summary: "Password support for accounts",
// Note: 2.2.0-beta.3 was published during the Meteor 1.6 prerelease
// process, so it might be best to skip to 2.3.x instead of reusing
// 2.2.x in the future. The version was also bumped to 2.0.0 temporarily
// during the Meteor 1.5.1 release process, so versions 2.0.0-beta.2
// through -beta.5 and -rc.0 have already been published.
version: '3.0.0',
version: "3.0.2",
});
Npm.depends({
bcrypt: '5.0.1',
bcrypt: "5.0.1",
});
Package.onUse(api => {
api.use(['accounts-base', 'sha', 'ejson', 'ddp'], ['client', 'server']);
Package.onUse((api) => {
api.use(["accounts-base", "sha", "ejson", "ddp"], ["client", "server"]);
// Export Accounts (etc) to packages using this one.
api.imply('accounts-base', ['client', 'server']);
api.imply("accounts-base", ["client", "server"]);
api.use('email', 'server');
api.use('random', 'server');
api.use('check', 'server');
api.use('ecmascript');
api.use("email", "server");
api.use("random", "server");
api.use("check", "server");
api.use("ecmascript");
api.addFiles('email_templates.js', 'server');
api.addFiles('password_server.js', 'server');
api.addFiles('password_client.js', 'client');
api.addFiles("email_templates.js", "server");
api.addFiles("password_server.js", "server");
api.addFiles("password_client.js", "client");
});
Package.onTest(api => {
Package.onTest((api) => {
api.use([
'accounts-password',
'sha',
'tinytest',
'test-helpers',
'tracker',
'accounts-base',
'random',
'email',
'check',
'ddp',
'ecmascript',
"accounts-password",
"sha",
"tinytest",
"test-helpers",
"tracker",
"accounts-base",
"random",
"email",
"check",
"ddp",
"ecmascript",
]);
api.addFiles('password_tests_setup.js', 'server');
api.addFiles('password_tests.js', ['client', 'server']);
api.addFiles('email_tests_setup.js', 'server');
api.addFiles('email_tests.js', 'client');
api.addFiles("password_tests_setup.js", "server");
api.addFiles("password_tests.js", ["client", "server"]);
api.addFiles("email_tests_setup.js", "server");
api.addFiles("email_tests.js", "client");
});

View File

@@ -701,7 +701,7 @@ if (Meteor.isClient) (() => {
// accounts-base/accounts_tests.js, but this is where the tests that
// actually log in are.
async function (test, expect) {
const clientUser = await Meteor.user();
const clientUser = await Meteor.userAsync();
Accounts.connection.call('testMeteorUser', expect((err, result) => {
test.equal(result._id, clientUser._id);
test.equal(result.username, clientUser.username);
@@ -1309,7 +1309,7 @@ if (Meteor.isServer) (() => {
password: hashPassword("new-password")
}
),
/Incorrect password/);
/Something went wrong. Please check your credentials./);
});
Tinytest.addAsync(
@@ -1388,7 +1388,7 @@ if (Meteor.isServer) (() => {
password: hashPassword("new-password")
}
),
/Incorrect password/);
/Something went wrong. Please check your credentials./);
});
Tinytest.addAsync('forgotPassword - different error messages returned depending' +

View File

@@ -1,20 +1,20 @@
Package.describe({
summary: "Login service for Twitter accounts",
version: '1.5.1',
version: "1.5.2",
});
Package.onUse(api => {
api.use('ecmascript');
api.use('accounts-base', ['client', 'server']);
Package.onUse((api) => {
api.use("ecmascript");
api.use("accounts-base", ["client", "server"]);
// Export Accounts (etc) to packages using this one.
api.imply('accounts-base', ['client', 'server']);
api.use('accounts-oauth', ['client', 'server']);
api.use('twitter-oauth');
api.imply('twitter-oauth');
api.imply("accounts-base", ["client", "server"]);
api.use("accounts-oauth", ["client", "server"]);
api.use("twitter-oauth");
api.imply("twitter-oauth");
api.use('http@1.0.1', ['client', 'server']);
api.use(['accounts-ui', 'twitter-config-ui'], ['client', 'server'], { weak: true });
api.use(["accounts-ui", "twitter-config-ui"], ["client", "server"], {
weak: true,
});
api.addFiles("notice.js");
api.addFiles("twitter.js");

View File

@@ -1,11 +1,11 @@
Package.describe({
name: "babel-compiler",
summary: "Parser/transpiler for ECMAScript 2015+ syntax",
version: '7.11.0',
version: '7.11.1',
});
Npm.depends({
'@meteorjs/babel': '7.20.0-beta.4',
'@meteorjs/babel': '7.20.0',
'json5': '2.1.1',
'semver': '7.3.8'
});

View File

@@ -1,13 +1,13 @@
Package.describe({
name: 'caching-compiler',
version: '2.0.0',
version: '2.0.1',
summary: 'An easy way to make compiler plugins cache',
documentation: 'README.md'
});
Npm.depends({
'lru-cache': '6.0.0'
})
});
Package.onUse(function(api) {
api.use(['ecmascript', 'random']);

View File

@@ -482,13 +482,18 @@ const testSubtree = (value, pattern, collectErrors = false, errors = [], path =
const keys = Object.keys(requiredPatterns);
if (keys.length) {
const result = {
message: `Missing key '${keys[0]}'`,
path: '',
};
const createMissingError = key => ({
message: `Missing key '${key}'`,
path: collectErrors ? path : '',
});
if (!collectErrors) return result;
errors.push(result);
if (!collectErrors) {
return createMissingError(keys[0]);
}
for (const key of keys) {
errors.push(createMissingError(key));
}
}
if (!collectErrors) return false;

View File

@@ -573,7 +573,8 @@ Tinytest.add('check - check throw all errors deeply nested', test => {
int: { i: 1.2, a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
oneOf: { f: 'm', a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
where: { w: 'a', a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
whereArr: [1, 2, 3]
whereArr: [1, 2, 3],
embedded: { thing: '1' }
};
const pattern = {
@@ -599,7 +600,10 @@ Tinytest.add('check - check throw all errors deeply nested', test => {
whereArr: Match.Where((x) => {
check(x, [String]);
return x.length === 1;
})
}),
missing1: String,
missing2: String,
embedded: { thing: String, another: String }
}
try {
@@ -609,7 +613,8 @@ Tinytest.add('check - check throw all errors deeply nested', test => {
}
test.isTrue(error);
test.equal(error.length, 37);
test.equal(error.length, 40);
test.equal(error.filter(e => e.message.includes('Missing key')).map(e => e.message), [`Match error: Missing key 'another' in field embedded`, `Match error: Missing key 'missing1'`, `Match error: Missing key 'missing2'`]);
error.every(e => test.instanceOf(e, Match.Error));
test.isFalse(Match.test(value, pattern));
})

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: 'Check whether a value matches a pattern',
version: '1.4.2',
version: '1.4.4',
});
Package.onUse(api => {

View File

@@ -11,6 +11,16 @@
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz",
"integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw=="
},
"lodash.has": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
"integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g=="
},
"lodash.identity": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-3.0.0.tgz",
"integrity": "sha512-AupTIzdLQxJS5wIYUQlgGyk2XRTfGXA+MCghDHqZk0pzUNYvd3EESS6dkChNauNYVIutcb0dfHw1ri9Q1yPV8Q=="
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",

View File

@@ -1,5 +1,4 @@
import { DDP } from "../common/namespace.js";
import { isEmpty, last } from "meteor/ddp-common/utils.js";
import { Connection } from "../common/livedata_connection";
// https://forums.meteor.com/t/proposal-to-fix-issues-with-async-method-stubs/60826
@@ -92,21 +91,18 @@ export const loadAsyncStubHelpers = () => {
return queueFunction(
(resolve, reject) => {
let hasStub = false;
let finished = false;
Meteor._setImmediate(() => {
const applyAsyncPromise = oldApplyAsync.apply(this, args);
stubPromiseResolver(applyAsyncPromise.stubPromise);
serverPromiseResolver(applyAsyncPromise.serverPromise);
hasStub = !!applyAsyncPromise.stubPromise;
if (hasStub) {
applyAsyncPromise.stubPromise
.catch(() => {}) // silent uncaught promise
.finally(() => {
finished = true;
});
}
applyAsyncPromise.stubPromise
.catch(() => {}) // silent uncaught promise
.finally(() => {
finished = true;
});
applyAsyncPromise
.then((result) => {
@@ -115,11 +111,12 @@ export const loadAsyncStubHelpers = () => {
.catch((err) => {
reject(err);
});
serverPromise.catch(() => {}); // silent uncaught promise
});
Meteor._setImmediate(() => {
if (hasStub && !finished) {
if (!finished) {
console.warn(
`Method stub (${name}) took too long and could cause unexpected problems. Learn more at https://v3-migration-docs.meteor.com/breaking-changes/call-x-callAsync.html#considerations-for-effective-use-of-meteor-callasync`
);
@@ -134,10 +131,10 @@ export const loadAsyncStubHelpers = () => {
};
let oldApply = Connection.prototype.apply;
Connection.prototype.apply = function () {
// [name, args, options]
let options = arguments[2] || {};
let wait = options.wait;
Connection.prototype.apply = function (name, args, options, callback) {
if (this._stream._neverQueued) {
return oldApply.apply(this, arguments);
}
// Apply runs the stub before synchronously returning.
//
@@ -147,64 +144,26 @@ export const loadAsyncStubHelpers = () => {
// This does mean the stubs run in a different order than the methods on the
// server.
let oldOutstandingMethodBlocks = Meteor.connection._outstandingMethodBlocks;
// Meteor only sends the method if _outstandingMethodBlocks.length is 1.
// Add a wait block to force Meteor to put the new method in a second block.
let outstandingMethodBlocks = [{ wait: true, methods: [] }];
Meteor.connection._outstandingMethodBlocks = outstandingMethodBlocks;
let result;
try {
result = oldApply.apply(this, arguments);
} finally {
Meteor.connection._outstandingMethodBlocks = oldOutstandingMethodBlocks;
if (!callback && typeof options === 'function') {
callback = options;
options = undefined;
}
if (outstandingMethodBlocks[1]) {
let methodInvoker = outstandingMethodBlocks[1].methods[0];
let { methodInvoker, result } = oldApply.call(this, name, args, {
...options,
_returnMethodInvoker: true,
}, callback);
if (methodInvoker) {
queueMethodInvoker(methodInvoker, wait);
}
if (methodInvoker) {
queueFunction((resolve) => {
this._addOutstandingMethod(methodInvoker, options);
resolve();
});
}
return result;
};
function queueMethodInvoker(methodInvoker, wait) {
queueFunction((resolve) => {
let self = Meteor.connection;
// based on https://github.com/meteor/meteor/blob/e0631738f2a8a914d8a50b1060e8f40cb0873680/packages/ddp-client/common/livedata_connection.js#L833-L853C1
if (wait) {
// It's a wait method! Wait methods go in their own block.
self._outstandingMethodBlocks.push({
wait: true,
methods: [methodInvoker],
});
} else {
// Not a wait method. Start a new block if the previous block was a wait
// block, and add it to the last block of methods.
if (
isEmpty(self._outstandingMethodBlocks) ||
last(self._outstandingMethodBlocks).wait
) {
self._outstandingMethodBlocks.push({
wait: false,
methods: [],
});
}
last(self._outstandingMethodBlocks).methods.push(methodInvoker);
}
// If we added it to the first block, send it out now.
if (self._outstandingMethodBlocks.length === 1)
methodInvoker.sendMessage();
resolve();
});
}
/**
* Queue subscriptions in case they rely on previous method calls
*/
@@ -242,6 +201,7 @@ export const loadAsyncStubHelpers = () => {
}
});
};
let _oldSendOutstandingMethodBlocksMessages =
Connection.prototype._sendOutstandingMethodBlocksMessages;
Connection.prototype._sendOutstandingMethodBlocksMessages = function () {

View File

@@ -3,7 +3,6 @@ import { DDPCommon } from 'meteor/ddp-common';
import { Tracker } from 'meteor/tracker';
import { EJSON } from 'meteor/ejson';
import { Random } from 'meteor/random';
import { Hook } from 'meteor/callback-hook';
import { MongoID } from 'meteor/mongo-id';
import { DDP } from './namespace.js';
import MethodInvoker from './MethodInvoker.js';
@@ -77,7 +76,8 @@ export class Connection {
if (typeof url === 'object') {
self._stream = url;
} else {
const { ClientStream } = require("meteor/socket-stream-client");
import { ClientStream } from "meteor/socket-stream-client";
self._stream = new ClientStream(url, {
retry: options.retry,
ConnectionError: DDP.ConnectionError,
@@ -639,6 +639,7 @@ export class Connection {
* @param {Boolean} options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback an error with the error code 'invocation-failed'.
* @param {Boolean} options.throwStubExceptions (Client only) If true, exceptions thrown by method stubs will be thrown instead of logged, and the method will not be invoked on the server.
* @param {Boolean} options.returnStubValue (Client only) If true then in cases where we would have otherwise discarded the stub's return value and returned undefined, instead we go ahead and return it. Specifically, this is any time other than when (a) we are already inside a stub or (b) we are in Node and no callback was provided. Currently we require this flag to be explicitly passed to reduce the likelihood that stub return values will be confused with server return values; we may improve this in future.
* @param {Boolean} options.returnServerResultPromise (Client only) If true, the promise returned by applyAsync will resolve to the server's return value, rather than the stub's return value. This is useful when you want to ensure that the server's return value is used, even if the stub returns a promise. The same behavior as `callAsync`.
*/
applyAsync(name, args, options, callback = null) {
const stubPromise = this._applyAsyncStubInvocation(name, args, options);
@@ -708,6 +709,7 @@ export class Connection {
_apply(name, stubCallValue, args, options, callback) {
const self = this;
// We were passed 3 arguments. They may be either (name, args, options)
// or (name, args, callback)
if (!callback && typeof options === 'function') {
@@ -745,12 +747,16 @@ export class Connection {
isFromCallAsync: stubCallValue.isFromCallAsync,
})
) {
let result;
if (callback) {
callback(exception, stubReturnValue);
return undefined;
} else {
if (exception) throw exception;
result = stubReturnValue;
}
if (exception) throw exception;
return stubReturnValue;
return options._returnMethodInvoker ? { result } : result;
}
// We only create the methodId here because we don't actually need one if
@@ -793,24 +799,18 @@ export class Connection {
// return the value of the RPC to the caller.
// If the caller didn't give a callback, decide what to do.
let future;
let promise;
if (!callback) {
if (
Meteor.isClient &&
!options.returnServerResultPromise &&
(!options.isFromCallAsync || options.returnStubValue)
) {
// On the client, we don't have fibers, so we can't block. The
// only thing we can do is to return undefined and discard the
// result of the RPC. If an error occurred then print the error
// to the console.
callback = (err) => {
err && Meteor._debug("Error invoking Method '" + name + "'", err);
};
} else {
// On the server, make the function synchronous. Throw on
// errors, return on success.
future = new Promise((resolve, reject) => {
promise = new Promise((resolve, reject) => {
callback = (...allArgs) => {
let args = Array.from(allArgs);
let err = args.shift();
@@ -839,45 +839,25 @@ export class Connection {
noRetry: !!options.noRetry
});
if (options.wait) {
// It's a wait method! Wait methods go in their own block.
self._outstandingMethodBlocks.push({
wait: true,
methods: [methodInvoker]
});
let result;
if (promise) {
result = options.returnStubValue ? promise.then(() => stubReturnValue) : promise;
} else {
// Not a wait method. Start a new block if the previous block was a wait
// block, and add it to the last block of methods.
if (isEmpty(self._outstandingMethodBlocks) ||
last(self._outstandingMethodBlocks).wait) {
self._outstandingMethodBlocks.push({
wait: false,
methods: [],
});
}
last(self._outstandingMethodBlocks).methods.push(methodInvoker);
result = options.returnStubValue ? stubReturnValue : undefined;
}
// If we added it to the first block, send it out now.
if (self._outstandingMethodBlocks.length === 1) methodInvoker.sendMessage();
// If we're using the default callback on the server,
// block waiting for the result.
if (future) {
// This is the result of the method ran in the client.
// You can opt-in in getting the local result by running:
// const { stubPromise, serverPromise } = Meteor.callAsync(...);
// const whatServerDid = await serverPromise;
if (options.returnStubValue) {
return future.then(() => stubReturnValue);
}
return future;
if (options._returnMethodInvoker) {
return {
methodInvoker,
result,
};
}
return options.returnStubValue ? stubReturnValue : undefined;
self._addOutstandingMethod(methodInvoker, options);
return result;
}
_stubCall(name, args, options) {
// Run the stub, if we have one. The stub is supposed to make some
// temporary writes to the database to give the user a smooth experience
@@ -1034,6 +1014,9 @@ export class Connection {
// Always queues the call before sending the message
// Used, for example, on subscription.[id].stop() to make sure a "sub" message is always called before an "unsub" message
// https://github.com/meteor/meteor/issues/13212
//
// This is part of the actual fix for the rest check:
// https://github.com/meteor/meteor/pull/13236
_sendQueued(obj) {
this._send(obj, true);
}
@@ -1750,6 +1733,33 @@ export class Connection {
}
}
_addOutstandingMethod(methodInvoker, options) {
if (options?.wait) {
// It's a wait method! Wait methods go in their own block.
this._outstandingMethodBlocks.push({
wait: true,
methods: [methodInvoker]
});
} else {
// Not a wait method. Start a new block if the previous block was a wait
// block, and add it to the last block of methods.
if (isEmpty(this._outstandingMethodBlocks) ||
last(this._outstandingMethodBlocks).wait) {
this._outstandingMethodBlocks.push({
wait: false,
methods: [],
});
}
last(this._outstandingMethodBlocks).methods.push(methodInvoker);
}
// If we added it to the first block, send it out now.
if (this._outstandingMethodBlocks.length === 1) {
methodInvoker.sendMessage();
}
}
// Called by MethodInvoker after a method's callback is invoked. If this was
// the last outstanding method in the current block, runs the next block. If
// there are no more methods, consider accepting a hot code push.
@@ -1831,6 +1841,7 @@ export class Connection {
// Now add the rest of the original blocks on.
self._outstandingMethodBlocks.push(...oldOutstandingMethodBlocks);
}
_callOnReconnectAndSendAppropriateOutstandingMethods() {
const self = this;
const oldOutstandingMethodBlocks = self._outstandingMethodBlocks;
@@ -1995,7 +2006,7 @@ export class Connection {
// add new subscriptions at the end. this way they take effect after
// the handlers and we don't see flicker.
Object.entries(this._subscriptions).forEach(([id, sub]) => {
this._send({
this._sendQueued({
msg: 'sub',
id: id,
name: sub.name,

View File

@@ -1,40 +1,45 @@
Package.describe({
summary: "Meteor's latency-compensated distributed data client",
version: '3.0.0',
documentation: null
version: "3.0.2",
documentation: null,
});
Npm.depends({
'@sinonjs/fake-timers': '7.0.5'
'@sinonjs/fake-timers': '7.0.5',
'lodash.has': '4.5.2',
'lodash.identity': '3.0.0'
});
Package.onUse((api) => {
api.use([
'check',
'random',
'ejson',
'tracker',
'retry',
'id-map',
'ecmascript',
'callback-hook',
'ddp-common',
'reload',
'socket-stream-client',
api.use(
[
"check",
"random",
"ejson",
"tracker",
"retry",
"id-map",
"ecmascript",
"callback-hook",
"ddp-common",
"reload",
"socket-stream-client",
// we depend on _diffObjects, _applyChanges,
'diff-sequence',
// we depend on _diffObjects, _applyChanges,
"diff-sequence",
// _idParse, _idStringify.
'mongo-id'
], ['client', 'server']);
// _idParse, _idStringify.
"mongo-id",
],
["client", "server"]
);
api.use('reload', 'client', { weak: true });
api.use("reload", "client", { weak: true });
// For backcompat where things use Package.ddp.DDP, etc
api.export('DDP');
api.mainModule('client/client.js', 'client');
api.mainModule('server/server.js', 'server');
api.export("DDP");
api.mainModule("client/client.js", "client");
api.mainModule("server/server.js", "server");
});
Package.onTest((api) => {
@@ -43,7 +48,6 @@ Package.onTest((api) => {
'mongo',
'test-helpers',
'ecmascript',
'underscore',
'tinytest',
'random',
'tracker',
@@ -55,12 +59,12 @@ Package.onTest((api) => {
'check'
]);
api.addFiles('test/stub_stream.js');
api.addFiles('test/livedata_connection_tests.js');
api.addFiles('test/livedata_tests.js');
api.addFiles('test/livedata_test_service.js');
api.addFiles('test/random_stream_tests.js');
api.addFiles('test/async_stubs/client.js', 'client');
api.addFiles('test/async_stubs/server_setup.js', 'server');
api.addFiles('test/livedata_callAsync_tests.js');
api.addFiles("test/stub_stream.js");
api.addFiles("test/livedata_connection_tests.js");
api.addFiles("test/livedata_tests.js");
api.addFiles("test/livedata_test_service.js");
api.addFiles("test/random_stream_tests.js");
api.addFiles("test/async_stubs/client.js", "client");
api.addFiles("test/async_stubs/server_setup.js", "server");
api.addFiles("test/livedata_callAsync_tests.js");
});

View File

@@ -1,3 +1,5 @@
import has from 'lodash.has';
import identity from 'lodash.identity'
import FakeTimers from '@sinonjs/fake-timers';
import { DDP } from '../common/namespace.js';
import { Connection } from '../common/livedata_connection.js';
@@ -8,7 +10,7 @@ const newConnection = function(stream, options) {
// change.
return new Connection(
stream,
_.extend(
Object.assign(
{
reloadWithOutstanding: true,
bufferedWritesInterval: 0
@@ -51,10 +53,10 @@ const testGotMessage = function(test, stream, expected) {
// function.
if (typeof expected === 'object') {
const keysWithStarValues = [];
_.each(expected, function(v, k) {
Object.entries(expected).forEach(function([k, v]) {
if (v === '*') keysWithStarValues.push(k);
});
_.each(keysWithStarValues, function(k) {
keysWithStarValues.forEach(function(k) {
expected[k] = got[k];
});
}
@@ -258,7 +260,7 @@ Tinytest.addAsync('livedata stub - reactive subscribe', async function(test) {
const onReadyCount = {};
const onReady = function(tag) {
return function() {
if (_.has(onReadyCount, tag)) ++onReadyCount[tag];
if (has(onReadyCount, tag)) ++onReadyCount[tag];
else onReadyCount[tag] = 1;
};
};
@@ -365,10 +367,15 @@ Tinytest.addAsync('livedata stub - reactive subscribe', async function(test) {
test.length(stream.sent, 4);
// The order of unsubs here is not important.
const unsubMessages = _.map(stream.sent, JSON.parse);
const unsubMessages = stream.sent.map(JSON.parse);
stream.sent.length = 0;
test.equal(_.unique(_.pluck(unsubMessages, 'msg')), ['unsub']);
const actualIds = _.pluck(unsubMessages, 'id');
test.equal(
[...new Set(unsubMessages.map(msg => {
return msg['msg']
}))], ['unsub']);
const actualIds = unsubMessages.map(function(msg){
return msg['id']
});
const expectedIds = [idFoo2, idBar1, idCompleter, idStopperAgain];
actualIds.sort();
expectedIds.sort();
@@ -471,7 +478,7 @@ Tinytest.addAsync('livedata stub - this', async function(test) {
});
// should throw no exceptions
conn.call('test_this', _.identity);
conn.call('test_this', identity);
// satisfy method, quiesce connection
let message = JSON.parse(stream.sent.shift());
test.isUndefined(message.randomSeed);
@@ -627,7 +634,7 @@ Tinytest.addAsync('livedata stub - mutating method args', async function(test) {
}
});
conn.call('mutateArgs', { foo: 50 }, _.identity);
conn.call('mutateArgs', { foo: 50 }, identity);
// Method should be called with original arg, not mutated arg.
let message = JSON.parse(stream.sent.shift());
@@ -643,7 +650,7 @@ Tinytest.addAsync('livedata stub - mutating method args', async function(test) {
const observeCursor = async function(test, cursor) {
const counts = { added: 0, removed: 0, changed: 0, moved: 0 };
const expectedCounts = _.clone(counts);
const expectedCounts = Object.assign({}, counts);
const handle = await cursor.observe({
addedAt: function() {
counts.added += 1;
@@ -659,9 +666,9 @@ const observeCursor = async function(test, cursor) {
}
});
return {
stop: _.bind(handle.stop, handle),
stop: handle.stop.bind(handle),
expectCallbacks: function(delta) {
_.each(delta, function(mod, field) {
Object.entries(delta || []).forEach(function([field, mod]) {
expectedCounts[field] += mod;
});
test.equal(counts, expectedCounts);
@@ -832,8 +839,8 @@ Tinytest.addAsync('livedata stub - reconnect', async function(test, onComplete)
methodCallbackFired = true;
});
conn.apply('do_something_else', [], { wait: true }, _.identity);
conn.apply('do_something_later', [], _.identity);
conn.apply('do_something_else', [], { wait: true }, identity);
conn.apply('do_something_later', [], identity);
test.isFalse(methodCallbackFired);
@@ -1503,9 +1510,9 @@ if (Meteor.isClient) {
test.equal(coll.find().count(), 0);
// Call a random method (no-op)
conn.call('no-op', _.identity);
conn.call('no-op', identity);
// Call a wait method
conn.apply('no-op', [], { wait: true }, _.identity);
conn.apply('no-op', [], { wait: true }, identity);
// Call a method with a stub that writes.
await conn.applyAsync('insertSomething', []);
@@ -1570,7 +1577,7 @@ Tinytest.addAsync('livedata stub - reactive resub', async function(test) {
// message with an id we haven't seen before
for (let msg of stream.sent) {
msg = JSON.parse(msg);
if (msg.msg === 'sub' && !_.has(readiedSubs, msg.id)) {
if (msg.msg === 'sub' && !has(readiedSubs, msg.id)) {
await stream.receive({ msg: 'ready', subs: [msg.id] });
readiedSubs[msg.id] = true;
}
@@ -1787,15 +1794,15 @@ addReconnectTests(
conn.methods({ do_something: function(x) {} });
setOnReconnect(conn, function() {
conn.apply('do_something', ['reconnect zero'], _.identity);
conn.apply('do_something', ['reconnect one'], _.identity);
conn.apply('do_something', ['reconnect two'], { wait: true }, _.identity);
conn.apply('do_something', ['reconnect three'], _.identity);
conn.apply('do_something', ['reconnect zero'], identity);
conn.apply('do_something', ['reconnect one'], identity);
conn.apply('do_something', ['reconnect two'], { wait: true }, identity);
conn.apply('do_something', ['reconnect three'], identity);
});
conn.apply('do_something', ['one'], _.identity);
conn.apply('do_something', ['two'], { wait: true }, _.identity);
conn.apply('do_something', ['three'], _.identity);
conn.apply('do_something', ['one'], identity);
conn.apply('do_something', ['two'], { wait: true }, identity);
conn.apply('do_something', ['three'], identity);
// reconnect
stream.sent = [];
@@ -1806,7 +1813,7 @@ addReconnectTests(
// what we expect to be blocked. The subsequent logic to correctly
// read the wait flag is tested separately.
test.equal(
_.map(stream.sent, function(msg) {
stream.sent.map(function(msg) {
return JSON.parse(msg).params[0];
}),
['reconnect zero', 'reconnect one']
@@ -1814,10 +1821,10 @@ addReconnectTests(
// white-box test:
test.equal(
_.map(conn._outstandingMethodBlocks, function(block) {
conn._outstandingMethodBlocks.map(function(block) {
return [
block.wait,
_.map(block.methods, function(method) {
block.methods.map(function(method) {
return method._message.params[0];
})
];
@@ -1852,7 +1859,7 @@ Tinytest.addAsync('livedata connection - ping with id', async function(test) {
testGotMessage(test, stream, { msg: 'pong', id: id });
});
_.each(DDPCommon.SUPPORTED_DDP_VERSIONS, function(version) {
DDPCommon.SUPPORTED_DDP_VERSIONS.forEach(function(version) {
Tinytest.addAsync('livedata connection - ping from ' + version, function(
test,
onComplete
@@ -2013,15 +2020,15 @@ addReconnectTests(
conn.methods({ do_something: function(x) {} });
setOnReconnect(conn, function() {
conn.apply('do_something', ['reconnect one'], _.identity);
conn.apply('do_something', ['reconnect two'], _.identity);
conn.apply('do_something', ['reconnect three'], _.identity);
conn.apply('do_something', ['reconnect one'], identity);
conn.apply('do_something', ['reconnect two'], identity);
conn.apply('do_something', ['reconnect three'], identity);
});
conn.apply('do_something', ['one'], _.identity);
conn.apply('do_something', ['two'], { wait: true }, _.identity);
conn.apply('do_something', ['three'], { wait: true }, _.identity);
conn.apply('do_something', ['four'], _.identity);
conn.apply('do_something', ['one'], identity);
conn.apply('do_something', ['two'], { wait: true }, identity);
conn.apply('do_something', ['three'], { wait: true }, identity);
conn.apply('do_something', ['four'], identity);
// reconnect
stream.sent = [];
@@ -2032,7 +2039,7 @@ addReconnectTests(
// what we expect to be blocked. The subsequent logic to correctly
// read the wait flag is tested separately.
test.equal(
_.map(stream.sent, function(msg) {
stream.sent.map(function(msg) {
return JSON.parse(msg).params[0];
}),
['reconnect one', 'reconnect two', 'reconnect three', 'one']
@@ -2040,10 +2047,10 @@ addReconnectTests(
// white-box test:
test.equal(
_.map(conn._outstandingMethodBlocks, function(block) {
conn._outstandingMethodBlocks.map(function(block) {
return [
block.wait,
_.map(block.methods, function(method) {
block.methods.map(function(method) {
return method._message.params[0];
})
];
@@ -2069,10 +2076,10 @@ addReconnectTests(
conn.methods({ do_something: function(x) {} });
setOnReconnect(conn, function() {
conn.apply('do_something', ['login'], { wait: true }, _.identity);
conn.apply('do_something', ['login'], { wait: true }, identity);
});
conn.apply('do_something', ['one'], _.identity);
conn.apply('do_something', ['one'], identity);
// initial connect
stream.sent = [];
@@ -2176,7 +2183,7 @@ addReconnectTests('livedata stub - reconnect double wait method', async function
// Call another method. It should be delivered immediately. This is a
// regression test for a case where it never got delivered because there was
// an empty block in _outstandingMethodBlocks blocking it from being sent.
conn.call('lastMethod', _.identity);
conn.call('lastMethod', identity);
testGotMessage(test, stream, {
msg: 'method',
method: 'lastMethod',
@@ -2556,4 +2563,4 @@ if (Meteor.isClient) {
// - reconnect, with session resume.
// - restart on update flag
// - on_update event
// - reloading when the app changes, including session migration
// - reloading when the app changes, including session migration

View File

@@ -1,10 +1,12 @@
import has from 'lodash.has';
Meteor.methods({
nothing: function() {
// No need to check if there are no arguments.
},
echo: function(/* arguments */) {
echo: function(...args) {
check(arguments, [Match.Any]);
return _.toArray(arguments);
return args;
},
echoOne: function(/*arguments*/) {
check(arguments, [Match.Any]);
@@ -34,9 +36,9 @@ Meteor.methods({
throw e;
}
},
setUserId: function(userId) {
async setUserId(userId) {
check(userId, Match.OneOf(String, null));
this.setUserId(userId);
await this.setUserId(userId);
}
});
@@ -218,14 +220,14 @@ if (Meteor.isServer) {
if (Meteor.isServer) {
Meteor.methods({
setUserIdAfterUnblock: function() {
async setUserIdAfterUnblock() {
this.unblock();
let threw = false;
const originalUserId = this.userId;
try {
// Calling setUserId after unblock should throw an error (and not mutate
// userId).
this.setUserId(originalUserId + 'bla');
await this.setUserId(originalUserId + 'bla');
} catch (e) {
threw = true;
}
@@ -243,15 +245,14 @@ if (Meteor.isServer) {
const collName = 'overlappingUniversalSubs';
const universalSubscribers = [[], []];
_.each([0, 1], function(index) {
[0, 1].forEach(function(index) {
Meteor.publish(null, function() {
const sub = this;
universalSubscribers[index].push(sub);
sub.onStop(function() {
universalSubscribers[index] = _.without(
universalSubscribers[index],
sub
);
universalSubscribers[index] = universalSubscribers[index].filter(function(value) {
return value !== sub;
});
});
});
});
@@ -259,13 +260,13 @@ if (Meteor.isServer) {
Meteor.methods({
testOverlappingSubs: function(token) {
check(token, String);
_.each(universalSubscribers[0], function(sub) {
universalSubscribers[0].forEach(function(sub) {
sub.added(collName, token, {});
});
_.each(universalSubscribers[1], function(sub) {
universalSubscribers[1].forEach(function(sub) {
sub.added(collName, token, {});
});
_.each(universalSubscribers[0], function(sub) {
universalSubscribers[0].forEach(function(sub) {
sub.removed(collName, token);
});
}
@@ -370,11 +371,11 @@ Meteor.startup(async () => {
const resultByValueArrays = Object.create(null);
Meteor.methods({
getArray: function(testId) {
if (!_.has(resultByValueArrays, testId)) resultByValueArrays[testId] = [];
if (!has(resultByValueArrays, testId)) resultByValueArrays[testId] = [];
return resultByValueArrays[testId];
},
pushToArray: function(testId, value) {
if (!_.has(resultByValueArrays, testId)) resultByValueArrays[testId] = [];
if (!has(resultByValueArrays, testId)) resultByValueArrays[testId] = [];
resultByValueArrays[testId].push(value);
}
});

View File

@@ -1,8 +1,6 @@
import { DDP } from '../common/namespace.js';
import { Connection } from '../common/livedata_connection.js';
const _sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const callWhenSubReady = async (subName, handle, cb = () => {}) => {
let control = 0;
@@ -12,7 +10,7 @@ const callWhenSubReady = async (subName, handle, cb = () => {}) => {
if (control++ === 1000) {
throw new Error(`Subscribe to ${subName} is taking too long!`);
}
await _sleep(0);
await Meteor._sleepForMs(0);
return;
}
await cb();
@@ -438,8 +436,7 @@ const eavesdropOnCollection = function(
collection_name,
messages
) {
const old_livedata_data = _.bind(
livedata_connection._livedata_data,
const old_livedata_data = livedata_connection._livedata_data.bind(
livedata_connection
);
@@ -483,7 +480,7 @@ if (Meteor.isClient) {
) {
let actualAddedMessageCount = 0;
let actualRemovedMessageCount = 0;
_.each(messages, function(msg) {
messages.forEach(function(msg) {
if (msg.msg === 'added') ++actualAddedMessageCount;
else if (msg.msg === 'removed') ++actualRemovedMessageCount;
else test.fail({ unexpected: JSON.stringify(msg) });
@@ -492,10 +489,9 @@ if (Meteor.isClient) {
test.equal(actualRemovedMessageCount, expectedRemovedMessageCount);
expectedNamesInCollection.sort();
test.equal(
_.pluck(
objectsWithUsers.find({}, { sort: ['name'] }).fetch(),
'name'
),
objectsWithUsers.find({}, { sort: ['name'] }).fetch().map(function(x) {
return x.name;
}),
expectedNamesInCollection
);
messages.length = 0; // clear messages without creating a new object
@@ -782,7 +778,7 @@ if (Meteor.isClient) {
},
function(test, expect) {
test.equal(coll.find().count(), 0);
test.equal(_.size(conn._subscriptions), 0); // white-box test
test.equal(Object.keys(conn._subscriptions).length, 0); // white-box test
conn.subscribe(
'publisherErrors',
@@ -800,7 +796,7 @@ if (Meteor.isClient) {
// Because the last subscription is ready, we should have a document.
test.equal(coll.find().count(), 1);
test.isFalse(errorFromRerun);
test.equal(_.size(conn._subscriptions), 1); // white-box test
test.equal(Object.keys(conn._subscriptions).length, 1); // white-box test
conn.call(
'setUserId',
'bla',
@@ -815,7 +811,7 @@ if (Meteor.isClient) {
test.instanceOf(errorFromRerun, Meteor.Error);
test.equal(errorFromRerun.error, 412);
test.equal(errorFromRerun.reason, 'Explicit error');
test.equal(_.size(conn._subscriptions), 0); // white-box test
test.equal(Object.keys(conn._subscriptions).length, 0); // white-box test
conn.subscribe(
'publisherErrors',
@@ -833,7 +829,7 @@ if (Meteor.isClient) {
test.equal(coll.find().count(), 0);
// sub.stop does NOT call onError.
test.isFalse(gotErrorFromStopper);
test.equal(_.size(conn._subscriptions), 0); // white-box test
test.equal(Object.keys(conn._subscriptions).length, 0); // white-box test
conn._stream.disconnect({ _permanent: true });
},
];
@@ -879,7 +875,7 @@ if (Meteor.isClient) {
},
function(test, expect) {
test.equal(coll.find().count(), 0);
test.equal(_.size(conn._subscriptions), 0); // white-box test
test.equal(Object.keys(conn._subscriptions).length, 0); // white-box test
conn.subscribe(
'publisherErrors',
@@ -897,12 +893,8 @@ if (Meteor.isClient) {
// Because the last subscription is ready, we should have a document.
test.equal(coll.find().count(), 1);
test.isFalse(errorFromRerun);
test.equal(_.size(conn._subscriptions), 1); // white-box test
conn.call(
'setUserId',
'bla',
expect(function() {})
);
test.equal(Object.keys(conn._subscriptions).length, 1); // white-box test
conn.call('setUserId', 'bla', expect(function() {}));
},
function(test, expect) {
// Now that we've re-run, we should have stopped the subscription,
@@ -912,7 +904,7 @@ if (Meteor.isClient) {
test.instanceOf(errorFromRerun, Meteor.Error);
test.equal(errorFromRerun.error, 412);
test.equal(errorFromRerun.reason, 'Explicit error');
test.equal(_.size(conn._subscriptions), 0); // white-box test
test.equal(Object.keys(conn._subscriptions).length, 0); // white-box test
const expected = expect();
conn.subscribe(
@@ -933,7 +925,7 @@ if (Meteor.isClient) {
test.equal(coll.find().count(), 0);
// sub.stop does NOT call onError.
test.isFalse(gotErrorFromStopper);
test.equal(_.size(conn._subscriptions), 0); // white-box test
test.equal(Object.keys(conn._subscriptions).length, 0); // white-box test
conn._stream.disconnect({ _permanent: true });
},
];
@@ -1223,4 +1215,4 @@ testAsyncMulti('livedata - methods with nested stubs', [
// reconnection
// reconnection not resulting in method re-execution
// reconnection tolerating all kinds of lost messages (including data)
// [probably lots more]
// [probably lots more]

View File

@@ -5,7 +5,7 @@ StubStream = function() {
self.callbacks = Object.create(null);
};
_.extend(StubStream.prototype, {
Object.assign(StubStream.prototype, {
// Methods from Stream
on: function(name, callback) {
const self = this;

View File

@@ -99,11 +99,11 @@ DDPCommon.MethodInvocation = class MethodInvocation {
* @instance
* @param {String | null} userId The value that should be returned by `userId` on this connection.
*/
setUserId(userId) {
async setUserId(userId) {
if (this._calledUnblock) {
throw new Error("Can't call setUserId in a method after calling unblock");
}
this.userId = userId;
this._setUserId(userId);
await this._setUserId(userId);
}
};

View File

@@ -1,27 +1,23 @@
Package.describe({
summary: "Code shared beween ddp-client and ddp-server",
version: '1.4.3',
documentation: null
version: "1.4.4",
documentation: null,
});
Package.onUse(function (api) {
api.use([
'check',
'random',
'ecmascript',
'ejson',
'tracker',
'retry',
], ['client', 'server']);
api.use(
["check", "random", "ecmascript", "ejson", "tracker", "retry"],
["client", "server"]
);
api.addFiles('namespace.js');
api.addFiles("namespace.js");
api.addFiles('heartbeat.js', ['client', 'server']);
api.addFiles('utils.js', ['client', 'server']);
api.addFiles('method_invocation.js', ['client', 'server']);
api.addFiles('random_stream.js', ['client', 'server']);
api.addFiles("heartbeat.js", ["client", "server"]);
api.addFiles("utils.js", ["client", "server"]);
api.addFiles("method_invocation.js", ["client", "server"]);
api.addFiles("random_stream.js", ["client", "server"]);
api.export('DDPCommon');
api.export("DDPCommon");
});
Package.onTest(function (api) {

View File

@@ -11,6 +11,26 @@
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
"integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q=="
},
"lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
},
"lodash.isobject": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
"integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA=="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"permessage-deflate": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/permessage-deflate/-/permessage-deflate-0.1.7.tgz",

View File

@@ -16,11 +16,11 @@ DDPServer._Crossbar = function (options) {
self.factName = options.factName || null;
};
_.extend(DDPServer._Crossbar.prototype, {
Object.assign(DDPServer._Crossbar.prototype, {
// msg is a trigger or a notification
_collectionForMessage: function (msg) {
var self = this;
if (! _.has(msg, 'collection')) {
if (!('collection' in msg)) {
return '';
} else if (typeof(msg.collection) === 'string') {
if (msg.collection === '')
@@ -47,7 +47,7 @@ _.extend(DDPServer._Crossbar.prototype, {
var collection = self._collectionForMessage(trigger);
var record = {trigger: EJSON.clone(trigger), callback: callback};
if (! _.has(self.listenersByCollection, collection)) {
if (! (collection in self.listenersByCollection)) {
self.listenersByCollection[collection] = {};
self.listenersByCollectionCount[collection] = 0;
}
@@ -88,13 +88,13 @@ _.extend(DDPServer._Crossbar.prototype, {
var collection = self._collectionForMessage(notification);
if (! _.has(self.listenersByCollection, collection)) {
if (!(collection in self.listenersByCollection)) {
return;
}
var listenersForCollection = self.listenersByCollection[collection];
var callbackIds = [];
_.each(listenersForCollection, function (l, id) {
Object.entries(listenersForCollection).forEach(function ([id, l]) {
if (self._matches(notification, l.trigger)) {
callbackIds.push(id);
}
@@ -110,7 +110,7 @@ _.extend(DDPServer._Crossbar.prototype, {
// first gets reduced down to the empty object (and then never gets
// increased again).
for (const id of callbackIds) {
if (_.has(listenersForCollection, id)) {
if (id in listenersForCollection) {
await listenersForCollection[id].callback(notification);
}
}
@@ -150,10 +150,9 @@ _.extend(DDPServer._Crossbar.prototype, {
return false;
}
return _.all(trigger, function (triggerValue, key) {
return !_.has(notification, key) ||
EJSON.equals(triggerValue, notification[key]);
});
return Object.keys(trigger).every(function (key) {
return !(key in notification) || EJSON.equals(trigger[key], notification[key]);
});
}
});
@@ -164,4 +163,4 @@ _.extend(DDPServer._Crossbar.prototype, {
// message from being sent).
DDPServer._InvalidationCrossbar = new DDPServer._Crossbar({
factName: "invalidation-crossbar-listeners"
});
});

View File

@@ -1,5 +1,10 @@
import isEmpty from 'lodash.isempty';
import isString from 'lodash.isstring';
import isObject from 'lodash.isobject';
DDPServer = {};
// Publication strategies define how we handle data from published cursors at the collection level
// This allows someone to:
// - Choose a trade-off between client-server bandwidth and server memory usage
@@ -89,7 +94,7 @@ DDPServer._getCurrentFence = function () {
return currentInvocation ? currentInvocation.fence : undefined;
};
_.extend(SessionDocumentView.prototype, {
Object.assign(SessionDocumentView.prototype, {
getFields: function () {
var self = this;
@@ -197,7 +202,7 @@ Object.assign(SessionCollectionView.prototype, {
diff: function (previous) {
var self = this;
DiffSequence.diffMaps(previous.documents, self.documents, {
both: _.bind(self.diffDocument, self),
both: self.diffDocument.bind(self),
rightOnly: function (id, nowDV) {
self.callbacks.added(self.collectionName, id, nowDV.getFields());
@@ -243,7 +248,7 @@ Object.assign(SessionCollectionView.prototype, {
}
docView.existsIn.add(subscriptionHandle);
var changeCollector = {};
_.each(fields, function (value, key) {
Object.entries(fields).forEach(function ([key, value]) {
docView.changeField(
subscriptionHandle, key, value, changeCollector, true);
});
@@ -259,7 +264,7 @@ Object.assign(SessionCollectionView.prototype, {
var docView = self.documents.get(id);
if (!docView)
throw new Error("Could not find element with id " + id + " to change");
_.each(changed, function (value, key) {
Object.entries(changed).forEach(function ([key, value]) {
if (value === undefined)
docView.clearField(subscriptionHandle, key, changedResult);
else
@@ -396,19 +401,12 @@ var Session = function (server, version, socket, options) {
};
Object.assign(Session.prototype, {
_checkPublishPromiseBeforeSend(f) {
if (!this._publishCursorPromise) {
f();
return;
}
this._publishCursorPromise.finally(() => f());
},
sendReady: function (subscriptionIds) {
var self = this;
if (self._isSending) {
self.send({msg: "ready", subs: subscriptionIds});
} else {
_.each(subscriptionIds, function (subscriptionId) {
subscriptionIds.forEach(function (subscriptionId) {
self._pendingReady.push(subscriptionId);
});
}
@@ -426,7 +424,7 @@ Object.assign(Session.prototype, {
},
sendChanged(collectionName, id, fields) {
if (_.isEmpty(fields))
if (isEmpty(fields))
return;
if (this._canSend(collectionName)) {
@@ -448,9 +446,9 @@ Object.assign(Session.prototype, {
getSendCallbacks: function () {
var self = this;
return {
added: _.bind(self.sendAdded, self),
changed: _.bind(self.sendChanged, self),
removed: _.bind(self.sendRemoved, self)
added: self.sendAdded.bind(self),
changed: self.sendChanged.bind(self),
removed: self.sendRemoved.bind(self)
};
},
@@ -500,8 +498,8 @@ Object.assign(Session.prototype, {
// Make a shallow copy of the set of universal handlers and start them. If
// additional universal publishers start while we're running them (due to
// yielding), they will run separately as part of Server.publish.
var handlers = _.clone(self.server.universal_publish_handlers);
_.each(handlers, function (handler) {
var handlers = [...self.server.universal_publish_handlers];
handlers.forEach(function (handler) {
self._startSubscription(handler);
});
},
@@ -543,7 +541,7 @@ Object.assign(Session.prototype, {
// Defer calling the close callbacks, so that the caller closing
// the session isn't waiting for all the callbacks to complete.
_.each(self._closeCallbacks, function (callback) {
self._closeCallbacks.forEach(function (callback) {
callback();
});
});
@@ -556,13 +554,11 @@ Object.assign(Session.prototype, {
// It should be a JSON object (it will be stringified).
send: function (msg) {
const self = this;
this._checkPublishPromiseBeforeSend(() => {
if (self.socket) {
if (Meteor._printSentDDP)
Meteor._debug('Sent DDP', DDPCommon.stringifyDDP(msg));
self.socket.send(DDPCommon.stringifyDDP(msg));
}
});
if (self.socket) {
if (Meteor._printSentDDP)
Meteor._debug("Sent DDP", DDPCommon.stringifyDDP(msg));
self.socket.send(DDPCommon.stringifyDDP(msg));
}
},
// Send a connection error.
@@ -627,6 +623,7 @@ Object.assign(Session.prototype, {
var processNext = function () {
var msg = self.inQueue && self.inQueue.shift();
if (!msg) {
self.workerRunning = false;
return;
@@ -647,12 +644,13 @@ Object.assign(Session.prototype, {
return true;
});
if (_.has(self.protocol_handlers, msg.msg)) {
if (msg.msg in self.protocol_handlers) {
const result = self.protocol_handlers[msg.msg].call(
self,
msg,
unblock
);
if (Meteor._isPromise(result)) {
result.finally(() => unblock());
} else {
@@ -681,7 +679,7 @@ Object.assign(Session.prototype, {
// reject malformed messages
if (typeof (msg.id) !== "string" ||
typeof (msg.name) !== "string" ||
(('params' in msg) && !(msg.params instanceof Array))) {
('params' in msg && !(msg.params instanceof Array))) {
self.sendError("Malformed subscription", msg);
return;
}
@@ -750,7 +748,7 @@ Object.assign(Session.prototype, {
// for forwards compatibility.
if (typeof (msg.id) !== "string" ||
typeof (msg.method) !== "string" ||
(('params' in msg) && !(msg.params instanceof Array)) ||
('params' in msg && !(msg.params instanceof Array)) ||
(('randomSeed' in msg) && (typeof msg.randomSeed !== "string"))) {
self.sendError("Malformed method invocation", msg);
return;
@@ -782,15 +780,13 @@ Object.assign(Session.prototype, {
return;
}
var setUserId = function(userId) {
self._setUserId(userId);
};
var invocation = new DDPCommon.MethodInvocation({
name: msg.method,
isSimulation: false,
userId: self.userId,
setUserId: setUserId,
setUserId(userId) {
return self._setUserId(userId);
},
unblock: unblock,
connection: self.connectionHandle,
randomSeed: randomSeed,
@@ -823,31 +819,16 @@ Object.assign(Session.prototype, {
}
}
const getCurrentMethodInvocationResult = () =>
DDP._CurrentMethodInvocation.withValue(
resolve(DDPServer._CurrentWriteFence.withValue(
fence,
() => DDP._CurrentMethodInvocation.withValue(
invocation,
() =>
maybeAuditArgumentChecks(
handler,
invocation,
msg.params,
"call to '" + msg.method + "'"
),
{
name: 'getCurrentMethodInvocationResult',
keyName: 'getCurrentMethodInvocationResult',
}
);
resolve(
DDPServer._CurrentWriteFence.withValue(
fence,
getCurrentMethodInvocationResult,
{
name: 'DDPServer._CurrentWriteFence',
keyName: '_CurrentWriteFence',
}
() => maybeAuditArgumentChecks(
handler, invocation, msg.params,
"call to '" + msg.method + "'"
)
)
);
));
});
async function finish() {
@@ -903,7 +884,7 @@ Object.assign(Session.prototype, {
// Sets the current user id in all appropriate contexts and reruns
// all subscriptions
_setUserId: function(userId) {
async _setUserId(userId) {
var self = this;
if (userId !== null && typeof userId !== "string")
@@ -938,19 +919,21 @@ Object.assign(Session.prototype, {
// DDP._CurrentMethodInvocation set. But DDP._CurrentMethodInvocation is not
// expected to be set inside a publish function, so we temporary unset it.
// Inside a publish function DDP._CurrentPublicationInvocation is set.
DDP._CurrentMethodInvocation.withValue(undefined, function () {
await DDP._CurrentMethodInvocation.withValue(undefined, async function () {
// Save the old named subs, and reset to having no subscriptions.
var oldNamedSubs = self._namedSubs;
self._namedSubs = new Map();
self._universalSubs = [];
oldNamedSubs.forEach(function (sub, subscriptionId) {
var newSub = sub._recreate();
await Promise.all([...oldNamedSubs].map(async ([subscriptionId, sub]) => {
const newSub = sub._recreate();
self._namedSubs.set(subscriptionId, newSub);
// nb: if the handler throws or calls this.error(), it will in fact
// immediately send its 'nosub'. This is OK, though.
newSub._runHandler();
});
await newSub._runHandler();
}));
// Allow newly-created universal subs to be started on our connection in
// parallel with the ones we're spinning up here, and spin up universal
@@ -965,7 +948,7 @@ Object.assign(Session.prototype, {
Meteor._noYieldsAllowed(function () {
self._isSending = true;
self._diffCollectionViews(beforeCVs);
if (!_.isEmpty(self._pendingReady)) {
if (!isEmpty(self._pendingReady)) {
self.sendReady(self._pendingReady);
self._pendingReady = [];
}
@@ -1054,7 +1037,7 @@ Object.assign(Session.prototype, {
return self.socket.remoteAddress;
var forwardedFor = self.socket.headers["x-forwarded-for"];
if (! _.isString(forwardedFor))
if (!isString(forwardedFor))
return null;
forwardedFor = forwardedFor.trim().split(/\s*,\s*/);
@@ -1207,16 +1190,16 @@ Object.assign(Subscription.prototype, {
resultOrThenable && typeof resultOrThenable.then === 'function';
if (isThenable) {
try {
self._publishHandlerResult(await resultOrThenable);
await self._publishHandlerResult(await resultOrThenable);
} catch(e) {
self.error(e)
}
} else {
self._publishHandlerResult(resultOrThenable);
await self._publishHandlerResult(resultOrThenable);
}
},
_publishHandlerResult: function (res) {
async _publishHandlerResult (res) {
// SPECIAL CASE: Instead of writing their own callbacks that invoke
// this.added/changed/ready/etc, the user can just return a collection
// cursor or array of cursors from the publish function; we call their
@@ -1239,14 +1222,18 @@ Object.assign(Subscription.prototype, {
return c && c._publishCursor;
};
if (isCursor(res)) {
this._publishCursorPromise = res._publishCursor(self).then(() => {
// _publishCursor only returns after the initial added callbacks have run.
// mark subscription as ready.
self.ready();
}).catch((e) => self.error(e));
} else if (_.isArray(res)) {
try {
await res._publishCursor(self);
} catch (e) {
self.error(e);
return;
}
// _publishCursor only returns after the initial added callbacks have run.
// mark subscription as ready.
self.ready();
} else if (Array.isArray(res)) {
// Check all the elements are cursors
if (! _.all(res, isCursor)) {
if (! res.every(isCursor)) {
self.error(new Error("Publish function returned an array of non-Cursors"));
return;
}
@@ -1254,24 +1241,25 @@ Object.assign(Subscription.prototype, {
// XXX we should support overlapping cursors, but that would require the
// merge box to allow overlap within a subscription
var collectionNames = {};
for (var i = 0; i < res.length; ++i) {
var collectionName = res[i]._getCollectionName();
if (_.has(collectionNames, collectionName)) {
if (collectionNames[collectionName]) {
self.error(new Error(
"Publish function returned multiple cursors for collection " +
collectionName));
return;
}
collectionNames[collectionName] = true;
};
}
this._publishCursorPromise = Promise.all(
res.map(c => c._publishCursor(self))
)
.then(() => {
self.ready();
})
.catch((e) => self.error(e));
try {
await Promise.all(res.map(cur => cur._publishCursor(self)));
} catch (e) {
self.error(e);
return;
}
self.ready();
} else if (res) {
// Truthy values other than cursors or arrays are probably a
// user mistake (possible returning a Mongo document via, say,
@@ -1301,7 +1289,7 @@ Object.assign(Subscription.prototype, {
// Tell listeners, so they can clean up
var callbacks = self._stopCallbacks;
self._stopCallbacks = [];
_.each(callbacks, function (callback) {
callbacks.forEach(function (callback) {
callback();
});
},
@@ -1409,7 +1397,6 @@ Object.assign(Subscription.prototype, {
ids.add(id);
}
this._session._publishCursorPromise = this._publishCursorPromise;
this._session.added(this._subscriptionHandle, collectionName, id, fields);
},
@@ -1589,33 +1576,33 @@ Object.assign(Server.prototype, {
},
/**
* @summary Set publication strategy for the given collection. Publications strategies are available from `DDPServer.publicationStrategies`. You call this method from `Meteor.server`, like `Meteor.server.setPublicationStrategy()`
* @summary Set publication strategy for the given publication. Publications strategies are available from `DDPServer.publicationStrategies`. You call this method from `Meteor.server`, like `Meteor.server.setPublicationStrategy()`
* @locus Server
* @alias setPublicationStrategy
* @param collectionName {String}
* @param publicationName {String}
* @param strategy {{useCollectionView: boolean, doAccountingForCollection: boolean}}
* @memberOf Meteor.server
* @importFromPackage meteor
*/
setPublicationStrategy(collectionName, strategy) {
setPublicationStrategy(publicationName, strategy) {
if (!Object.values(publicationStrategies).includes(strategy)) {
throw new Error(`Invalid merge strategy: ${strategy}
for collection ${collectionName}`);
for collection ${publicationName}`);
}
this._publicationStrategies[collectionName] = strategy;
this._publicationStrategies[publicationName] = strategy;
},
/**
* @summary Gets the publication strategy for the requested collection. You call this method from `Meteor.server`, like `Meteor.server.getPublicationStrategy()`
* @summary Gets the publication strategy for the requested publication. You call this method from `Meteor.server`, like `Meteor.server.getPublicationStrategy()`
* @locus Server
* @alias getPublicationStrategy
* @param collectionName {String}
* @param publicationName {String}
* @memberOf Meteor.server
* @importFromPackage meteor
* @return {{useCollectionView: boolean, doAccountingForCollection: boolean}}
*/
getPublicationStrategy(collectionName) {
return this._publicationStrategies[collectionName]
getPublicationStrategy(publicationName) {
return this._publicationStrategies[publicationName]
|| this.options.defaultPublicationStrategy;
},
@@ -1637,9 +1624,9 @@ Object.assign(Server.prototype, {
// The connect message must specify a version and an array of supported
// versions, and it must claim to support what it is proposing.
if (!(typeof (msg.version) === 'string' &&
_.isArray(msg.support) &&
_.all(msg.support, _.isString) &&
_.contains(msg.support, msg.version))) {
Array.isArray(msg.support) &&
msg.support.every(isString) &&
msg.support.includes(msg.version))) {
socket.send(DDPCommon.stringifyDDP({msg: 'failed',
version: DDPCommon.SUPPORTED_DDP_VERSIONS[0]}));
socket.close();
@@ -1704,7 +1691,7 @@ Object.assign(Server.prototype, {
publish: function (name, handler, options) {
var self = this;
if (! _.isObject(name)) {
if (!isObject(name)) {
options = options || {};
if (name && name in self.publish_handlers) {
@@ -1752,7 +1739,7 @@ Object.assign(Server.prototype, {
}
}
else{
_.each(name, function(value, key) {
Object.entries(name).forEach(function([key, value]) {
self.publish(key, value, {});
});
}
@@ -1783,7 +1770,7 @@ Object.assign(Server.prototype, {
*/
methods: function (methods) {
var self = this;
_.each(methods, function (func, name) {
Object.entries(methods).forEach(function ([name, func]) {
if (typeof func !== 'function')
throw new Error("Method '" + name + "' must be a function");
if (self.method_handlers[name])
@@ -1863,25 +1850,22 @@ Object.assign(Server.prototype, {
// get the user state from the outer method or publish function, otherwise
// don't allow setUserId to be called
var userId = null;
var setUserId = function() {
let setUserId = () => {
throw new Error("Can't call setUserId on a server initiated method call");
};
var connection = null;
var currentMethodInvocation = DDP._CurrentMethodInvocation.get();
var currentPublicationInvocation = DDP._CurrentPublicationInvocation.get();
var randomSeed = null;
if (currentMethodInvocation) {
userId = currentMethodInvocation.userId;
setUserId = function(userId) {
currentMethodInvocation.setUserId(userId);
};
setUserId = (userId) => currentMethodInvocation.setUserId(userId);
connection = currentMethodInvocation.connection;
randomSeed = DDPCommon.makeRpcSeed(currentMethodInvocation, name);
} else if (currentPublicationInvocation) {
userId = currentPublicationInvocation.userId;
setUserId = function(userId) {
currentPublicationInvocation._session._setUserId(userId);
};
setUserId = (userId) => currentPublicationInvocation._session._setUserId(userId);
connection = currentPublicationInvocation.connection;
}
@@ -1926,8 +1910,8 @@ Object.assign(Server.prototype, {
var calculateVersion = function (clientSupportedVersions,
serverSupportedVersions) {
var correctVersion = _.find(clientSupportedVersions, function (version) {
return _.contains(serverSupportedVersions, version);
var correctVersion = clientSupportedVersions.find(function (version) {
return serverSupportedVersions.includes(version);
});
if (!correctVersion) {
correctVersion = serverSupportedVersions[0];
@@ -1967,8 +1951,7 @@ var wrapInternalException = function (exception, context) {
// Did the error contain more details that could have been useful if caught in
// server code (or if thrown from non-client-originated code), but also
// provided a "sanitized" version with more context than 500 Internal server
// error? Use that.
// provided a "sanitized" version with more context than 500 Internal server error? Use that.
if (exception.sanitizedError) {
if (exception.sanitizedError.isClientSafe)
return exception.sanitizedError;
@@ -1989,4 +1972,4 @@ var maybeAuditArgumentChecks = function (f, context, args, description) {
f, context, args, description);
}
return f.apply(context, args);
};
};

View File

@@ -7,7 +7,7 @@ function sleep(ms) {
var onSubscription = {};
Meteor.publish('livedata_server_test_sub_async', async function(connectionId) {
await sleep(50);
await Meteor._sleepForMs(50);
var callback = onSubscription[connectionId];
if (callback) callback(this);
this.stop();
@@ -17,7 +17,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function(
connectionId,
userId
) {
await sleep(50);
await Meteor._sleepForMs(50);
var callback = onSubscription[connectionId];
var methodInvocation = DDP._CurrentMethodInvocation.get();
var publicationInvocation = DDP._CurrentPublicationInvocation.get();
@@ -99,7 +99,7 @@ let onSubscriptions = {};
Meteor.publish({
async publicationObjectAsync() {
await sleep(50);
await Meteor._sleepForMs(50);
let callback = onSubscriptions;
if (callback) callback("publicationObjectAsync");
this.stop();
@@ -108,7 +108,7 @@ Meteor.publish({
Meteor.publish({
publication_object_async: async function() {
await sleep(50);
await Meteor._sleepForMs(50);
let callback = onSubscriptions;
if (callback) callback("publication_object_async");
this.stop();
@@ -116,7 +116,7 @@ Meteor.publish({
});
Meteor.publish('publication_compatibility_async', async function() {
await sleep(50);
await Meteor._sleepForMs(50);
let callback = onSubscriptions;
if (callback) callback("publication_compatibility_async");
this.stop();
@@ -133,7 +133,6 @@ Tinytest.addAsync('livedata server - async publish object', function(
// for debugging
// console.log('subscription is ok:', subscription)
testsLength++;
delete onSubscriptions;
if (testsLength === 3) {
clientConn.disconnect();
onComplete();

View File

@@ -303,7 +303,6 @@ Tinytest.addAsync(
let testsLength = 0;
onSubscriptions = function (subscription) {
delete onSubscriptions;
clientConn.disconnect();
testsLength++;
if (testsLength == 3) {
@@ -442,3 +441,66 @@ Tinytest.addAsync("livedata server - waiting for Promise", (test, onComplete) =>
.then(onComplete);
})
);
/**
* https://github.com/meteor/meteor/issues/13212
*/
Tinytest.addAsync('livedata server - publish cursor is properly awaited', async function (test) {
let sub = null;
const { conn, messages, cleanup } = await captureConnectionMessages(test);
const coll = new Mongo.Collection('items', {
defineMutationMethods: false,
});
for (let i = 0; i < 10; i++) {
await coll.removeAsync({ _id: `item_${i}` })
await coll.insertAsync({ _id: `item_${i}`, title: `Item #${i}` });
}
const publicationName = `publication_${Random.id()}`
delete Meteor.server.publish_handlers[publicationName];
Meteor.publish(publicationName, async function (count) {
return coll.find({}, { limit: count });
});
const reactiveVar = new ReactiveVar(1);
const computation = Tracker.autorun(() => {
sub = conn.subscribe(publicationName, reactiveVar.get());
});
await Meteor._sleepForMs(100);
reactiveVar.set(2);
await Meteor._sleepForMs(100);
const expectedMessages = ['sub', 'added', 'ready', 'sub', 'unsub', 'added', 'ready', 'nosub']
/**
* There shouldn't ever be `removed` messages here, otherwise the UI will glitch
*/
const parsedMessages = messages.map(m => m.msg)
test.equal(parsedMessages, expectedMessages)
computation.stop();
cleanup()
});
function getTestConnections(test) {
return new Promise((resolve, reject) => {
makeTestConnection(test, (clientConn, serverConn) => {
resolve({ clientConn, serverConn });
}, reject);
})
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -1,61 +1,79 @@
Package.describe({
summary: "Meteor's latency-compensated distributed data server",
version: '3.0.0',
documentation: null
version: "3.0.2",
documentation: null,
});
Npm.depends({
"permessage-deflate": "0.1.7",
sockjs: "0.3.24"
sockjs: "0.3.24",
"lodash.once": "4.1.1",
"lodash.isempty": "4.4.0",
"lodash.isstring": "4.0.1",
"lodash.isobject": "3.0.2"
});
Package.onUse(function (api) {
api.use(['check', 'random', 'ejson', 'underscore',
'retry', 'mongo-id', 'diff-sequence', 'ecmascript'],
'server');
api.use(
[
"check",
"random",
"ejson",
"retry",
"mongo-id",
"diff-sequence",
"ecmascript",
],
"server"
);
// common functionality
api.use('ddp-common', 'server'); // heartbeat
api.use('ddp-rate-limiter', 'server', {weak: true});
api.use("ddp-common", "server"); // heartbeat
api.use("ddp-rate-limiter", "server", { weak: true });
// Transport
api.use('ddp-client', 'server');
api.imply('ddp-client');
api.use("ddp-client", "server");
api.imply("ddp-client");
api.use(['webapp', 'routepolicy'], 'server');
api.use(["webapp", "routepolicy"], "server");
// Detect whether or not the user wants us to audit argument checks.
api.use(['audit-argument-checks'], 'server', {weak: true});
api.use(["audit-argument-checks"], "server", { weak: true });
// Allow us to detect 'autopublish', so we can print a warning if the user
// runs Meteor.publish while it's loaded.
api.use('autopublish', 'server', {weak: true});
api.use("autopublish", "server", { weak: true });
// If the facts package is loaded, publish some statistics.
api.use('facts-base', 'server', {unordered: true});
api.use("facts-base", "server", { unordered: true });
api.use('callback-hook', 'server');
api.export('DDPServer', 'server');
api.use("callback-hook", "server");
api.export("DDPServer", "server");
api.addFiles('stream_server.js', 'server');
api.addFiles("stream_server.js", "server");
api.addFiles('livedata_server.js', 'server');
api.addFiles('writefence.js', 'server');
api.addFiles('crossbar.js', 'server');
api.addFiles("livedata_server.js", "server");
api.addFiles("writefence.js", "server");
api.addFiles("crossbar.js", "server");
api.addFiles('server_convenience.js', 'server');
api.addFiles("server_convenience.js", "server");
});
Package.onTest(function (api) {
api.use('ecmascript', ['client', 'server']);
api.use('livedata', ['client', 'server']);
api.use('mongo', ['client', 'server']);
api.use('test-helpers', ['client', 'server']);
api.use(['underscore', 'tinytest', 'random', 'tracker', 'minimongo', 'reactive-var']);
api.use("ecmascript", ["client", "server"]);
api.use("ejson", ["client", "server"]);
api.use("livedata", ["client", "server"]);
api.use("mongo", ["client", "server"]);
api.use("test-helpers", ["client", "server"]);
api.use([
"tinytest",
"random",
"tracker",
"minimongo",
"reactive-var",
]);
api.addFiles('livedata_server_tests.js', 'server');
api.addFiles('livedata_server_async_tests.js', 'server');
api.addFiles('session_view_tests.js', ['server']);
api.addFiles('crossbar_tests.js', ['server']);
api.addFiles("livedata_server_tests.js", "server");
api.addFiles("livedata_server_async_tests.js", "server");
api.addFiles("session_view_tests.js", ["server"]);
api.addFiles("crossbar_tests.js", ["server"]);
});

View File

@@ -11,7 +11,7 @@ Meteor.refresh = async function (notification) {
// Proxy the public methods of Meteor.server so they can
// be called directly on Meteor.
_.each(
[
'publish',
'isAsyncCall',
@@ -22,8 +22,8 @@ _.each(
'applyAsync',
'onConnection',
'onMessage',
],
].forEach(
function(name) {
Meteor[name] = _.bind(Meteor.server[name], Meteor.server);
Meteor[name] = Meteor.server[name].bind(Meteor.server);
}
);

View File

@@ -1,3 +1,5 @@
import isEmpty from 'lodash.isempty';
var newView = function(test) {
var results = [];
var view = new DDPServer._SessionCollectionView('test', {
@@ -5,7 +7,7 @@ var newView = function(test) {
results.push({fun: 'added', id: id, fields: fields});
},
changed: function (collection, id, changed) {
if (_.isEmpty(changed))
if (isEmpty(changed))
return;
results.push({fun: 'changed', id: id, changed: changed});
},
@@ -17,8 +19,8 @@ var newView = function(test) {
view: view,
results: results
};
_.each(["added", "changed", "removed"], function (it) {
v[it] = _.bind(view[it], view);
["added", "changed", "removed"].forEach(function (it) {
v[it] = view[it].bind(view);
});
v.expectResult = function (result) {
test.equal(results.shift(), result);
@@ -182,7 +184,7 @@ Tinytest.add('livedata - sessionview - change to canonical value produces no cha
v.added("B", "A1", {foo: "baz"});
var canon = "bar";
var maybeResults = v.drain();
if (!_.isEmpty(maybeResults)) {
if (!isEmpty(maybeResults)) {
// if something happened, it was a change message to baz.
// if nothing did, canon is still bar.
test.length(maybeResults, 1);
@@ -390,4 +392,4 @@ Tinytest.add('livedata - sessionview - clear undefined value', function (test) {
v.changed("A", "A1", {field: undefined});
v.expectNoResult();
});
});

View File

@@ -1,3 +1,5 @@
import once from 'lodash.once';
// By default, we use the permessage-deflate extension with default
// configuration. If $SERVER_WEBSOCKET_COMPRESSION is set, then it must be valid
// JSON. If it represents a falsey value, then we do not use permessage-deflate
@@ -9,7 +11,7 @@
// crash the tool during isopacket load if your JSON doesn't parse. This is only
// a problem because the tool has to load the DDP server code just in order to
// be a DDP client; see https://github.com/meteor/meteor/issues/3452 .)
var websocketExtensions = _.once(function () {
var websocketExtensions = once(function () {
var extensions = [];
var websocketCompressionConfig = process.env.SERVER_WEBSOCKET_COMPRESSION
@@ -116,7 +118,9 @@ StreamServer = function () {
socket.write(data);
};
socket.on('close', function () {
self.open_sockets = _.without(self.open_sockets, socket);
self.open_sockets = self.open_sockets.filter(function(value) {
return value !== socket;
});
});
self.open_sockets.push(socket);
@@ -128,7 +132,7 @@ StreamServer = function () {
// call all our callbacks when we get a new socket. they will do the
// work of setting up handlers and such for specific messages.
_.each(self.registration_callbacks, function (callback) {
self.registration_callbacks.forEach(function (callback) {
callback(socket);
});
});
@@ -141,7 +145,7 @@ Object.assign(StreamServer.prototype, {
register: function (callback) {
var self = this;
self.registration_callbacks.push(callback);
_.each(self.all_sockets(), function (socket) {
self.all_sockets().forEach(function (socket) {
callback(socket);
});
},
@@ -149,7 +153,7 @@ Object.assign(StreamServer.prototype, {
// get a list of all sockets
all_sockets: function () {
var self = this;
return _.values(self.open_sockets);
return Object.values(self.open_sockets);
},
// Redirect /websocket to /sockjs/websocket in order to not expose
@@ -183,11 +187,11 @@ Object.assign(StreamServer.prototype, {
parsedUrl.pathname = self.prefix + '/websocket';
request.url = url.format(parsedUrl);
}
_.each(oldHttpServerListeners, function(oldListener) {
oldHttpServerListeners.forEach(function(oldListener) {
oldListener.apply(httpServer, args);
});
};
httpServer.addListener(event, newListener);
});
}
});
});

View File

@@ -2,9 +2,9 @@
"lockfileVersion": 4,
"dependencies": {
"core-js": {
"version": "3.16.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz",
"integrity": "sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g=="
"version": "3.38.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz",
"integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw=="
}
}
}

View File

@@ -7,7 +7,7 @@ Package.describe({
});
Npm.depends({
"core-js": "3.16.0"
"core-js": "3.38.1"
});
Package.onUse(function(api) {

View File

@@ -1,6 +1,6 @@
Package.describe({
name: "ecmascript-runtime",
version: '0.8.2',
version: '0.8.3',
summary: "Polyfills for new ECMAScript 2015 APIs like Map and Set",
git: "https://github.com/meteor/ecmascript-runtime",
documentation: "README.md"

View File

@@ -2,9 +2,9 @@
"lockfileVersion": 4,
"dependencies": {
"@types/node": {
"version": "20.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
"integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="
"version": "22.7.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ=="
},
"@types/nodemailer": {
"version": "6.4.14",
@@ -57,9 +57,9 @@
"integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ=="
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
}
}
}

View File

@@ -6,10 +6,6 @@ const CUSTOM_TRANSPORT_SETTINGS = {
email: { service: '1on1', user: 'test', password: 'pwd' },
};
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
// Create dynamic async tests
TEST_CASES.forEach(({ title, options, testCalls }) => {
Tinytest.addAsync(`${title}`, async function (test, onComplete) {
@@ -222,7 +218,7 @@ Tinytest.addAsync(
async function (test) {
Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS;
Email.customTransport = async (options) => {
await sleep(3000);
await Meteor._sleepForMs(3000);
test.equal(options.from, 'foo@example.com');
test.equal(options.packageSettings?.service, '1on1');
};

View File

@@ -1,25 +1,25 @@
Package.describe({
summary: 'Send email messages',
version: '3.0.0',
summary: "Send email messages",
version: "3.1.0",
});
Npm.depends({
nodemailer: '6.9.10',
'stream-buffers': '3.0.2',
'@types/nodemailer': '6.4.14',
'nodemailer-openpgp' : '2.2.0'
nodemailer: "6.9.10",
"stream-buffers": "3.0.2",
"@types/nodemailer": "6.4.14",
"nodemailer-openpgp": "2.2.0",
});
Package.onUse(function(api) {
api.use(['ecmascript', 'logging', 'callback-hook'], 'server');
api.addAssets('email.d.ts', 'server');
api.mainModule('email.js', 'server');
api.export(['Email', 'EmailInternals'], 'server');
api.export('EmailTest', 'server', { testOnly: true });
Package.onUse(function (api) {
api.use(["ecmascript", "logging", "callback-hook"], "server");
api.addAssets("email.d.ts", "server");
api.mainModule("email.js", "server");
api.export(["Email", "EmailInternals"], "server");
api.export("EmailTest", "server", { testOnly: true });
});
Package.onTest(function(api) {
api.use('email', 'server');
api.use(['tinytest', 'ecmascript']);
api.addFiles('email_tests.js', 'server');
Package.onTest(function (api) {
api.use("email", "server");
api.use(["tinytest", "ecmascript"]);
api.addFiles("email_tests.js", "server");
});

View File

@@ -346,9 +346,9 @@
"integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ=="
},
"regexp.prototype.flags": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
"integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw=="
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
"integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ=="
},
"remove-trailing-separator": {
"version": "1.1.0",

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: 'The Meteor command-line tool',
version: '3.0.1',
version: '3.0.4',
});
Package.includeTool();

View File

@@ -85,6 +85,10 @@ Meteor.promisify = function (fn, context, errorFirst) {
}
return function () {
var self = this;
var filteredArgs = Array.prototype.slice.call(arguments)
.filter(function (i) { return i !== undefined; });
return new Promise(function (resolve, reject) {
var callback = Meteor.bindEnvironment(function (error, result) {
var _error = error, _result = result;
@@ -100,11 +104,9 @@ Meteor.promisify = function (fn, context, errorFirst) {
resolve(_result);
});
var filteredArgs = Array.prototype.slice.call(arguments)
.filter(function (i) { return i !== undefined; });
filteredArgs.push(callback);
return fn.apply(context || this, filteredArgs);
return fn.apply(context || self, filteredArgs);
});
};
};

View File

@@ -100,3 +100,24 @@ Tinytest.add("environment - startup", function (test) {
});
test.isTrue(called);
});
Tinytest.addAsync("environment - promisify", async function (test) {
function TestClass(value) {
this.value = value;
}
TestClass.prototype.method = function (arg1, arg2, callback) {
var value = this.value;
setTimeout(function () {
callback(null, arg1 + arg2 + value);
}, 0);
};
TestClass.prototype.methodAsync = Meteor.promisify(TestClass.prototype.method);
var instance = new TestClass(5);
test.equal(await instance.methodAsync(1, 2), 8);
var asyncMethodWithContext = Meteor.promisify(instance.method, instance);
test.equal(await asyncMethodWithContext(2, 3), 10);
});

View File

@@ -2,7 +2,7 @@
Package.describe({
summary: "Core Meteor environment",
version: '2.0.0',
version: '2.0.1',
});
Package.registerBuildPlugin({

View File

@@ -3301,9 +3301,7 @@ Tinytest.addAsync('minimongo - observe ordered', async test => {
handle = c
.find({ tags: "flower" }, { reactive: false })
.observe(makecb("c"));
// TODO: think about this one below.
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
await sleep(10);
await Meteor._sleepForMs(10);
expect("ac4_ac5_");
// This insert shouldn't trigger a callback because it's not reactive.
await c.insertAsync({ _id: 6, name: "river", tags: ["flower"] });

View File

@@ -1,52 +1,52 @@
Package.describe({
summary: "Meteor's client-side datastore: a port of MongoDB to Javascript",
version: '2.0.0',
version: "2.0.1",
});
Package.onUse(api => {
api.export('LocalCollection');
api.export('Minimongo');
Package.onUse((api) => {
api.export("LocalCollection");
api.export("Minimongo");
api.export('MinimongoTest', { testOnly: true });
api.export('MinimongoError', { testOnly: true });
api.export("MinimongoTest", { testOnly: true });
api.export("MinimongoError", { testOnly: true });
api.use([
// This package is used to get diff results on arrays and objects
'diff-sequence',
'ecmascript',
'ejson',
"diff-sequence",
"ecmascript",
"ejson",
// This package is used for geo-location queries such as $near
'geojson-utils',
'id-map',
'mongo-id',
'ordered-dict',
'random',
'tracker'
"geojson-utils",
"id-map",
"mongo-id",
"ordered-dict",
"random",
"tracker",
]);
// Make weak use of Decimal type on client
api.use('mongo-decimal', 'client', {weak: true});
api.use('mongo-decimal', 'server');
api.use("mongo-decimal", "client", { weak: true });
api.use("mongo-decimal", "server");
api.mainModule('minimongo_client.js', 'client');
api.mainModule('minimongo_server.js', 'server');
api.mainModule("minimongo_client.js", "client");
api.mainModule("minimongo_server.js", "server");
});
Package.onTest(api => {
api.use('minimongo');
Package.onTest((api) => {
api.use("minimongo");
api.use([
'ecmascript',
'ejson',
'mongo-id',
'ordered-dict',
'random',
'reactive-var',
'test-helpers',
'tinytest',
'tracker'
"ecmascript",
"ejson",
"mongo-id",
"ordered-dict",
"random",
"reactive-var",
"test-helpers",
"tinytest",
"tracker",
]);
api.addFiles('minimongo_tests.js');
api.addFiles('minimongo_tests_client.js', 'client');
api.addFiles('minimongo_tests_server.js', 'server');
api.addFiles("minimongo_tests.js");
api.addFiles("minimongo_tests_client.js", "client");
api.addFiles("minimongo_tests_server.js", "server");
});

View File

@@ -2,14 +2,14 @@
"lockfileVersion": 4,
"dependencies": {
"@meteorjs/reify": {
"version": "0.24.0",
"resolved": "git+ssh://git@github.com/meteor/reify.git#cf61c57c6c4fefcbf164bf63d3c12fda1924b3d2",
"integrity": "sha512-NN0E7fURAY9XrtgkmP1XqV6fMSwKHIjENOE6V5AqWXU0wynLlXb+l5NhMC72hYwbtT73R9E35dDR1lqBKQSFdg=="
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.3.tgz",
"integrity": "sha512-OVtWOLNvonGwA9Uowzp18q6L2Z3V/kPItS1bNyJMryfXFnosM2O0Hm3pYcxRfP36/0tc1BCiV3dA8yrr8RgMUA=="
},
"@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
},
"acorn": {
"version": "8.12.1",
@@ -42,9 +42,9 @@
"integrity": "sha512-FuCZe61mWxQOJAQFEfmt9FjzebRlcpFz8sFPbyaCKtdusPkMEbA9ey0eARnRav5zAhmXznhaQkKGFAPn7X9NUw=="
},
"semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
},
"sourcemap-codec": {
"version": "1.4.8",

View File

@@ -1,12 +1,12 @@
Package.describe({
name: "modules",
version: '0.20.1',
version: '0.20.2',
summary: "CommonJS module system",
documentation: "README.md"
});
Npm.depends({
"@meteorjs/reify": "git+https://github.com/meteor/reify.git#cf61c57c6c4fefcbf164bf63d3c12fda1924b3d2",
"@meteorjs/reify": "0.25.3",
"meteor-babel-helpers": "0.0.3",
});

View File

@@ -1,6 +1,46 @@
{
"lockfileVersion": 4,
"dependencies": {
"lodash.clone": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
"integrity": "sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg=="
},
"lodash.has": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
"integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g=="
},
"lodash.identity": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-3.0.0.tgz",
"integrity": "sha512-AupTIzdLQxJS5wIYUQlgGyk2XRTfGXA+MCghDHqZk0pzUNYvd3EESS6dkChNauNYVIutcb0dfHw1ri9Q1yPV8Q=="
},
"lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
},
"lodash.isobject": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
"integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
},
"lodash.times": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.times/-/lodash.times-4.3.2.tgz",
"integrity": "sha512-FfaJzl0SA35CRPDh5SWe2BTght6y5KSK7yJv166qIp/8q7qOwBDCvuDZE2RUSMRpBkLF6rZKbLEUoTmaP3qg6A=="
},
"mongodb-uri": {
"version": "0.9.7",
"resolved": "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz",

View File

@@ -1,3 +1,5 @@
import has from 'lodash.has';
if (Meteor.isServer) {
// Set up allow/deny rules for test collections
@@ -21,7 +23,7 @@ if (Meteor.isServer) {
var fullName = name + idGeneration + nonce;
var collection;
if (_.has(allowCollections, fullName)) {
if (has(allowCollections, fullName)) {
collection = allowCollections[fullName];
if (needToConfigure === true)
throw new Error("collections inconsistently exist");
@@ -126,7 +128,7 @@ if (Meteor.isServer) {
return doc.canInsert2;
},
updateAsync: function(userId, doc, fields, modifier) {
return -1 !== _.indexOf(fields, 'canUpdate2');
return -1 !== fields.indexOf('canUpdate2');
},
removeAsync: function(userId, doc) {
return doc.canRemove2;
@@ -144,22 +146,22 @@ if (Meteor.isServer) {
}, {
insertAsync: function(userId, doc) {
// Don't allow explicit ID to be set by the client.
return _.has(doc, '_id');
return has(doc, '_id');
},
updateAsync: function(userId, doc, fields, modifier) {
return -1 !== _.indexOf(fields, 'verySecret');
return -1 !== fields.indexOf('verySecret');
}
}];
_.each([
[
restrictedCollectionDefaultSecure,
restrictedCollectionDefaultInsecure,
restrictedCollectionForUpdateOptionsTest
], function (collection) {
_.each(allows, function (allow) {
].forEach(function (collection) {
allows.forEach(function (allow) {
collection.allow(allow);
});
_.each(denies, function (deny) {
denies.forEach(function (deny) {
collection.deny(deny);
});
});
@@ -180,12 +182,12 @@ if (Meteor.isServer) {
updateAsync: function(userId, doc) {
// throw fields in doc so that we can inspect them in test
throw new Meteor.Error(
999, "Test: Fields in doc: " + _.keys(doc).sort().join(','));
999, "Test: Fields in doc: " + Object.keys(doc).sort().join(','));
},
removeAsync: function(userId, doc) {
// throw fields in doc so that we can inspect them in test
throw new Meteor.Error(
999, "Test: Fields in doc: " + _.keys(doc).sort().join(','));
999, "Test: Fields in doc: " + Object.keys(doc).sort().join(','));
},
fetch: ['field1']
});
@@ -203,12 +205,12 @@ if (Meteor.isServer) {
updateAsync: function(userId, doc) {
// throw fields in doc so that we can inspect them in test
throw new Meteor.Error(
999, "Test: Fields in doc: " + _.keys(doc).sort().join(','));
999, "Test: Fields in doc: " + Object.keys(doc).sort().join(','));
},
removeAsync: function(userId, doc) {
// throw fields in doc so that we can inspect them in test
throw new Meteor.Error(
999, "Test: Fields in doc: " + _.keys(doc).sort().join(','));
999, "Test: Fields in doc: " + Object.keys(doc).sort().join(','));
},
fetch: ['field1']
});
@@ -222,7 +224,7 @@ if (Meteor.isServer) {
}
if (Meteor.isClient) {
_.each(['STRING', 'MONGO'], function (idGeneration) {
['STRING', 'MONGO'].forEach(function (idGeneration) {
// Set up a bunch of test collections... on the client! They match the ones
// created by setUpAllowTestsCollections.
@@ -730,8 +732,8 @@ if (Meteor.isClient) {
]);
})();
_.each(
[restrictedCollectionDefaultInsecure, restrictedCollectionDefaultSecure],
[restrictedCollectionDefaultInsecure, restrictedCollectionDefaultSecure].forEach(
function(collection) {
var canUpdateId, canRemoveId;
@@ -1078,7 +1080,7 @@ if (Meteor.isServer) {
collection.deny({invalidOption: true});
});
_.each(['insert', 'update', 'remove', 'fetch'], function (key) {
['insert', 'update', 'remove', 'fetch'].forEach(function (key) {
var options = {};
options[key] = true;
test.throws(function () {
@@ -1089,7 +1091,7 @@ if (Meteor.isServer) {
});
});
_.each(['insert', 'update', 'remove'], function (key) {
['insert', 'update', 'remove'].forEach(function (key) {
var options = {};
options[key] = false;
test.throws(function () {
@@ -1100,7 +1102,7 @@ if (Meteor.isServer) {
});
});
_.each(['insert', 'update', 'remove'], function (key) {
['insert', 'update', 'remove'].forEach(function (key) {
var options = {};
options[key] = undefined;
test.throws(function () {
@@ -1111,7 +1113,7 @@ if (Meteor.isServer) {
});
});
_.each(['insert', 'update', 'remove'], function (key) {
['insert', 'update', 'remove'].forEach(function (key) {
var options = {};
options[key] = ['an array']; // this should be a function, not an array
test.throws(function () {

View File

@@ -153,6 +153,8 @@ Mongo.Collection = function Collection(name, options) {
is_auto: true,
});
}
Mongo._collections.set(this._name, this);
};
Object.assign(Mongo.Collection.prototype, {
@@ -1208,6 +1210,28 @@ Object.assign(Mongo.Collection.prototype, {
},
});
Object.assign(Mongo, {
/**
* @summary Retrieve a Meteor collection instance by name. Only collections defined with [`new Mongo.Collection(...)`](#collections) are available with this method. For plain MongoDB collections, you'll want to look at [`rawDatabase()`](#Mongo-Collection-rawDatabase).
* @locus Anywhere
* @memberof Mongo
* @static
* @param {string} name Name of your collection as it was defined with `new Mongo.Collection()`.
* @returns {Mongo.Collection | undefined}
*/
getCollection(name) {
return this._collections.get(name);
},
/**
* @summary A record of all defined Mongo.Collection instances, indexed by collection name.
* @type {Map<string, Mongo.Collection>}
* @memberof Mongo
* @protected
*/
_collections: new Map(),
})
// Convert the callback to not return a result if there is an error
function wrapCallback(callback, convertResult) {
return (

View File

@@ -464,6 +464,15 @@ Tinytest.addAsync('collection - should not block on cursor mismatch (#12516)',
}
);
Tinytest.add('collection - get collection by name',
function (test) {
const collectionName = 'get' + test.id;
const collection = new Mongo.Collection(collectionName);
test.ok(Mongo.getCollection(collectionName) instanceof Mongo.Collection);
test.equal(Mongo.getCollection(collectionName), collection);
}
);
Meteor.isServer && Tinytest.addAsync('collection - simple add', async function(test){

View File

@@ -83,6 +83,15 @@ export namespace Mongo {
defineMutationMethods?: boolean | undefined;
}
): Collection<T, U>;
/**
* Retrieve a previously defined Mongo.Collection instance by its name. The collection must already have been defined with `new Mongo.Collection(name, ...)`.
* Plain MongoDB collections are not available by this method.
* @param name The name of the collection instance.
*/
getCollection<
TCollection extends Collection<any, any> | undefined = Collection<NpmModuleMongodb.Document> | undefined
>(name: string): TCollection;
}
interface Collection<T extends NpmModuleMongodb.Document, U = T> {
allow<Fn extends Transform<T> = undefined>(options: {

View File

@@ -1,4 +1,6 @@
import { normalizeProjection } from "./mongo_utils";
import has from 'lodash.has';
import identity from 'lodash.identity';
import clone from 'lodash.clone';
/**
* Provide a synchronous Collection API using fibers, backed by
@@ -47,11 +49,11 @@ const APP_FOLDER = 'app';
// inside an EJSON custom type. It should only be called on pure JSON!
var replaceNames = function (filter, thing) {
if (typeof thing === "object" && thing !== null) {
if (_.isArray(thing)) {
return _.map(thing, _.bind(replaceNames, null, filter));
if (Array.isArray(thing)) {
return thing.map(replaceNames.bind(null, filter));
}
var ret = {};
_.each(thing, function (value, key) {
Object.entries(thing).forEach(function ([key, value]) {
ret[filter(key)] = replaceNames(filter, value);
});
return ret;
@@ -85,7 +87,7 @@ var replaceMongoAtomWithMeteor = function (document) {
if (document instanceof MongoDB.Decimal128) {
return Decimal(document.toString());
}
if (document["EJSON$type"] && document["EJSON$value"] && _.size(document) === 2) {
if (document["EJSON$type"] && document["EJSON$value"] && Object.keys(document).length === 2) {
return EJSON.fromJSONValue(replaceNames(unmakeMongoLegal, document));
}
if (document instanceof MongoDB.Timestamp) {
@@ -138,12 +140,12 @@ var replaceTypes = function (document, atomTransformer) {
return replacedTopLevelAtom;
var ret = document;
_.each(document, function (val, key) {
Object.entries(document).forEach(function ([key, val]) {
var valReplaced = replaceTypes(val, atomTransformer);
if (val !== valReplaced) {
// Lazy clone. Shallow copy.
if (ret === document)
ret = _.clone(document);
ret = clone(document);
ret[key] = valReplaced;
}
});
@@ -170,12 +172,12 @@ MongoConnection = function (url, options) {
// Internally the oplog connections specify their own maxPoolSize
// which we don't want to overwrite with any user defined value
if (_.has(options, 'maxPoolSize')) {
if (has(options, 'maxPoolSize')) {
// If we just set this for "server", replSet will override it. If we just
// set it for replSet, it will be ignored if we're not using a replSet.
mongoOptions.maxPoolSize = options.maxPoolSize;
}
if (_.has(options, 'minPoolSize')) {
if (has(options, 'minPoolSize')) {
mongoOptions.minPoolSize = options.minPoolSize;
}
@@ -194,6 +196,11 @@ MongoConnection = function (url, options) {
self._oplogHandle = null;
self._docFetcher = null;
mongoOptions.driverInfo = {
name: 'Meteor',
version: Meteor.release
}
self.client = new MongoDB.MongoClient(url, mongoOptions);
self.db = self.client.db();
@@ -380,8 +387,8 @@ MongoConnection.prototype._refresh = async function (collectionName, selector) {
var specificIds = LocalCollection._idsMatchedBySelector(selector);
if (specificIds) {
for (const id of specificIds) {
await Meteor.refresh(_.extend({id: id}, refreshKey));
}
await Meteor.refresh(Object.assign({id: id}, refreshKey));
};
} else {
await Meteor.refresh(refreshKey);
}
@@ -738,7 +745,7 @@ MongoConnection.prototype.upsertAsync = async function (collectionName, selector
}
return self.updateAsync(collectionName, selector, mod,
_.extend({}, options, {
Object.assign({}, options, {
upsert: true,
_returnObject: true
}));
@@ -898,7 +905,7 @@ Cursor.prototype.countAsync = async function () {
Cursor.prototype.count = function () {
throw new Error(
"count() is not avaible on the server. Please use countAsync() instead."
"count() is not available on the server. Please use countAsync() instead."
);
};
@@ -988,9 +995,10 @@ Cursor.prototype.observeChangesAsync = async function (callbacks, options = {})
};
MongoConnection.prototype._createSynchronousCursor = function(
cursorDescription, options) {
cursorDescription, options = {}) {
var self = this;
options = _.pick(options || {}, 'selfForIteration', 'useTransform');
const { selfForIteration, useTransform } = options;
options = { selfForIteration, useTransform };
var collection = self.rawCollection(cursorDescription.collectionName);
var cursorOptions = cursorDescription.options;
@@ -1195,7 +1203,8 @@ class AsynchronousCursor {
var SynchronousCursor = function (dbCursor, cursorDescription, options, collection) {
var self = this;
options = _.pick(options || {}, 'selfForIteration', 'useTransform');
const { selfForIteration, useTransform } = options;
options = { selfForIteration, useTransform };
self._dbCursor = dbCursor;
self._cursorDescription = cursorDescription;
@@ -1219,7 +1228,7 @@ var SynchronousCursor = function (dbCursor, cursorDescription, options, collecti
self._visitedIds = new LocalCollection._IdMap;
};
_.extend(SynchronousCursor.prototype, {
Object.assign(SynchronousCursor.prototype, {
// Returns a Promise for the next object from the underlying cursor (before
// the Mongo->Meteor type replacement).
_rawNextObjectPromise: function () {
@@ -1246,7 +1255,7 @@ _.extend(SynchronousCursor.prototype, {
if (!doc) return null;
doc = replaceTypes(doc, replaceMongoAtomWithMeteor);
if (!self._cursorDescription.options.tailable && _.has(doc, '_id')) {
if (!self._cursorDescription.options.tailable && has(doc, '_id')) {
// Did Mongo give us duplicate documents in the same cursor? If so,
// ignore this one. (Do this before the transform, since transform might
// return some unrelated value.) We don't do this for tailable cursors,
@@ -1340,7 +1349,7 @@ _.extend(SynchronousCursor.prototype, {
fetch: function () {
var self = this;
return self.map(_.identity);
return self.map(identity);
},
count: function () {
@@ -1432,7 +1441,7 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo
lastTS = doc.ts;
docCallback(doc);
} else {
var newSelector = _.clone(cursorDescription.selector);
var newSelector = Object.assign({}, cursorDescription.selector);
if (lastTS) {
newSelector.ts = {$gt: lastTS};
}
@@ -1478,8 +1487,8 @@ Object.assign(MongoConnection.prototype, {
throw Error("You may not observe a cursor with {fields: {_id: 0}}");
}
var observeKey = EJSON.stringify(
_.extend({ordered: ordered}, cursorDescription));
var observeKey = EJSON.stringify(
Object.assign({ordered: ordered}, cursorDescription));
var multiplexer, observeDriver;
var firstHandle = false;
@@ -1487,7 +1496,7 @@ Object.assign(MongoConnection.prototype, {
// Find a matching ObserveMultiplexer, or create a new one. This next block is
// guaranteed to not yield (and it doesn't call anything that can observe a
// new query), so no other calls to this function can interleave with it.
if (_.has(self._observeMultiplexers, observeKey)) {
if (has(self._observeMultiplexers, observeKey)) {
multiplexer = self._observeMultiplexers[observeKey];
} else {
firstHandle = true;
@@ -1507,15 +1516,18 @@ Object.assign(MongoConnection.prototype, {
);
const oplogOptions = self?._oplogHandle?._oplogOptions || {};
const { includeCollections, excludeCollections } = oplogOptions;if (firstHandle) {
const { includeCollections, excludeCollections } = oplogOptions;
if (firstHandle) {
var matcher, sorter;
var canUseOplog = _.all([
var canUseOplog = [
function () {
// At a bare minimum, using the oplog requires us to have an oplog, to
// want unordered callbacks, and to not want a callback on the polls
// that won't happen.
return self._oplogHandle && !ordered &&
!callbacks._testOnlyPollCallback;}, function () {
!callbacks._testOnlyPollCallback;
},
function () {
// We also need to check, if the collection of this Cursor is actually being "watched" by the Oplog handle
// if not, we have to fallback to long polling
if (excludeCollections?.length && excludeCollections.includes(collectionName)) {
@@ -1533,59 +1545,63 @@ Object.assign(MongoConnection.prototype, {
return false;
}
return true;
}, function () {
// We need to be able to compile the selector. Fall back to polling for
// some newfangled $selector that minimongo doesn't support yet.
try {
matcher = new Minimongo.Matcher(cursorDescription.selector);
return true;
} catch (e) {
// XXX make all compilation errors MinimongoError or something
// so that this doesn't ignore unrelated exceptions
return false;
}
}, function () {
// ... and the selector itself needs to support oplog.
return OplogObserveDriver.cursorSupported(cursorDescription, matcher);
}, function () {
// And we need to be able to compile the sort, if any. eg, can't be
// {$natural: 1}.
if (!cursorDescription.options.sort)
return true;
try {
sorter = new Minimongo.Sorter(cursorDescription.options.sort);
return true;
} catch (e) {
// XXX make all compilation errors MinimongoError or something
// so that this doesn't ignore unrelated exceptions
return false;
}
}], function (f) { return f(); }); // invoke each function
var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver;
observeDriver = new driverClass({
cursorDescription: cursorDescription,
mongoHandle: self,
multiplexer: multiplexer,
ordered: ordered,
matcher: matcher, // ignored by polling
sorter: sorter, // ignored by polling
_testOnlyPollCallback: callbacks._testOnlyPollCallback
});
if (observeDriver._init) {
await observeDriver._init();
},
function () {
// We need to be able to compile the selector. Fall back to polling for
// some newfangled $selector that minimongo doesn't support yet.
try {
matcher = new Minimongo.Matcher(cursorDescription.selector);
return true;
} catch (e) {
// XXX make all compilation errors MinimongoError or something
// so that this doesn't ignore unrelated exceptions
return false;
}
},
function () {
// ... and the selector itself needs to support oplog.
return OplogObserveDriver.cursorSupported(cursorDescription, matcher);
},
function () {
// And we need to be able to compile the sort, if any. eg, can't be
// {$natural: 1}.
if (!cursorDescription.options.sort)
return true;
try {
sorter = new Minimongo.Sorter(cursorDescription.options.sort);
return true;
} catch (e) {
// XXX make all compilation errors MinimongoError or something
// so that this doesn't ignore unrelated exceptions
return false;
}
}
].every(f => f()); // invoke each function and check if all return true
// This field is only set for use in tests.
multiplexer._observeDriver = observeDriver;
var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver;
observeDriver = new driverClass({
cursorDescription: cursorDescription,
mongoHandle: self,
multiplexer: multiplexer,
ordered: ordered,
matcher: matcher, // ignored by polling
sorter: sorter, // ignored by polling
_testOnlyPollCallback: callbacks._testOnlyPollCallback
});
if (observeDriver._init) {
await observeDriver._init();
}
self._observeMultiplexers[observeKey] = multiplexer;
// Blocks until the initial adds have been sent.
await multiplexer.addHandleAndSendInitialAdds(observeHandle);
return observeHandle;
},
// This field is only set for use in tests.
multiplexer._observeDriver = observeDriver;
}
self._observeMultiplexers[observeKey] = multiplexer;
// Blocks until the initial adds have been sent.
await multiplexer.addHandleAndSendInitialAdds(observeHandle);
return observeHandle;
},
});
@@ -1605,7 +1621,7 @@ listenAll = async function (cursorDescription, listenCallback) {
return {
stop: function () {
_.each(listeners, function (listener) {
listeners.forEach(function (listener) {
listener.stop();
});
}
@@ -1687,4 +1703,4 @@ MongoConnection.prototype._observeChangesTailable = function (
// operation to interact with capped collections.
MongoInternals.MongoTimestamp = MongoDB.Timestamp;
MongoInternals.Connection = MongoConnection;
MongoInternals.Connection = MongoConnection;

View File

@@ -1,3 +1,7 @@
import isEmpty from 'lodash.isempty';
import isObject from 'lodash.isobject';
import times from 'lodash.times';
// This is a magic collection that fails its writes on the server when
// the selector (or inserted document) contains fail: true.
@@ -83,8 +87,8 @@ var stripId = function (obj) {
var compareResults = function (test, skipIds, actual, expected) {
if (skipIds) {
_.map(actual, stripId);
_.map(expected, stripId);
actual.map(stripId);
expected.map(stripId);
}
// (technically should ignore order in comparison)
test.equal(actual, expected);
@@ -117,7 +121,7 @@ const upsert = async function(coll, useUpdate, query, mod, options, callback) {
if (useUpdate) {
if (callback) {
await callWithCallBack(() =>
coll.updateAsync(query, mod, _.extend({ upsert: true }, options))
coll.updateAsync(query, mod, Object.assign({ upsert: true }, options))
);
return;
}
@@ -125,7 +129,7 @@ const upsert = async function(coll, useUpdate, query, mod, options, callback) {
numberAffected: await coll.updateAsync(
query,
mod,
_.extend({ upsert: true }, options)
Object.assign({ upsert: true }, options)
),
};
}
@@ -205,7 +209,7 @@ var Dog = function (name, color, actions) {
self.name = name;
self.actions = actions || [{name: "wag"}, {name: "swim"}];
};
_.extend(Dog.prototype, {
Object.assign(Dog.prototype, {
getName: function () { return this.name;},
getColor: function () { return this.name;},
equals: function (other) { return other.name === this.name &&
@@ -220,7 +224,7 @@ EJSON.addType("dog", function (o) { return new Dog(o.name, o.color, o.actions);}
// Parameterize tests.
_.each( [ 'MONGO', 'STRING'], function(idGeneration) {
['STRING', 'MONGO'].forEach(function(idGeneration) {
var collectionOptions = { idGeneration: idGeneration};
@@ -404,10 +408,8 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
[2, 8]
);
test.equal(
_.pluck(await coll.find({ run: run }, { sort: { x: -1 } }).fetchAsync(), 'x'),
[4, 1]
);
test.equal(await coll.find({run: run}, {sort: {x: -1}}).mapAsync(doc => doc.x),
[4, 1]);
await expectObserve('', async function() {
var count = await coll.updateAsync(
@@ -426,7 +428,7 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
);
test.equal(count, 2);
test.equal(
_.pluck(await coll.find({ run: run }, { sort: { x: -1 } }).fetchAsync(), 'x'),
(await coll.find({ run: run }, { sort: { x: -1 } }).fetchAsync()).map(doc => doc.x),
[6, 3]
);
});
@@ -441,8 +443,8 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
async function() {
await coll.updateAsync({ run: run, x: 3 }, { $inc: { x: 10 } }, { multi: true });
test.equal(
_.pluck(await coll.find({ run: run }, { sort: { x: -1 } }).fetchAsync(), 'x'),
[13, 6]
(await coll.find({ run: run }, { sort: { x: -1 } }).fetchAsync()).map(doc => doc.x),
[13, 6]
);
}
);
@@ -545,7 +547,7 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
return;
}
var max_counters = _.clone(counters);
var max_counters = Object.assign({}, counters);
await finishObserve(async function() {
if (Meteor.isServer) obs._multiplexer._observeDriver._suspendPolling();
@@ -595,7 +597,7 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
// Did we limit ourselves to one 'moved' message per change,
// rather than O(results) moved messages?
_.each(max_counters, function(v, k) {
Object.entries(max_counters).forEach(([k, v]) => {
test.isTrue(max_counters[k] >= counters[k], k);
});
@@ -1005,12 +1007,15 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
}
);
// compares arrays a and b w/o looking at order
var setsEqual = function(a, b) {
a = _.map(a, EJSON.stringify);
b = _.map(b, EJSON.stringify);
return _.isEmpty(_.difference(a, b)) && _.isEmpty(_.difference(b, a));
};
// compares arrays a and b w/o looking at order
const setsEqual = function (a, b) {
a = a.map(EJSON.stringify);
b = b.map(EJSON.stringify);
const difference = (arr1, arr2) => arr1.filter(x => !arr2.includes(x));
return difference(a, b).length === 0 && difference(b, a).length === 0;
};
// This test mainly checks the correctness of oplog code dealing with limited
// queries. Compitablity with poll-diff is added as well.
@@ -1321,13 +1326,13 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
])
);
test.length(_.keys(o.state), 3);
test.equal(o.state[docId6], { _id: docId6, foo: 22, bar: 24 });
test.equal(o.state[docId11], { _id: docId11, foo: 22, bar: 33.5 });
test.equal(o.state[docId12], { _id: docId12, foo: 22, bar: 43.5 });
clearOutput(o);
testOplogBufferIds([]);
testSafeAppendToBufferFlag(true);
test.length(Object.keys(o.state), 3);
test.equal(o.state[docId6], { _id: docId6, foo: 22, bar: 24 });
test.equal(o.state[docId11], { _id: docId11, foo: 22, bar: 33.5 });
test.equal(o.state[docId12], { _id: docId12, foo: 22, bar: 43.5 });
clearOutput(o);
testOplogBufferIds([]);
testSafeAppendToBufferFlag(true);
var docId13 = await ins({ foo: 22, bar: 50 });
var docId14 = await ins({ foo: 22, bar: 51 });
@@ -1422,10 +1427,10 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
setsEqual(o.output, [{ added: docId4 }, { removed: docId2 }])
);
test.equal(_.size(o.state), 2);
test.equal(o.state[docId4], { _id: docId4, y: -1222 });
test.equal(o.state[docId1], { _id: docId1, y: 1222 });
clearOutput(o);
test.equal(Object.keys(o.state).length, 2);
test.equal(o.state[docId4], {_id: docId4, y: -1222});
test.equal(o.state[docId1], {_id: docId1, y: 1222});
clearOutput(o);
await rem(docId2);
// Becomes [docId4 docId1 | docId3]
@@ -1438,10 +1443,10 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
setsEqual(o.output, [{ added: docId3 }, { removed: docId4 }])
);
test.equal(_.size(o.state), 2);
test.equal(o.state[docId3], { _id: docId3, y: 7222 });
test.equal(o.state[docId1], { _id: docId1, y: 1222 });
clearOutput(o);
test.equal(Object.keys(o.state).length, 2);
test.equal(o.state[docId3], {_id: docId3, y: 7222});
test.equal(o.state[docId1], {_id: docId1, y: 1222});
clearOutput(o);
onComplete();
}
@@ -1821,8 +1826,9 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
'mongo-livedata - transform sets _id if not present, ' + idGeneration,
[
function(test, expect) {
var justId = function(doc) {
return _.omit(doc, '_id');
const justId = function(doc) {
const { _id, ...rest } = doc;
return rest;
};
TRANSFORMS['justId'] = justId;
var collectionOptions = {
@@ -2111,9 +2117,9 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
// This test is duplicated below (with some changes) for async upserts that go
// over the network.
_.each(Meteor.isServer ? [true, false] : [true], function(minimongo) {
_.each([true, false], function(useUpdate) {
_.each([true, false], function(useDirectCollection) {
Meteor.isServer ? [true, false] : [true].forEach(function(minimongo) {
[true, false].forEach(function(useUpdate) {
[true, false].forEach(function(useDirectCollection) {
Tinytest.addAsync(
'mongo-livedata - ' +
(useUpdate ? 'update ' : '') +
@@ -2129,7 +2135,7 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
// directly calling MongoConnection.upsert().
var skipIds = useUpdate || (!minimongo && useDirectCollection);
if (minimongo)
options = _.extend({}, collectionOptions, { connection: null });
options = Object.assign({}, collectionOptions, { connection: null });
var coll = new Mongo.Collection(
'livedata_upsert_collection_' +
run +
@@ -2356,9 +2362,9 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
// the Mongo.Collection and the MongoConnection.
//
// XXX Rewrite with testAsyncMulti, that would simplify things a lot!
_.each(Meteor.isServer ? [false] : [true, false], function(useNetwork) {
_.each(useNetwork ? [false] : [true, false], function(useDirectCollection) {
_.each([true, false], function(useUpdate) {
Meteor.isServer ? [false] : [true, false].forEach(function(useNetwork) {
useNetwork ? [false] : [true, false].forEach(function(useDirectCollection) {
[true, false].forEach(function(useUpdate) {
Tinytest.addAsync(
asyncUpsertTestName(
useNetwork,
@@ -2399,7 +2405,7 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
coll = new Mongo.Collection(collName, collectionOptions);
Meteor.subscribe('c-' + collName, next0);
} else {
var opts = _.clone(collectionOptions);
var opts = Object.assign({}, collectionOptions);
if (Meteor.isClient) opts.connection = null;
coll = new Mongo.Collection(collName, opts);
if (useDirectCollection) coll = coll._collection;
@@ -2649,7 +2655,7 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
// Runs a method and its stub which do some upserts. The method throws an error
// if we don't get the right return values.
if (Meteor.isClient) {
_.each([true, false], function(useUpdate) {
[true, false].forEach(function(useUpdate) {
Tinytest.addAsync(
'mongo-livedata - ' +
(useUpdate ? 'update ' : '') +
@@ -2678,8 +2684,8 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
});
}
_.each(Meteor.isServer ? [true, false] : [true], function(minimongo) {
_.each([true, false], function(useUpdate) {
Meteor.isServer ? [true, false] : [true].forEach(function(minimongo) {
[true, false].forEach(function(useUpdate) {
Tinytest.addAsync(
'mongo-livedata - ' +
(useUpdate ? 'update ' : '') +
@@ -2691,7 +2697,7 @@ _.each( [ 'MONGO', 'STRING'], function(idGeneration) {
var run = test.runId();
var options = collectionOptions;
if (minimongo)
options = _.extend({}, collectionOptions, { connection: null });
options = Object.assign({}, collectionOptions, { connection: null });
var coll = new Mongo.Collection(
'livedata_upsert_by_id_collection_' + run,
options
@@ -2821,7 +2827,7 @@ testAsyncMulti("mongo-livedata - specified _id", [
async function collectionInsert (test, expect, coll, index) {
const id = await coll.insertAsync({name: "foo"});
const o = await coll.findOneAsync(id) || {};
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
@@ -2833,7 +2839,7 @@ async function collectionUpsert(test, expect, coll, index) {
test.equal(result.numberAffected, 1);
const o = await coll.findOneAsync(upsertId);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
@@ -2841,14 +2847,14 @@ async function collectionUpsertExisting(test, expect, coll, index) {
const id = await coll.insertAsync({ name: 'foo' });
const o = await coll.findOneAsync(id);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
const result = await coll.upsertAsync(id, { $set: { name: 'bar' } });
test.equal(result.insertedId, id);
test.equal(result.numberAffected, 1);
const ob = await coll.findOneAsync(id);
test.isTrue(_.isObject(ob));
test.isTrue(isObject(ob));
test.equal(ob.name, 'bar');
}
@@ -2867,7 +2873,7 @@ async function functionCallsInsert(test, expect, coll, index) {
test.equal(ids[0], stubId);
const o = await coll.findOneAsync(stubId);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
@@ -2885,7 +2891,7 @@ async function functionCallsUpsert(test, expect, coll, index) {
test.equal(result.numberAffected, 1);
const o = await coll.findOneAsync(upsertId);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
@@ -2908,7 +2914,7 @@ async function functionCallsUpsertExisting(test, expect, coll, index) {
test.equal(result.insertedId, undefined);
const ob = await coll.findOneAsync(id);
test.isTrue(_.isObject(ob));
test.isTrue(isObject(ob));
test.equal(ob.name, 'bar');
}
@@ -2927,7 +2933,7 @@ async function functionCalls3Inserts(test, expect, coll, index) {
test.equal(ids[i], stubId);
var o = await coll.findOneAsync(stubId);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
}
@@ -2949,7 +2955,7 @@ async function functionChainInsert(test, expect, coll, index) {
await Meteor._sleepForMs(100);
var o = await coll.findOneAsync(stubId);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
@@ -2971,7 +2977,7 @@ async function functionChain2Insert(test, expect, coll, index) {
await Meteor._sleepForMs(100);
const o = await coll.findOneAsync(stubId);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
@@ -2989,41 +2995,32 @@ async function functionChain2Upsert(test, expect, coll, index) {
test.equal(result.numberAffected, 1);
await Meteor._sleepForMs(100);
const o = await coll.findOneAsync(upsertId);
test.isTrue(_.isObject(o));
test.isTrue(isObject(o));
test.equal(o.name, 'foo');
}
_.each(
{
collectionInsert: collectionInsert,
collectionUpsert: collectionUpsert,
functionCallsInsert: functionCallsInsert,
functionCallsUpsert: functionCallsUpsert,
functionCallsUpsertExisting: functionCallsUpsertExisting,
functionCalls3Insert: functionCalls3Inserts,
functionChainInsert: functionChainInsert,
functionChain2Insert: functionChain2Insert,
functionChain2Upsert: functionChain2Upsert,
},
function(fn, name) {
_.each([1, 3], function(repetitions) {
_.each([1, 3], function(collectionCount) {
_.each(['STRING', 'MONGO'], function(idGeneration) {
testAsyncMulti(
'mongo-livedata - consistent _id generation ' +
name +
', ' +
repetitions +
' repetitions on ' +
collectionCount +
' collections, idGeneration=' +
idGeneration,
[
function(test, expect) {
var collectionOptions = { idGeneration: idGeneration };
Object.entries({
collectionInsert: collectionInsert,
collectionUpsert: collectionUpsert,
functionCallsInsert: functionCallsInsert,
functionCallsUpsert: functionCallsUpsert,
functionCallsUpsertExisting: functionCallsUpsertExisting,
functionCalls3Insert: functionCalls3Inserts,
functionChainInsert: functionChainInsert,
functionChain2Insert: functionChain2Insert,
functionChain2Upsert: functionChain2Upsert
}).forEach(function([name, fn]) {
[1, 3].forEach(function(repetitions) {
[1, 3].forEach(function(collectionCount) {
['STRING', 'MONGO'].forEach(function(idGeneration) {
testAsyncMulti('mongo-livedata - consistent _id generation ' + name + ', ' + repetitions + ' repetitions on ' + collectionCount + ' collections, idGeneration=' + idGeneration, [function(test, expect) {
var collectionOptions = {
idGeneration: idGeneration
};
var cleanups = (this.cleanups = []);
this.collections = _.times(collectionCount, function() {
this.collections = times(collectionCount, function() {
var collectionName = 'consistentid_' + Random.id();
if (Meteor.isClient) {
Meteor.call(
@@ -3672,7 +3669,7 @@ var TestCustomType = function (head, tail) {
this.myHead = head;
this.myTail = tail;
};
_.extend(TestCustomType.prototype, {
Object.assign(TestCustomType.prototype, {
clone: function () {
return new TestCustomType(this.myHead, this.myTail);
},
@@ -3807,7 +3804,7 @@ testAsyncMulti('mongo-livedata - oplog - update EJSON', [
async function waitUntilOplogCaughtUp() {
var oplogHandle =
MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle;
MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle;
if (oplogHandle)
await oplogHandle.waitUntilCaughtUp();
}
@@ -3816,7 +3813,7 @@ async function waitUntilOplogCaughtUp() {
Meteor.isServer &&
Tinytest.addAsync('mongo-livedata - cursor dedup stop', async function(test) {
var coll = new Mongo.Collection(Random.id());
_.times(100, async function() {
times(100, async function() {
await coll.insertAsync({ foo: 'baz' });
});
var handler = await coll.find({}).observeChanges({
@@ -3899,7 +3896,7 @@ Meteor.isServer &&
toDelete: true,
});
});
test.equal(_.keys(state), [self.id2]);
test.equal(Object.keys(state), [self.id2]);
// Mutate the one in the unpublished buffer and the one below the
// buffer. Before the fix for #2274, this left the observe state machine in
@@ -3912,14 +3909,14 @@ Meteor.isServer &&
{ multi: 1 }
);
});
test.equal(_.keys(state), [self.id2]);
test.equal(Object.keys(state), [self.id2]);
// Now remove the one published document. This should slide up id1 from the
// buffer, but this didn't work before the #2274 fix.
await runInFence(async function() {
await self.coll.removeAsync({ toDelete: true });
});
test.equal(_.keys(state), [self.id1]);
test.equal(Object.keys(state), [self.id1]);
},
]);
@@ -4511,4 +4508,4 @@ Tinytest.addAsync(
}
}
},
);
);

View File

@@ -6,9 +6,9 @@ var makeCollection = function () {
}
};
_.each ([{added: 'added', forceOrdered: true},
([{added: 'added', forceOrdered: true},
{added: 'added', forceOrdered: false},
{added: 'addedBefore', forceOrdered: false}], function (options) {
{added: 'addedBefore', forceOrdered: false}]).forEach(function (options) {
var added = options.added;
var forceOrdered = options.forceOrdered;
@@ -478,7 +478,7 @@ if (Meteor.isServer) {
self.expects = [];
self.insert = async function(fields) {
return coll.insertAsync(
_.extend({ ts: new MongoInternals.MongoTimestamp(0, 0) }, fields)
Object.assign({ ts: new MongoInternals.MongoTimestamp(0, 0) }, fields)
);
};

View File

@@ -1,3 +1,6 @@
import has from 'lodash.has';
import isEmpty from 'lodash.isempty';
let nextObserveHandleId = 1;
ObserveMultiplexer = class {
@@ -67,7 +70,7 @@ ObserveMultiplexer = class {
Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact(
"mongo-livedata", "observe-handles", -1);
if (_.isEmpty(this._handles) &&
if (isEmpty(this._handles) &&
this._addHandleTasksScheduledButNotPerformed === 0) {
await this._stop();
}
@@ -190,7 +193,7 @@ ObserveMultiplexer = class {
return;
// note: docs may be an _IdMap or an OrderedDict
await this._cache.docs.forEachAsync(async (doc, id) => {
if (!_.has(this._handles, handle._id))
if (!has(this._handles, handle._id))
throw Error("handle got removed before sending initial adds!");
const { _id, ...fields } = handle.nonMutatingCallbacks ? doc
: EJSON.clone(doc);
@@ -229,4 +232,4 @@ ObserveHandle = class {
this._stopped = true;
await this._multiplexer.removeHandle(this._id);
}
};
};

View File

@@ -1,3 +1,5 @@
import has from 'lodash.has';
import isEmpty from 'lodash.isempty';
import { oplogV2V1Converter } from "./oplog_v2_converter";
import { check, Match } from 'meteor/check';
@@ -207,7 +209,7 @@ _.extend(OplogObserveDriver.prototype, {
_addPublished: function (id, doc) {
var self = this;
Meteor._noYieldsAllowed(function () {
var fields = _.clone(doc);
var fields = Object.assign({}, doc);
delete fields._id;
self._published.set(id, self._sharedProjectionFn(doc));
self._multiplexer.added(id, self._projectionFn(fields));
@@ -296,7 +298,7 @@ _.extend(OplogObserveDriver.prototype, {
var projectedOld = self._projectionFn(oldDoc);
var changed = DiffSequence.makeChangedFields(
projectedNew, projectedOld);
if (!_.isEmpty(changed))
if (!isEmpty(changed))
self._multiplexer.changed(id, changed);
});
},
@@ -634,7 +636,7 @@ _.extend(OplogObserveDriver.prototype, {
// selector)?
// oplog format has changed on mongodb 5, we have to support both now
// diff is the format in Mongo 5+ (oplog v2)
var isReplace = !_.has(op.o, '$set') && !_.has(op.o, 'diff') && !_.has(op.o, '$unset');
var isReplace = !has(op.o, '$set') && !has(op.o, 'diff') && !has(op.o, '$unset');
// If this modifier modifies something inside an EJSON custom type (ie,
// anything with EJSON$), then we can't try to use
// LocalCollection._modify, since that just mutates the EJSON encoding,
@@ -646,7 +648,7 @@ _.extend(OplogObserveDriver.prototype, {
var bufferedBefore = self._limit && self._unpublishedBuffer.has(id);
if (isReplace) {
self._handleDoc(id, _.extend({_id: id}, op.o));
self._handleDoc(id, Object.assign({_id: id}, op.o));
} else if ((publishedBefore || bufferedBefore) &&
canDirectlyModifyDoc) {
// Oh great, we actually know what the document is, so we can apply
@@ -864,11 +866,11 @@ _.extend(OplogObserveDriver.prototype, {
// the selector, not just the fields we are going to publish (that's the
// "shared" projection). And we don't want to apply any transform in the
// cursor, because observeChanges shouldn't use the transform.
var options = _.clone(self._cursorDescription.options);
var options = Object.assign({}, self._cursorDescription.options);
// Allow the caller to modify the options. Useful to specify different
// skip and limit values.
_.extend(options, optionsOverwrite);
Object.assign(options, optionsOverwrite);
options.fields = self._sharedProjection;
delete options.transform;
@@ -906,7 +908,7 @@ _.extend(OplogObserveDriver.prototype, {
if (!newResults.has(id))
idsToRemove.push(id);
});
_.each(idsToRemove, function (id) {
idsToRemove.forEach(function (id) {
self._removePublished(id);
});
@@ -1044,11 +1046,11 @@ OplogObserveDriver.cursorSupported = function (cursorDescription, matcher) {
};
var modifierCanBeDirectlyApplied = function (modifier) {
return _.all(modifier, function (fields, operation) {
return _.all(fields, function (value, field) {
return Object.entries(modifier).every(function ([operation, fields]) {
return Object.entries(fields).every(function ([field, value]) {
return !/EJSON\$/.test(field);
});
});
};
MongoInternals.OplogObserveDriver = OplogObserveDriver;
MongoInternals.OplogObserveDriver = OplogObserveDriver;

View File

@@ -1,3 +1,6 @@
import isEmpty from 'lodash.isempty';
import has from 'lodash.has';
import { NpmModuleMongodb } from "meteor/npm-mongo";
const { Long } = NpmModuleMongodb;
@@ -358,7 +361,7 @@ Object.assign(OplogHandle.prototype, {
if (doc.o.dropDatabase) {
delete trigger.collection;
trigger.dropDatabase = true;
} else if (_.has(doc.o, "drop")) {
} else if (has(doc.o, "drop")) {
trigger.collection = doc.o.drop;
trigger.dropCollection = true;
trigger.id = null;
@@ -419,7 +422,7 @@ Object.assign(OplogHandle.prototype, {
_setLastProcessedTS: function (ts) {
var self = this;
self._lastProcessedTS = ts;
while (!_.isEmpty(self._catchingUpResolvers) && self._catchingUpResolvers[0].ts.lessThanOrEqual(self._lastProcessedTS)) {
while (!isEmpty(self._catchingUpResolvers) && self._catchingUpResolvers[0].ts.lessThanOrEqual(self._lastProcessedTS)) {
var sequencer = self._catchingUpResolvers.shift();
sequencer.resolver();
}
@@ -432,4 +435,4 @@ Object.assign(OplogHandle.prototype, {
_resetTooFarBehind: function() {
TOO_FAR_BEHIND = process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000;
}
});
});

View File

@@ -9,81 +9,97 @@
Package.describe({
summary: "Adaptor for using MongoDB and Minimongo over DDP",
version: '2.0.0',
version: "2.0.2",
});
Npm.depends({
"mongodb-uri": "0.9.7"
"mongodb-uri": "0.9.7",
"lodash.times": "4.3.2",
"lodash.isempty": "4.4.0",
"lodash.has": "4.5.2",
"lodash.throttle": "4.1.1",
"lodash.once": "4.1.1",
"lodash.identity": "3.0.0",
"lodash.isobject": "3.0.2",
"lodash.clone": "4.5.0"
});
Npm.strip({
mongodb: ["test/"]
mongodb: ["test/"],
});
Package.onUse(function (api) {
api.use('npm-mongo', 'server');
api.use('allow-deny');
api.use("npm-mongo", "server");
api.use("allow-deny");
api.use([
'random',
'ejson',
'minimongo',
'ddp',
'tracker',
'diff-sequence',
'mongo-id',
'check',
'ecmascript',
'mongo-dev-server',
'logging'
"random",
"ejson",
"minimongo",
"ddp",
"tracker",
"diff-sequence",
"mongo-id",
"check",
"ecmascript",
"mongo-dev-server",
"logging",
]);
// Make weak use of Decimal type on client
api.use('mongo-decimal', 'client', {weak: true});
api.use('mongo-decimal', 'server');
api.use("mongo-decimal", "client", { weak: true });
api.use("mongo-decimal", "server");
//api.use('emitter-promise', 'server');
api.use('underscore', 'server');
api.use("underscore", "server");
// Binary Heap data structure is used to optimize oplog observe driver
// performance.
api.use('binary-heap', 'server');
api.use("binary-heap", "server");
// Allow us to detect 'insecure'.
api.use('insecure', {weak: true});
api.use("insecure", { weak: true });
// Allow us to detect 'autopublish', and publish collections if it's loaded.
api.use('autopublish', 'server', {weak: true});
api.use("autopublish", "server", { weak: true });
// Allow us to detect 'disable-oplog', which turns off oplog tailing for your
// app even if it's configured in the environment. (This package will be
// probably be removed before 1.0.)
api.use('disable-oplog', 'server', {weak: true});
api.use("disable-oplog", "server", { weak: true });
// defaultRemoteCollectionDriver gets its deployConfig from something that is
// (for questionable reasons) initialized by the webapp package.
api.use('webapp', 'server', {weak: true});
api.use("webapp", "server", { weak: true });
// If the facts package is loaded, publish some statistics.
api.use('facts-base', 'server', {weak: true});
api.use("facts-base", "server", { weak: true });
api.use('callback-hook', 'server');
api.use("callback-hook", "server");
// Stuff that should be exposed via a real API, but we haven't yet.
api.export('MongoInternals', 'server');
api.export("MongoInternals", "server");
api.export("Mongo");
api.export('ObserveMultiplexer', 'server', {testOnly: true});
api.export("ObserveMultiplexer", "server", { testOnly: true });
api.addFiles(['mongo_driver.js', 'oplog_tailing.js',
'observe_multiplex.js', 'doc_fetcher.js',
'polling_observe_driver.js','oplog_observe_driver.js', 'oplog_v2_converter.js'],
'server');
api.addFiles('local_collection_driver.js', ['client', 'server']);
api.addFiles('remote_collection_driver.js', 'server');
api.addFiles('collection.js', ['client', 'server']);
api.addFiles('connection_options.js', 'server');
api.addAssets('mongo.d.ts', 'server');
api.addFiles(
[
"mongo_driver.js",
"oplog_tailing.js",
"observe_multiplex.js",
"doc_fetcher.js",
"polling_observe_driver.js",
"oplog_observe_driver.js",
"oplog_v2_converter.js",
],
"server"
);
api.addFiles("local_collection_driver.js", ["client", "server"]);
api.addFiles("remote_collection_driver.js", "server");
api.addFiles("collection.js", ["client", "server"]);
api.addFiles("connection_options.js", "server");
api.addAssets("mongo.d.ts", "server");
});
Package.onTest(function (api) {
@@ -91,18 +107,17 @@ Package.onTest(function (api) {
api.use('check');
api.use('ecmascript');
api.use('npm-mongo', 'server');
//api.use('emitter-promise', 'server');
api.use(['tinytest', 'underscore', 'test-helpers', 'ejson', 'random',
api.use(['tinytest', 'test-helpers', 'ejson', 'random',
'ddp', 'base64']);
// XXX test order dependency: the allow_tests "partial allow" test
// fails if it is run before mongo_livedata_tests.
api.addFiles('mongo_livedata_tests.js', ['client', 'server']);
api.addFiles('upsert_compatibility_test.js', 'server');
api.addFiles('allow_tests.js', ['client', 'server']);
api.addFiles('collection_tests.js', ['client', 'server']);
api.addFiles('collection_async_tests.js', ['client', 'server']);
api.addFiles('observe_changes_tests.js', ['client', 'server']);
api.addFiles('oplog_tests.js', 'server');
api.addFiles('oplog_v2_converter_tests.js', 'server');
api.addFiles('doc_fetcher_tests.js', 'server');
api.addFiles("mongo_livedata_tests.js", ["client", "server"]);
api.addFiles("upsert_compatibility_test.js", "server");
api.addFiles("allow_tests.js", ["client", "server"]);
api.addFiles("collection_tests.js", ["client", "server"]);
api.addFiles("collection_async_tests.js", ["client", "server"]);
api.addFiles("observe_changes_tests.js", ["client", "server"]);
api.addFiles("oplog_tests.js", "server");
api.addFiles("oplog_v2_converter_tests.js", "server");
api.addFiles("doc_fetcher_tests.js", "server");
});

View File

@@ -1,3 +1,5 @@
import throttle from 'lodash.throttle';
var POLLING_THROTTLE_MS = +process.env.METEOR_POLLING_THROTTLE_MS || 50;
var POLLING_INTERVAL_MS = +process.env.METEOR_POLLING_INTERVAL_MS || 10 * 1000;
@@ -31,7 +33,7 @@ PollingObserveDriver = function (options) {
// Make sure to create a separately throttled function for each
// PollingObserveDriver object.
self._ensurePollIsScheduled = _.throttle(
self._ensurePollIsScheduled = throttle(
self._unthrottledEnsurePollIsScheduled,
self._cursorDescription.options.pollingThrottleMs || POLLING_THROTTLE_MS /* ms */);
@@ -77,7 +79,7 @@ _.extend(PollingObserveDriver.prototype, {
self._cursorDescription.options._pollingInterval || // COMPAT with 1.2
POLLING_INTERVAL_MS;
const intervalHandle = Meteor.setInterval(
_.bind(self._ensurePollIsScheduled, self), pollingInterval);
self._ensurePollIsScheduled.bind(self), pollingInterval);
self._stopCallbacks.push(function () {
Meteor.clearInterval(intervalHandle);
});
@@ -87,10 +89,10 @@ _.extend(PollingObserveDriver.prototype, {
await this._unthrottledEnsurePollIsScheduled();
Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact(
"mongo-livedata", "observe-drivers-polling", 1);
},
// This is always called through _.throttle (except once at startup).
_unthrottledEnsurePollIsScheduled: async function () {
"mongo-livedata", "observe-drivers-polling", 1);
},
// This is always called through _.throttle (except once at startup).
_unthrottledEnsurePollIsScheduled: async function () {
var self = this;
if (self._pollsScheduledButNotStarted > 0)
return;
@@ -220,12 +222,12 @@ _.extend(PollingObserveDriver.prototype, {
await c();
};
_.each(self._stopCallbacks, stopCallbacksCaller);
self._stopCallbacks.forEach(stopCallbacksCaller);
// Release any write fences that are waiting on us.
_.each(self._pendingWrites, async function (w) {
self._pendingWrites.forEach(async function (w) {
await w.committed();
});
Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact(
"mongo-livedata", "observe-drivers-polling", -1);
}
});
});

View File

@@ -1,3 +1,4 @@
import once from 'lodash.once';
import {
ASYNC_COLLECTION_METHODS,
getAsyncMethodName,
@@ -32,7 +33,7 @@ Object.assign(MongoInternals.RemoteCollectionDriver.prototype, {
var self = this;
var ret = {};
REMOTE_COLLECTION_METHODS.forEach(function (m) {
ret[m] = _.bind(self.mongo[m], self.mongo, name);
ret[m] = self.mongo[m].bind(self.mongo, name);
if (!ASYNC_COLLECTION_METHODS.includes(m)) return;
const asyncMethodName = getAsyncMethodName(m);
@@ -60,10 +61,11 @@ Object.assign(MongoInternals.RemoteCollectionDriver.prototype, {
},
});
// Create the singleton RemoteCollectionDriver only on demand, so we
// only require Mongo configuration if it's actually used (eg, not if
// you're only trying to receive data from a remote DDP server.)
MongoInternals.defaultRemoteCollectionDriver = _.once(function () {
MongoInternals.defaultRemoteCollectionDriver = once(function () {
var connectionOptions = {};
var mongoUrl = process.env.MONGO_URL;
@@ -76,7 +78,6 @@ MongoInternals.defaultRemoteCollectionDriver = _.once(function () {
throw new Error("MONGO_URL must be set in environment");
const driver = new MongoInternals.RemoteCollectionDriver(mongoUrl, connectionOptions);
// As many deployment tools, including Meteor Up, send requests to the app in
// order to confirm that the deployment finished successfully, it's required
// to know about a database connection problem before the app starts. Doing so
@@ -87,4 +88,4 @@ MongoInternals.defaultRemoteCollectionDriver = _.once(function () {
});
return driver;
});
});

View File

@@ -1,5 +1,5 @@
Package.describe({
version: '1.2.4-rc300.0',
version: '1.2.6',
summary: 'Meteor bundle analysis and visualization.',
documentation: 'README.md',
});
@@ -16,10 +16,10 @@ Npm.depends({
Package.onUse(function(api) {
api.use('isobuild:dynamic-import@1.5.0');
api.use([
'ecmascript@0.16.9-rc300.2',
'dynamic-import@0.7.4-rc300.2',
'fetch@0.1.5-rc300.2',
'webapp@2.0.0-rc300.2',
'ecmascript@0.16.9',
'dynamic-import@0.7.4',
'fetch@0.1.5',
'webapp@2.0.1',
]);
api.mainModule('server.js', 'server');
api.mainModule('client.js', 'client');

Some files were not shown because too many files have changed in this diff Show More