Merge pull request #12926 from meteor/docs/meteor-3-docs

[ Meteor 3 ] - API Docs
This commit is contained in:
Gabriel Grubba
2024-02-29 15:19:50 +01:00
committed by GitHub
120 changed files with 21778 additions and 51 deletions

View File

@@ -419,14 +419,14 @@ export class AccountsCommon {
/**
* @summary Get the current user id, or `null` if no user is logged in. A reactive data source.
* @locus Anywhere but publish functions
* @locus Anywhere
* @importFromPackage meteor
*/
Meteor.userId = () => Accounts.userId();
/**
* @summary Get the current user record, or `null` if no user is logged in. A reactive data source.
* @locus Anywhere but publish functions
* @locus Anywhere
* @importFromPackage meteor
* @param {Object} [options]
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
@@ -435,7 +435,7 @@ Meteor.user = options => Accounts.user(options);
/**
* @summary Get the current user record, or `null` if no user is logged in. A reactive data source.
* @locus Anywhere but publish functions
* @locus Anywhere
* @importFromPackage meteor
* @param {Object} [options]
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.

View File

@@ -108,7 +108,7 @@ Accounts._checkPasswordAsync = checkPasswordAsync;
/**
* @summary Finds the user with the specified username.
* @summary Finds the user asynchronously with the specified username.
* First tries to match username case sensitively; if that fails, it
* tries case insensitively; but if more than one user matches the case
* insensitive search, it returns null.
@@ -116,7 +116,7 @@ Accounts._checkPasswordAsync = checkPasswordAsync;
* @param {String} username The username to look for
* @param {Object} [options]
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
* @returns {Object} A user if found, else null
* @returns {Promise<Object>} A user if found, else null
* @importFromPackage accounts-base
*/
Accounts.findUserByUsername =
@@ -124,7 +124,7 @@ Accounts.findUserByUsername =
await Accounts._findUserByQuery({ username }, options);
/**
* @summary Finds the user with the specified email.
* @summary Finds the user asynchronously with the specified email.
* First tries to match email case sensitively; if that fails, it
* tries case insensitively; but if more than one user matches the case
* insensitive search, it returns null.
@@ -132,7 +132,7 @@ Accounts.findUserByUsername =
* @param {String} email The email address to look for
* @param {Object} [options]
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
* @returns {Object} A user if found, else null
* @returns {Promise<Object>} A user if found, else null
* @importFromPackage accounts-base
*/
Accounts.findUserByEmail =
@@ -219,7 +219,7 @@ Accounts.registerLoginHandler("password", async options => {
///
/**
* @summary Change a user's username. Use this instead of updating the
* @summary Change a user's username asynchronously. Use this instead of updating the
* database directly. The operation will fail if there is an existing user
* with a username only differing in case.
* @locus Server
@@ -378,13 +378,13 @@ Meteor.methods({forgotPassword: async options => {
}});
/**
* @summary Generates a reset token and saves it into the database.
* @summary Asynchronously generates a reset token and saves it into the database.
* @locus Server
* @param {String} userId The id of the user to generate the reset token for.
* @param {String} email Which address of the user to generate the reset token for. This address must be in the user's `emails` list. If `null`, defaults to the first email in the list.
* @param {String} reason `resetPassword` or `enrollAccount`.
* @param {Object} [extraTokenData] Optional additional data to be added into the token record.
* @returns {Object} Object with {email, user, token} values.
* @returns {Promise<Object>} Promise of an object with {email, user, token} values.
* @importFromPackage accounts-base
*/
Accounts.generateResetToken =
@@ -452,12 +452,12 @@ Accounts.generateResetToken =
};
/**
* @summary Generates an e-mail verification token and saves it into the database.
* @summary Generates asynchronously an e-mail verification token and saves it into the database.
* @locus Server
* @param {String} userId The id of the user to generate the e-mail verification token for.
* @param {String} email Which address of the user to generate the e-mail verification token for. This address must be in the user's `emails` list. If `null`, defaults to the first unverified email in the list.
* @param {Object} [extraTokenData] Optional additional data to be added into the token record.
* @returns {Object} Object with {email, user, token} values.
* @returns {Promise<Object>} Promise of an object with {email, user, token} values.
* @importFromPackage accounts-base
*/
Accounts.generateVerificationToken =
@@ -517,13 +517,13 @@ Accounts.generateVerificationToken =
// to set a new password, without the old password.
/**
* @summary Send an email with a link the user can use to reset their password.
* @summary Send an email asynchronously with a link the user can use to reset their password.
* @locus Server
* @param {String} userId The id of the user to send email to.
* @param {String} [email] Optional. Which address of the user's to send the email to. This address must be in the user's `emails` list. Defaults to the first email in the list.
* @param {Object} [extraTokenData] Optional additional data to be added into the token record.
* @param {Object} [extraParams] Optional additional params to be added to the reset url.
* @returns {Object} Object with {email, user, token, url, options} values.
* @returns {Promise<Object>} Promise of an object with {email, user, token, url, options} values.
* @importFromPackage accounts-base
*/
Accounts.sendResetPasswordEmail =
@@ -549,13 +549,13 @@ Accounts.sendResetPasswordEmail =
// want to use enrollment emails.
/**
* @summary Send an email with a link the user can use to set their initial password.
* @summary Send an email asynchronously with a link the user can use to set their initial password.
* @locus Server
* @param {String} userId The id of the user to send email to.
* @param {String} [email] Optional. Which address of the user's to send the email to. This address must be in the user's `emails` list. Defaults to the first email in the list.
* @param {Object} [extraTokenData] Optional additional data to be added into the token record.
* @param {Object} [extraParams] Optional additional params to be added to the enrollment url.
* @returns {Object} Object with {email, user, token, url, options} values.
* @returns {Promise<Object>} Promise of an object {email, user, token, url, options} values.
* @importFromPackage accounts-base
*/
Accounts.sendEnrollmentEmail =
@@ -728,14 +728,13 @@ Meteor.methods(
// address as verified
/**
* @summary Send an email with a link the user can use verify their email address.
* @summary Send an email asynchronously with a link the user can use verify their email address.
* @locus Server
* @param {String} userId The id of the user to send email to.
* @param {String} [email] Optional. Which address of the user's to send the email to. This address must be in the user's `emails` list. Defaults to the first unverified email in the list.
* @param {Object} [extraTokenData] Optional additional data to be added into the token record.
* @param {Object} [extraParams] Optional additional params to be added to the verification url.
*
* @returns {Object} Object with {email, user, token, url, options} values.
* @returns {Promise<Object>} Promise of an object with {email, user, token, url, options} values.
* @importFromPackage accounts-base
*/
Accounts.sendVerificationEmail =
@@ -831,7 +830,7 @@ Meteor.methods(
});
/**
* @summary Add an email address for a user. Use this instead of directly
* @summary Add an email asynchronously address for a user. Use this instead of directly
* updating the database. The operation will fail if there is a different user
* with an email only differing in case. If the specified user has an existing
* email only differing in case however, we replace it.
@@ -931,7 +930,7 @@ Accounts.addEmail =
}
/**
* @summary Remove an email address for a user. Use this instead of updating
* @summary Remove an email address asynchronously for a user. Use this instead of updating
* the database directly.
* @locus Server
* @param {String} userId The ID of the user to update.
@@ -1011,7 +1010,7 @@ Meteor.methods(
});
/**
* @summary Creates an user and sends an email if `options.email` is informed.
* @summary Creates an user asynchronously and sends an email if `options.email` is informed.
* Then if the `sendVerificationEmail` option from the `Accounts` package is
* enabled, you'll send a verification email if `options.password` is informed,
* otherwise you'll send an enrollment email.

View File

@@ -384,11 +384,11 @@ EJSON.fromJSONValue = item => {
* @locus Anywhere
* @param {EJSON} val A value to stringify.
* @param {Object} [options]
* @param {Boolean | Integer | String} options.indent Indents objects and
* @param {Boolean | Integer | String} [options.indent] Indents objects and
* arrays for easy readability. When `true`, indents by 2 spaces; when an
* integer, indents by that number of spaces; and when a string, uses the
* string as the indentation pattern.
* @param {Boolean} options.canonical When `true`, stringifies keys in an
* @param {Boolean} [options.canonical] When `true`, stringifies keys in an
* object in sorted order.
*/
EJSON.stringify = handleError((item, options) => {

View File

@@ -203,7 +203,7 @@ Email.customTransport = undefined;
* @locus Server
* @return {Promise}
* @param {Object} options
* @param {String} [options.from] "From:" address (required)
* @param {String} options.from "From:" address (required)
* @param {String|String[]} options.to,cc,bcc,replyTo
* "To:", "Cc:", "Bcc:", and "Reply-To:" addresses
* @param {String} [options.inReplyTo] Message-ID this message is replying to
@@ -273,7 +273,7 @@ Email.sendAsync = async function (options) {
* @locus Server
* @return {Promise}
* @param {Object} options
* @param {String} [options.from] "From:" address (required)
* @param {String} options.from "From:" address (required)
* @param {String|String[]} options.to,cc,bcc,replyTo
* "To:", "Cc:", "Bcc:", and "Reply-To:" addresses
* @param {String} [options.inReplyTo] Message-ID this message is replying to

View File

@@ -44,7 +44,7 @@ class EnvironmentVariableAsync {
* @method withValue
* @param {any} value The value to set for the duration of the function call
* @param {Function} func The function to call with the new value of the
* @param {Object} [options] Optional additional options
* @param {Object} [options] Optional additional properties for adding in [asl](https://nodejs.org/api/async_context.html#class-asynclocalstorage)
* @returns {Promise<any>} The return value of the function
*/
withValue(value, func, options = {}) {

View File

@@ -42,23 +42,7 @@ Meteor.makeErrorType = function (name, constructor) {
* @param {String} error A string code uniquely identifying this kind of error.
* This string should be used by callers of the method to determine the
* appropriate action to take, instead of attempting to parse the reason
* or details fields. For example:
*
* ```
* // on the server, pick a code unique to this error
* // the reason field should be a useful debug message
* throw new Meteor.Error("logged-out",
* "The user must be logged in to post a comment.");
*
* // on the client
* Meteor.call("methodName", function (error) {
* // identify the error
* if (error && error.error === "logged-out") {
* // show a nice error message
* Session.set("errorMessage", "Please log in to post a comment.");
* }
* });
* ```
* or details fields.
*
* For legacy reasons, some built-in Meteor functions such as `check` throw
* errors with a number in this field.

View File

@@ -20,7 +20,7 @@ Mongo = {};
* @class
* @param {String} name The name of the collection. If null, creates an unmanaged (unsynchronized) local collection.
* @param {Object} [options]
* @param {Object} options.connection The server connection that will manage this collection. Uses the default connection if not specified. Pass the return value of calling [`DDP.connect`](#ddp_connect) to specify a different server. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection.
* @param {Object} options.connection The server connection that will manage this collection. Uses the default connection if not specified. Pass the return value of calling [`DDP.connect`](#DDP-connect) to specify a different server. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection.
* @param {String} options.idGeneration The method of generating the `_id` fields of new documents in this collection. Possible values:
- **`'STRING'`**: random strings

View File

@@ -92,12 +92,10 @@
"version": "3.523.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.523.0.tgz",
"integrity": "sha512-ggAkL8szaJkqD8oOsS68URJ9XMDbLA/INO/NPZJqv9BhmftecJvfy43uUVWGNs6n4YXNzfF0Y+zQ3DT0fZkv9g=="
},
"@aws-sdk/core": {
"version": "3.523.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.523.0.tgz",
"integrity": "sha512-JHa3ngEWkTzZ2YTn6EavcADC8gv6zZU4U9WBAleClh6ioXH0kGMBawZje3y0F0mKyLTfLhFqFUlCV5sngI/Qcw=="
},
"@aws-sdk/credential-provider-cognito-identity": {
"version": "3.523.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.523.0.tgz",
@@ -167,7 +165,6 @@
"version": "3.523.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.523.0.tgz",
"integrity": "sha512-IypIAecBc8b4jM0uVBEj90NYaIsc0vuLdSFyH4LPO7is4rQUet4CkkD+S036NvDdcdxBsQ4hJZBmWrqiizMHhQ=="
},
"@aws-sdk/token-providers": {
"version": "3.523.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.523.0.tgz",
@@ -222,7 +219,6 @@
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.5.tgz",
"integrity": "sha512-Rrc+e2Jj6Gu7Xbn0jvrzZlSiP2CZocIOfZ9aNUA82+1sa6GBnxqL9+iZ9EKHeD9aqD1nU8EK4+oN2EiFpSv7Yw=="
},
"@smithy/credential-provider-imds": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.2.4.tgz",
@@ -242,7 +238,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.1.3.tgz",
"integrity": "sha512-FsAPCUj7VNJIdHbSxMd5uiZiF20G2zdSDgrgrDrHqIs/VMxK85Vqk5kMVNNDMCZmMezp6UKnac0B4nAyx7HJ9g=="
},
"@smithy/invalid-dependency": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.1.3.tgz",
@@ -317,7 +312,6 @@
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.3.4.tgz",
"integrity": "sha512-CiZmPg9GeDKbKmJGEFvJBsJcFnh0AQRzOtQAzj1XEa8N/0/uSN/v1LYzgO7ry8hhO8+9KB7+DhSW0weqBra4Aw=="
},
"@smithy/signature-v4": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.1.3.tgz",

6
v3-docs/docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/node_modules
/.vitepress/cache
/.vitepress/dist
/data/data.js
/data/names.json

View File

@@ -0,0 +1,292 @@
import { defineConfig } from "vitepress";
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Meteor API Docs",
description: "Meteor.js API docs",
head: [["link", { rel: "icon", href: "/logo.png" }]],
lastUpdated: true,
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: "Galaxy", link: "https://www.meteor.com/cloud" },
{
text: "Tutorials",
link: "https://www.meteor.com/developers/tutorials",
},
{ text: "Guide", link: "https://guide.meteor.com/" },
{ text: "API Docs", link: "/about/what-is" },
{ text: "Forums", link: "https://forums.meteor.com/" },
],
sidebar: [
{
text: "About",
items: [
{
text: "What is Meteor?",
link: "/about/what-is#what-is-meteor",
},
{
text: "Meteor resources",
link: "/about/what-is#learning-more",
},
{
text: "Roadmap",
link: "/about/roadmap",
},
],
collapsed: true,
},
{
text: "Quick Start",
items: [
{
text: "Install Meteor",
link: "/about/install",
},
// TODO: Your first app meteor app
],
collapsed: true,
},
{
text: "API",
link: "/api/index",
items: [
{
text: "Accounts",
link: "/api/accounts",
items: [
{ text: "Accounts-Base", link: "/api/accounts#accounts-base" },
{ text: "Multi-server", link: "/api/accounts#multi-server" },
{ text: "Passwords", link: "/api/accounts#passwords" },
],
collapsed: true,
},
{
text: "Meteor",
link: "/api/meteor",
items: [
{ text: "Core", link: "/api/meteor#core" },
{ text: "Methods", link: "/api/meteor#methods" },
{ text: "Publish and Subscribe", link: "/api/meteor#pubsub" },
{ text: "Server connections", link: "/api/meteor#connections" },
{ text: "Timers", link: "/api/meteor#timers" },
],
},
{
text: "Collections",
link: "/api/collections",
},
{
text: "DDPRateLimiter",
link: "/api/DDPRateLimiter",
},
{
text: "Check",
link: "/api/check",
},
{
text: "Session",
link: "/api/session",
},
{
text: "Blaze",
link: "/api/blaze",
},
{
text: "Templates",
link: "/api/templates",
},
{
text: "Email",
link: "/api/email",
},
{
text: "Tracker",
link: "/api/Tracker",
},
{
text: "Reactive Var",
link: "/api/ReactiveVar",
},
{
text: "Reactive Dict",
link: "/api/ReactiveDict",
},
{
text: "EJSON",
link: "/api/EJSON",
},
{
text: "Assets",
link: "/api/assets",
},
{
text: "Mobile Configuration",
link: "/api/app",
},
{
text: "Package.js",
link: "/api/package",
},
],
collapsed: false,
},
{
text: "Packages",
items: [
{
text: "accounts-ui",
link: "/packages/accounts-ui",
},
{
text: "accounts-passwordless",
link: "/packages/accounts-passwordless",
},
{
text: "accounts-2fa",
link: "/packages/accounts-2fa",
},
{
text: "appcache",
link: "/packages/appcache",
},
{
text: "audit-arguments-checks",
link: "/packages/audit-argument-checks",
},
{
text: "autoupdate",
link: "/packages/autoupdate",
},
{
text: "browser-policy",
link: "/packages/browser-policy",
},
{
text: "bundler-visualizer",
link: "/packages/bundle-visualizer",
},
{
text: "coffeescript",
link: "/packages/coffeescript",
},
{
text: "ecmascript",
link: "/packages/ecmascript",
},
{
text: "fetch",
link: "/packages/fetch",
},
{
text: "hot-module-replacement",
link: "/packages/hot-module-replacement",
},
{
text: "less",
link: "/packages/less",
},
{
text: "logging",
link: "/packages/logging",
},
{
text: "markdown",
link: "/packages/markdown",
},
{
text: "modules",
link: "/packages/modules",
},
{
text: "oauth-encryption",
link: "/packages/oauth-encryption",
},
{
text: "random",
link: "/packages/random",
},
{
text: "server-render",
link: "/packages/server-render",
},
{
text: "standard-minifier-css",
link: "/packages/standard-minifier-css",
},
{
text: "underscore",
link: "/packages/underscore",
},
{
text: "url",
link: "/packages/url",
},
{
text: "webapp",
link: "/packages/webapp",
},
{
link: "packages/packages-listing",
text: "Maintained Packages",
},
],
collapsed: true,
},
{
text: "Troubleshooting",
items: [
{
text: "Expired Certificates",
link: "/troubleshooting/expired-certificate",
},
{ text: "Windows", link: "/troubleshooting/windows" },
{
text: "Known issues in 2.13",
link: "/troubleshooting/known-issues",
},
],
collapsed: true,
},
{
text: "Command Line",
items: [
{ link: "cli/index", text: "CLI" },
{ link: "cli/using-core-types", text: "Using Core Types" },
{ link: "cli/environment-variables", text: "Environment Variables" },
],
collapsed: true,
},
{
text: "Changelog",
items: [
// TODO: Open issue in Vitepress about this
{ link: "/history", text: "Current" },
{
link: "https://docs.meteor.com/changelog#v2020210120",
text: "Pre-2.0",
},
],
collapsed: true,
},
],
socialLinks: [{ icon: "github", link: "https://github.com/meteor/meteor" }],
logo: { dark: "/meteor-logo.png", light: "/meteor-blue.png" },
search: {
provider: "local",
},
footer: {
message:
'Released under the <a href="https://github.com/meteor/meteor?tab=License-1-ov-file#readme">MIT License</a>.',
copyright:
'Copyright (c) 2011 - present <a href="https://www.meteor.com/">Meteor Software</a>.',
},
editLink: {
pattern: "https://github.com/meteor/meteor/edit/main/v3-docs/docs/:path",
text: "Edit this page on GitHub",
},
},
});

View File

@@ -0,0 +1,80 @@
<script setup lang="ts">
import { useData, useRouter } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { nextTick, provide } from 'vue'
import { redirect } from './redirects/script';
const { isDark } = useData()
const router = useRouter()
const isClient = typeof window !== 'undefined';
if (isClient) {
const { path, shouldRedirect } = redirect(window.location.href)
if (shouldRedirect) router.go(path)
}
const enableTransitions = () =>
isClient &&
'startViewTransition' in document &&
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
if (!enableTransitions()) {
isDark.value = !isDark.value
return
}
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
)}px at ${x}px ${y}px)`
]
await document.startViewTransition(async () => {
isDark.value = !isDark.value
await nextTick()
}).ready
document.documentElement.animate(
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
{
duration: 300,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
}
)
})
</script>
<template>
<DefaultTheme.Layout />
</template>
<style>
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-old(root),
.dark::view-transition-new(root) {
z-index: 1;
}
::view-transition-new(root),
.dark::view-transition-old(root) {
z-index: 9999;
}
.VPSwitchAppearance {
width: 22px !important;
}
.VPSwitchAppearance .check {
transform: none !important;
}
</style>

View File

@@ -0,0 +1,17 @@
// .vitepress/theme/index.ts
import type { Theme } from "vitepress";
import DefaultTheme from "vitepress/theme";
import ApiBox from "../../components/ApiBox.vue";
import ApiMap from "../../components/ApiMap.vue";
import Layout from "./Layout.vue";
import "./theme.css";
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
// register your custom global components
app.component("ApiBox", ApiBox);
app.component("ApiMap", ApiMap);
},
Layout,
} satisfies Theme;

View File

@@ -0,0 +1,4 @@
{
"meteor_call": "meteor-call",
"ddp_connect": "DDP-connect"
}

View File

@@ -0,0 +1,18 @@
// import json from './redirects.json';
/**
*
* @param {string} path
*/
export const redirect = (path) => {
let shouldRedirect = false;
console.log(path)
if (path.includes("_")) {
shouldRedirect = true;
path = path.replace("_", "-");
}
return {
path,
shouldRedirect
};
};

View File

@@ -0,0 +1,557 @@
/**
* Colors: Solid
* -------------------------------------------------------------------------- */
:root {
--vp-c-white: #ffffff;
--vp-c-black: #000000;
--vp-c-neutral: var(--vp-c-black);
--vp-c-neutral-inverse: var(--vp-c-white);
}
.dark {
--vp-c-neutral: var(--vp-c-white);
--vp-c-neutral-inverse: var(--vp-c-black);
}
/**
* Colors: Palette
*
* The primitive colors used for accent colors. These colors are referenced
* by functional colors such as "Text", "Background", or "Brand".
*
* Each colors have exact same color scale system with 3 levels of solid
* colors with different brightness, and 1 soft color.
*
* - `XXX-1`: The most solid color used mainly for colored text. It must
* satisfy the contrast ratio against when used on top of `XXX-soft`.
*
* - `XXX-2`: The color used mainly for hover state of the button.
*
* - `XXX-3`: The color for solid background, such as bg color of the button.
* It must satisfy the contrast ratio with pure white (#ffffff) text on
* top of it.
*
* - `XXX-soft`: The color used for subtle background such as custom container
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
* on top of it.
*
* The soft color must be semi transparent alpha channel. This is crucial
* because it allows adding multiple "soft" colors on top of each other
* to create a accent, such as when having inline code block inside
* custom containers.
* -------------------------------------------------------------------------- */
:root {
--vp-c-gray-1: #dddde3;
--vp-c-gray-2: #e4e4e9;
--vp-c-gray-3: #ebebef;
--vp-c-gray-soft: rgba(142, 150, 170, 0.14);
--vp-c-indigo-1: #3451b2;
--vp-c-indigo-2: #3a5ccc;
--vp-c-indigo-3: #5672cd;
--vp-c-indigo-soft: rgba(100, 108, 255, 0.14);
--vp-c-blue-1: #383695;
--vp-c-blue-2: #3e3e9e;
--vp-c-blue-3: #4c4ca7;
--vp-c-blue-soft: rgba(100, 108, 255, 0.14);
--vp-c-green-1: #18794e;
--vp-c-green-2: #299764;
--vp-c-green-3: #30a46c;
--vp-c-green-soft: rgba(16, 185, 129, 0.14);
--vp-c-orange-1: #FF6A3E;
--vp-c-orange-2: #FF7A4D;
--vp-c-orange-3: #FF8A5C;
--vp-c-orange-soft: rgba(255, 106, 62, 0.14);
--vp-c-meteor-red-1: #BF212E;
--vp-c-meteor-red-2: #A01A24;
--vp-c-meteor-red-3: #7F141D;
--vp-c-meteor-red-soft: rgba(191, 33, 46, 0.16);
--vp-c-yellow-1: #915930;
--vp-c-yellow-2: #946300;
--vp-c-yellow-3: #9f6a00;
--vp-c-yellow-soft: rgba(234, 179, 8, 0.14);
--vp-c-red-1: #b8272c;
--vp-c-red-2: #d5393e;
--vp-c-red-3: #e0575b;
--vp-c-red-soft: rgba(244, 63, 94, 0.14);
--vp-c-sponsor: #db2777;
}
.dark {
--vp-c-gray-1: #515c67;
--vp-c-gray-2: #414853;
--vp-c-gray-3: #32363f;
--vp-c-gray-soft: rgba(101, 117, 133, 0.16);
--vp-c-indigo-1: #a8b1ff;
--vp-c-indigo-2: #5c73e7;
--vp-c-indigo-3: #3e63dd;
--vp-c-indigo-soft: rgba(100, 108, 255, 0.16);
--vp-c-blue-1: #4b6cb7;
--vp-c-blue-2: #3e4f8c;
--vp-c-blue-3: #36436b;
--vp-c-blue-soft: rgba(100, 108, 255, 0.16);
--vp-c-green-1: #3dd68c;
--vp-c-green-2: #30a46c;
--vp-c-green-3: #298459;
--vp-c-green-soft: rgba(16, 185, 129, 0.16);
--vc-c-orange-1: #ff7e17;
--vc-c-orange-2: #e66c00;
--vc-c-orange-3: #b35e00;
--vc-c-orange-soft: rgba(255, 126, 23, 0.16);
--vc-c-meteor-red-1: #BF212E;
--vc-c-meteor-red-2: #A01A24;
--vc-c-meteor-red-3: #7F141D;
--vc-c-meteor-red-soft: rgba(191, 33, 46, 0.16);
--vp-c-yellow-1: #f9b44e;
--vp-c-yellow-2: #da8b17;
--vp-c-yellow-3: #a46a0a;
--vp-c-yellow-soft: rgba(234, 179, 8, 0.16);
--vp-c-red-1: #f66f81;
--vp-c-red-2: #f14158;
--vp-c-red-3: #b62a3c;
--vp-c-red-soft: rgba(244, 63, 94, 0.16);
}
/**
* Colors: Background
*
* - `bg`: The bg color used for main screen.
*
* - `bg-alt`: The alternative bg color used in places such as "sidebar",
* or "code block".
*
* - `bg-elv`: The elevated bg color. This is used at parts where it "floats",
* such as "dialog".
*
* - `bg-soft`: The bg color to slightly distinguish some components from
* the page. Used for things like "carbon ads" or "table".
* -------------------------------------------------------------------------- */
:root {
--vp-c-bg: #ffffff;
--vp-c-bg-alt: #f6f6f7;
--vp-c-bg-elv: #ffffff;
--vp-c-bg-soft: #f6f6f7;
}
.dark {
--vp-c-bg: #101926;
--vp-c-bg-alt: #0a0f1f;
--vp-c-bg-elv: #101926;
--vp-c-bg-soft: #0a0f1f;
}
/**
* Colors: Borders
*
* - `divider`: This is used for separators. This is used to divide sections
* within the same components, such as having separator on "h2" heading.
*
* - `border`: This is designed for borders on interactive components.
* For example this should be used for a button outline.
*
* - `gutter`: This is used to divide components in the page. For example
* the header and the lest of the page.
* -------------------------------------------------------------------------- */
:root {
--vp-c-border: #c2c2c4;
--vp-c-divider: #e2e2e3;
--vp-c-gutter: #e2e2e3;
}
.dark {
--vp-c-border: #3c3f44;
--vp-c-divider: #2e2e32;
--vp-c-gutter: #000000;
}
/**
* Colors: Text
*
* - `text-1`: Used for primary text.
*
* - `text-2`: Used for muted texts, such as "inactive menu" or "info texts".
*
* - `text-3`: Used for subtle texts, such as "placeholders" or "caret icon".
* -------------------------------------------------------------------------- */
:root {
--vp-c-text-1: rgba(60, 60, 67);
--vp-c-text-2: rgba(60, 60, 67, 0.78);
--vp-c-text-3: rgba(60, 60, 67, 0.56);
}
.dark {
--vp-c-text-1: rgba(255, 255, 245, 0.86);
--vp-c-text-2: rgba(235, 235, 245, 0.6);
--vp-c-text-3: rgba(235, 235, 245, 0.38);
}
/**
* Colors: Function
*
* - `default`: The color used purely for subtle indication without any
* special meanings attached to it such as bg color for menu hover state.
*
* - `brand`: Used for primary brand colors, such as link text, button with
* brand theme, etc.
*
* - `tip`: Used to indicate useful information. The default theme uses the
* brand color for this by default.
*
* - `warning`: Used to indicate warning to the users. Used in custom
* container, badges, etc.
*
* - `danger`: Used to show error, or dangerous message to the users. Used
* in custom container, badges, etc.
*
* To understand the scaling system, refer to "Colors: Palette" section.
* -------------------------------------------------------------------------- */
:root {
--vp-c-default-1: var(--vp-c-gray-1);
--vp-c-default-2: var(--vp-c-gray-2);
--vp-c-default-3: var(--vp-c-gray-3);
--vp-c-default-soft: var(--vp-c-gray-soft);
--vp-c-brand-1: var(--vp-c-meteor-red-1);
--vp-c-brand-2: var(--vp-c-meteor-red-2);
--vp-c-brand-3: var(--vp-c-meteor-red-3);
--vp-c-brand-soft: var(--vp-c-blue-soft);
/* DEPRECATED: Use `--vp-c-brand-1` instead. */
--vp-c-brand: var(--vp-c-brand-1);
--vp-c-tip-1: var(--vp-c-brand-1);
--vp-c-tip-2: var(--vp-c-brand-2);
--vp-c-tip-3: var(--vp-c-brand-3);
--vp-c-tip-soft: var(--vp-c-brand-soft);
--vp-c-warning-1: var(--vp-c-yellow-1);
--vp-c-warning-2: var(--vp-c-yellow-2);
--vp-c-warning-3: var(--vp-c-yellow-3);
--vp-c-warning-soft: var(--vp-c-yellow-soft);
--vp-c-danger-1: var(--vp-c-red-1);
--vp-c-danger-2: var(--vp-c-red-2);
--vp-c-danger-3: var(--vp-c-red-3);
--vp-c-danger-soft: var(--vp-c-red-soft);
}
/**
* Typography
* -------------------------------------------------------------------------- */
:root {
--vp-font-family-base: 'Chinese Quotes', 'Inter var', 'Inter', ui-sans-serif,
system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Helvetica, Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--vp-font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco,
Consolas, 'Liberation Mono', 'Courier New', monospace;
}
/**
* Shadows
* -------------------------------------------------------------------------- */
:root {
--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.07), 0 1px 4px rgba(0, 0, 0, 0.07);
--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08);
--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, 0.12), 0 3px 9px rgba(0, 0, 0, 0.12);
--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, 0.16), 0 4px 12px rgba(0, 0, 0, 0.16);
}
/**
* Z-indexes
* -------------------------------------------------------------------------- */
:root {
--vp-z-index-footer: 10;
--vp-z-index-local-nav: 20;
--vp-z-index-nav: 30;
--vp-z-index-layout-top: 40;
--vp-z-index-backdrop: 50;
--vp-z-index-sidebar: 60;
}
/**
* Icons
* -------------------------------------------------------------------------- */
:root {
--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
}
/**
* Layouts
* -------------------------------------------------------------------------- */
:root {
--vp-layout-max-width: 1440px;
}
/**
* Component: Header Anchor
* -------------------------------------------------------------------------- */
:root {
--vp-header-anchor-symbol: '#';
}
/**
* Component: Code
* -------------------------------------------------------------------------- */
:root {
--vp-code-line-height: 1.7;
--vp-code-font-size: 0.875em;
--vp-code-color: var(--vp-c-brand-1);
--vp-code-link-color: var(--vp-c-brand-1);
--vp-code-link-hover-color: var(--vp-c-brand-2);
--vp-code-bg: var(--vp-c-default-soft);
--vp-code-block-color: var(--vp-c-text-2);
--vp-code-block-bg: var(--vp-c-bg-alt);
--vp-code-block-divider-color: var(--vp-c-gutter);
--vp-code-lang-color: var(--vp-c-text-3);
--vp-code-line-highlight-color: var(--vp-c-default-soft);
--vp-code-line-number-color: var(--vp-c-text-3);
--vp-code-line-diff-add-color: var(--vp-c-green-soft);
--vp-code-line-diff-add-symbol-color: var(--vp-c-green-1);
--vp-code-line-diff-remove-color: var(--vp-c-red-soft);
--vp-code-line-diff-remove-symbol-color: var(--vp-c-red-1);
--vp-code-line-warning-color: var(--vp-c-yellow-soft);
--vp-code-line-error-color: var(--vp-c-red-soft);
--vp-code-copy-code-border-color: var(--vp-c-divider);
--vp-code-copy-code-bg: var(--vp-c-bg-soft);
--vp-code-copy-code-hover-border-color: var(--vp-c-divider);
--vp-code-copy-code-hover-bg: var(--vp-c-bg);
--vp-code-copy-code-active-text: var(--vp-c-text-2);
--vp-code-copy-copied-text-content: 'Copied';
--vp-code-tab-divider: var(--vp-code-block-divider-color);
--vp-code-tab-text-color: var(--vp-c-text-2);
--vp-code-tab-bg: var(--vp-code-block-bg);
--vp-code-tab-hover-text-color: var(--vp-c-text-1);
--vp-code-tab-active-text-color: var(--vp-c-text-1);
--vp-code-tab-active-bar-color: var(--vp-c-brand-1);
}
/**
* Component: Button
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: transparent;
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-blue-3);
--vp-button-brand-hover-border: transparent;
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-blue-2);
--vp-button-brand-active-border: transparent;
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-c-blue-1);
--vp-button-alt-border: transparent;
--vp-button-alt-text: var(--vp-c-text-1);
--vp-button-alt-bg: var(--vp-c-default-3);
--vp-button-alt-hover-border: transparent;
--vp-button-alt-hover-text: var(--vp-c-text-1);
--vp-button-alt-hover-bg: var(--vp-c-default-2);
--vp-button-alt-active-border: transparent;
--vp-button-alt-active-text: var(--vp-c-text-1);
--vp-button-alt-active-bg: var(--vp-c-default-1);
--vp-button-sponsor-border: var(--vp-c-text-2);
--vp-button-sponsor-text: var(--vp-c-text-2);
--vp-button-sponsor-bg: transparent;
--vp-button-sponsor-hover-border: var(--vp-c-sponsor);
--vp-button-sponsor-hover-text: var(--vp-c-sponsor);
--vp-button-sponsor-hover-bg: transparent;
--vp-button-sponsor-active-border: var(--vp-c-sponsor);
--vp-button-sponsor-active-text: var(--vp-c-sponsor);
--vp-button-sponsor-active-bg: transparent;
}
/**
* Component: Custom Block
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-font-size: 14px;
--vp-custom-block-code-font-size: 13px;
--vp-custom-block-info-border: transparent;
--vp-custom-block-info-text: var(--vp-c-text-1);
--vp-custom-block-info-bg: var(--vp-c-default-soft);
--vp-custom-block-info-code-bg: var(--vp-c-default-soft);
--vp-custom-block-tip-border: transparent;
--vp-custom-block-tip-text: var(--vp-c-text-1);
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
--vp-custom-block-warning-border: transparent;
--vp-custom-block-warning-text: var(--vp-c-text-1);
--vp-custom-block-warning-bg: var(--vp-c-warning-soft);
--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);
--vp-custom-block-danger-border: transparent;
--vp-custom-block-danger-text: var(--vp-c-text-1);
--vp-custom-block-danger-bg: var(--vp-c-danger-soft);
--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);
--vp-custom-block-details-border: var(--vp-custom-block-info-border);
--vp-custom-block-details-text: var(--vp-custom-block-info-text);
--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);
--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg);
}
/**
* Component: Input
* -------------------------------------------------------------------------- */
:root {
--vp-input-border-color: var(--vp-c-border);
--vp-input-bg-color: var(--vp-c-bg-alt);
--vp-input-switch-bg-color: var(--vp-c-gray-soft);
}
/**
* Component: Nav
* -------------------------------------------------------------------------- */
:root {
--vp-nav-height: 64px;
--vp-nav-bg-color: var(--vp-c-bg);
--vp-nav-screen-bg-color: var(--vp-c-bg);
--vp-nav-logo-height: 24px;
}
.hide-nav {
--vp-nav-height: 0px;
}
.hide-nav .VPSidebar {
--vp-nav-height: 22px;
}
/**
* Component: Local Nav
* -------------------------------------------------------------------------- */
:root {
--vp-local-nav-bg-color: var(--vp-c-bg);
}
/**
* Component: Sidebar
* -------------------------------------------------------------------------- */
:root {
--vp-sidebar-width: 272px;
--vp-sidebar-bg-color: var(--vp-c-bg-alt);
}
/**
* Colors Backdrop
* -------------------------------------------------------------------------- */
:root {
--vp-backdrop-bg-color: rgba(0, 0, 0, 0.6);
}
/**
* Component: Home
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: var(--vp-c-brand-1);
--vp-home-hero-name-background: transparent;
--vp-home-hero-image-background-image: none;
--vp-home-hero-image-filter: none;
}
/**
* Component: Badge
* -------------------------------------------------------------------------- */
:root {
--vp-badge-info-border: transparent;
--vp-badge-info-text: var(--vp-c-text-2);
--vp-badge-info-bg: var(--vp-c-default-soft);
--vp-badge-tip-border: transparent;
--vp-badge-tip-text: var(--vp-c-brand-1);
--vp-badge-tip-bg: var(--vp-c-brand-soft);
--vp-badge-warning-border: transparent;
--vp-badge-warning-text: var(--vp-c-warning-1);
--vp-badge-warning-bg: var(--vp-c-warning-soft);
--vp-badge-danger-border: transparent;
--vp-badge-danger-text: var(--vp-c-danger-1);
--vp-badge-danger-bg: var(--vp-c-danger-soft);
}
/**
* Component: Carbon Ads
* -------------------------------------------------------------------------- */
:root {
--vp-carbon-ads-text-color: var(--vp-c-text-1);
--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);
--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);
--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);
--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1);
}
/**
* Component: Local Search
* -------------------------------------------------------------------------- */
:root {
--vp-local-search-bg: var(--vp-c-bg);
--vp-local-search-result-bg: var(--vp-c-bg);
--vp-local-search-result-border: var(--vp-c-divider);
--vp-local-search-result-selected-bg: var(--vp-c-bg);
--vp-local-search-result-selected-border: var(--vp-c-brand-1);
--vp-local-search-highlight-bg: var(--vp-c-brand-1);
--vp-local-search-highlight-text: var(--vp-c-neutral-inverse);
}
::selection {
background: var(--vp-c-brand-soft);
}

View File

@@ -0,0 +1,133 @@
# Install
You need to install the Meteor command line tool to create, run, and manage your Meteor.js projects. Check the prerequisites and follow the installation process below.
## Prerequisites {#prereqs}
### Node.js version {#prereqs-node}
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3.0 is in progress, and it will run on the latest Node.js version. For more information, please consult our [migration guide](https://guide.meteor.com/3.0-migration.html).
- 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.
### Operating System (OS) {#prereqs-os}
- Meteor currently supports **OS X, Windows, and Linux**. Only 64-bit is supported.
- Meteor supports Windows 7 / Windows Server 2008 R2 and up.
- Apple M1 is natively supported from Meteor 2.5.1 onward (for older versions, rosetta terminal is required).
- If you are on a Mac M1 (Arm64 version) you need to have Rosetta 2 installed, as Meteor uses it for running MongoDB. Check how to install it [here](https://osxdaily.com/2020/12/04/how-install-rosetta-2-apple-silicon-mac/).
- Disabling antivirus (Windows Defender, etc.) will improve performance.
- For compatibility, Linux binaries are built with CentOS 6.4 i386/amd64.
### Mobile Development {#prereqs-mobile}
- iOS development requires the latest Xcode.
## Installation
Install the latest official version of Meteor.js from your terminal by running one of the commands below. You can check our [changelog](https://docs.meteor.com/changelog.html) for the release notes.
> Run `node -v` to ensure you are using Node.js 14. Meteor 3.0 is in progress, and it will run on the latest Node.js version.
For Windows, Linux and OS X, you can run the following command:
```bash
npm install -g meteor
```
An alternative for Linux and OS X, is to install Meteor by using curl:
```bash
curl https://install.meteor.com/ | sh
```
You can also install a specific Meteor.js version by using curl:
```bash
curl https://install.meteor.com/\?release\=2.8 | sh
```
> Do not install the npm Meteor Tool in your project's package.json. This library is just an installer.
## Troubleshooting {#troubleshooting}
If your user doesn't have permission to install global binaries, and you need to use sudo, it's necessary to append *--unsafe-perm* to the above command:
```bash
sudo npm install -g meteor --unsafe-perm
```
We strongly discourage the usage of Node.js or Meteor with root permissions.
Only run the above command with sudo if you know what you are doing.
If you only use sudo because of a distribution default permission system, [check this link for fixing it](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally).
In some cases you can get this error `npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules` because your Node.js installation was performed with wrong permissions. An easy way to fix this is to install Node.js using [nvm](https://github.com/nvm-sh/nvm) and forcing it to be used in your terminal. You can force it in the current session of your terminal by running `nvm use 14`.
## PATH management {#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`)
## Old Versions on Apple M1 {#old-versions-m1}
For Apple M1 computers, you can append Rosetta prefix as following, if you need to run older versions of Meteor (before 2.5.1):
```bash
arch -x86_64 npm install -g meteor
```
or select Terminal in the Applications folder, press CMD(⌘)+I and check the "Open using Rosetta" option.
## Meteor Docker image {#meteor-docker}
You can also use a Docker container for running Meteor inside your CI, or even in your local development toolchain.
We do provide the meteor/meteor-base ubuntu-based Docker image, that comes pre-bundled with Node.JS and Meteor, and runs it as a local user (not as root).
You can refer to our meteor/galaxy-images repository to see how to use it, and the latest version. [More about meteor-base here.](https://github.com/meteor/galaxy-images/blob/master/meteor-base/README.md)
## Note for Windows users {#windows}
On Windows, the installer runs faster when [Windows Developer Mode](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development) is enabled. The installation extracts a large number of small files, which Windows Defender can cause to be very slow.
## Node version manager {#nvm}
If you use a node version manager that uses a separate global `node_modules` folder for each Node version, you will need to re-install the `meteor` npm package when changing to a Node version for the first time. Otherwise, the `meteor` command will no longer be found.
## Note for fish shell users (Linux) {#fish-shell}
To be able to user `meteor` command from fish it's needed to include `/home/<user>/.meteor` in `$PATH`; to do that just add this line in `/home/<user>/.config/fish/config.fish` file (replace `<user>` with your username):
`set PATH /home/<user>/.meteor $PATH`
## Uninstalling Meteor {#uninstall}
If you installed Meteor using npm, you can remove it by running:
`meteor-installer uninstall`
If you installed Meteor using curl, you can remove it by running:
`rm -rf ~/.meteor`
`sudo rm /usr/local/bin/meteor` 

View File

@@ -0,0 +1,84 @@
# Roadmap
Describes the high-level features and actions for the Meteor project in the near-to-medium term future.
## Introduction
**Quick update moving items to Finished: June 1, 2023**
**Last new items added: September 14, 2022.**
The description of many items include sentences and ideas from Meteor community members.
Contributors are encouraged to focus their efforts on work that aligns with the roadmap then we can work together in these areas.
> As with any roadmap, this is a living document that will evolve as priorities and dependencies shift.
> If you have new feature requests or ideas you should open a new [discussion](https://github.com/meteor/meteor/discussions/new).
### Meteor 3.0 release
- Change how Meteor executes Async code; ([Discussion](https://github.com/meteor/meteor/discussions/11505))
- Provide new async APIs where Fibers are required;
- Mongo package with Async API; ([PR](https://github.com/meteor/meteor/pull/12028))
- Provide async versions for Accounts and core packages;
- Adapt Meteor Promise implementation;
- Enable Top-Level Await (TLA) on Meteor server-side; ([PR](https://github.com/meteor/meteor/pull/12095))
- Support Top-Level Await (TLA) on Reify;
- Remove Fibers dependency from Meteor Public APIs;
- Remove Fibers entirely;
- Update Cordova integration to Meteor 3.0;
- Run Meteor on Node.js 18;
- Change web engine from Connect to Express;
### Next releases
- Improve TypeScript support for Meteor and packages; ([Discussion](https://github.com/meteor/meteor/discussions/12080))
- Linux ARM Support; ([PR](https://github.com/meteor/meteor/pull/11809))
- Improve release quality with test coverage and CI automation;
- Review and help to modernize Meteor tools; ([Discussion](https://github.com/meteor/meteor/discussions/12073))
- Improve support for Windows 11 or adopt Windows with WSL;
- Improve Meteor build time; ([Discussion](https://github.com/meteor/meteor/discussions/11587))
- HTTP/3 Support;
- Tree-shaking; ([PR](https://github.com/meteor/meteor/pull/11164))
- Support package.json exports fields; ([Discussion](https://github.com/meteor/meteor/discussions/11727))
### Candidate items
We need to discuss further to decide whether or not to proceed with these implementations.
- Update and fix Meteor Client Bundler or Improve DDP Client;
- Improve Passwordless package; ([Discussion](https://github.com/meteor/meteor/discussions/12075))
- Support building mobile apps using CapacitorJS;
- Bring Redis-oplog to core; ([Repository](https://github.com/Meteor-Community-Packages/redis-oplog))
- MongoDB Change Streams support; ([Discussion](https://github.com/meteor/meteor/discussions/11842))
- Better file upload support via DDP; ([Discussion](https://github.com/meteor/meteor/discussions/11523))
### Next educational items
- Create a new Meteor Guide; ([Current Guide](https://guide.meteor.com/))
- Scaling Meteor Apps course; ([Meteor University](https://university.meteor.com/))
### Finished items
- New Async Tracker; ([Blog Post](https://blog.meteor.com/new-meteor-js-2-10-and-the-async-tracker-feature-ffdbe817c801))
- New Suspense hooks for React + Meteor; ([Blog Post](https://blog.meteor.com/new-suspense-hooks-for-meteor-5391570b3007))
- Release Blaze 2.7 supporting async calls; ([Changelog](https://www.blazejs.org/changelog.html))
- New Scaffold API / generate command; ([Blog Post](https://blog.meteor.com/new-meteor-2-9-and-the-scaffold-api-8b5b2b2b2b2b))
- Types added to the core; ([Blog Post](https://blog.meteor.com/new-meteor-2-8-1-and-adding-types-to-the-core-8a6ee56f0141))
- Update Apollo skeleton NPM dependencies;
- MongoDB 6.0 Support; ([Discussion](https://github.com/meteor/meteor/discussions/12092) / [Blog Post](https://blog.meteor.com/new-meteor-2-11-and-the-new-embedded-mongodb-19767076961b))
- Vite integration;
- SolidJS integration;
- Vue 3 integration; ([Forums](https://forums.meteor.com/t/status-of-vue-3-meteor/57915/25) / [Discussion](https://github.com/meteor/meteor/discussions/11521))
- SolidJS starter template;
- Login and Accounts Course; ([Meteor University](https://university.meteor.com/))
- Updated MongoDB driver to 4.8; ([PR](https://github.com/meteor/meteor/pull/12097))
- Make MongoDB integration stable by fixing critical issues;
- New skeleton for creating Meteor apps with Chakra UI;
- Evaluate and improve support for Meteor in VSCode; ([Repository](https://github.com/matheusccastroo/vscode-meteor-toolbox))
- Release Blaze 2.6.2; ([Blog Post](https://blog.meteor.com/new-meteor-js-2-12-and-the-blaze-2-6-2-release-b72c2a7a593f))
-----------
For more completed items, refer to our [changelog](https://docs.meteor.com/changelog.html).

View File

@@ -0,0 +1,33 @@
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3.0 is in progress, and it will run on the latest Node.js version. For more information, please consult our [migration guide](https://guide.meteor.com/3.0-migration.html).
# What is Meteor? {#what-is-meteor}
Meteor is a full-stack JavaScript platform for developing modern web and mobile applications. Meteor includes a key set of technologies for building connected-client reactive applications, a build tool, and a curated set of packages from the Node.js and general JavaScript community.
- Meteor allows you to develop in **one language**, JavaScript, in all environments: application server, web browser, and mobile device.
- Meteor uses **data on the wire**, meaning the server sends data, not HTML, and the client renders it.
- Meteor **embraces the ecosystem**, bringing the best parts of the extremely active JavaScript community to you in a careful and considered way.
- Meteor provides **full stack reactivity**, allowing your UI to seamlessly reflect the true state of the world with minimal development effort.
## Meteor resources {#learning-more}
1. First, learn how to install meteor in the [installation section](/about/install.html).
1. The place to get started with Meteor is the [tutorials page](https://www.meteor.com/developers/tutorials).
1. [Meteor Examples](https://github.com/meteor/examples) is a list of examples using Meteor. You can also include your example with Meteor.
1. Once you are familiar with the basics, the [Meteor Guide](http://guide.meteor.com) covers intermediate material on how to use Meteor in a larger scale app.
1. Visit the [Meteor discussion forums](https://forums.meteor.com) to announce projects, get help, talk about the community, or discuss changes to core.
1. [Meteor Slack Community](https://join.slack.com/t/meteor-community/shared_invite/enQtODA0NTU2Nzk5MTA3LWY5NGMxMWRjZDgzYWMyMTEyYTQ3MTcwZmU2YjM5MTY3MjJkZjQ0NWRjOGZlYmIxZjFlYTA5Mjg4OTk3ODRiOTc) is the best place to ask (and answer!) technical questions and also meet Meteor developers.
1. [Atmosphere](https://atmospherejs.com) is the repository of community packages designed especially for Meteor.

View File

@@ -0,0 +1,49 @@
---
outline: deep
---
# Runtime API Examples
This page demonstrates usage of some of the runtime APIs provided by VitePress.
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
```md
<script setup>
import { useData } from 'vitepress'
const { theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
```
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).

View File

@@ -0,0 +1,81 @@
# DDPRateLimiter
Customize rate limiting for methods and subscriptions to avoid a high load of WebSocket messages in your app.
> Galaxy (Meteor hosting) offers additional App Protection, [read more](https://galaxy-guide.meteor.com/protection.html) and try it with our [free 30-day trial](https://www.meteor.com/hosting).
By default, `DDPRateLimiter` is configured with a single rule. This rule
limits login attempts, new user creation, and password resets to 5 attempts
every 10 seconds per connection. It can be removed by calling
`Accounts.removeDefaultRateLimit()`.
To use `DDPRateLimiter` for modifying the default rate-limiting rules,
add the `ddp-rate-limiter` package to your project in your terminal:
```bash
meteor add ddp-rate-limiter
```
<ApiBox name="DDPRateLimiter.addRule" hasCustomExample/>
Custom rules can be added by calling `DDPRateLimiter.addRule`. The rate
limiter is called on every method and subscription invocation.
A rate limit is reached when a bucket has surpassed the rule's predefined
capacity, at which point errors will be returned for that input until the
buckets are reset. Buckets are regularly reset after the end of a time
interval.
Here's example of defining a rule and adding it into the `DDPRateLimiter`:
```js
// Define a rule that matches login attempts by non-admin users.
const loginRule = {
userId(userId) {
const user = Meteor.users.findOne(userId);
return user && user.type !== 'admin';
},
type: 'method',
name: 'login'
};
// Add the rule, allowing up to 5 messages every 1000 milliseconds.
DDPRateLimiter.addRule(loginRule, 5, 1000);
```
<ApiBox name="DDPRateLimiter.removeRule" />
<ApiBox name="DDPRateLimiter.setErrorMessage" />
<ApiBox name="DDPRateLimiter.setErrorMessageOnRule" />
Allows developers to specify custom error messages for each rule instead of being
limited to one global error message for every rule.
It adds some clarity to what rules triggered which errors, allowing for better UX
and also opens the door for i18nable error messages per rule instead of the
default English error message.
Here is an example with a custom error message:
```js
const setupGoogleAuthenticatorRule = {
userId(userId) {
const user = Meteor.users.findOne(userId);
return user;
},
type: 'method',
name: 'Users.setupGoogleAuthenticator',
};
// Add the rule, allowing up to 1 google auth setup message every 60 seconds
const ruleId = DDPRateLimiter.addRule(setupGoogleAuthenticatorRule, 1, 60000);
DDPRateLimiter.setErrorMessageOnRule(ruleId, function (data) {
return `You have reached the maximum number of Google Authenticator attempts. Please try again in ${Math.ceil(data.timeToReset / 1000)} seconds.`;
});
```
Or a more simple approach:
```js
const ruleId = DDPRateLimiter.addRule(setupGoogleAuthenticatorRule, 1, 60000);
DDPRateLimiter.setErrorMessageOnRule(ruleId, 'Example as a single string error message');
```

130
v3-docs/docs/api/EJSON.md Normal file
View File

@@ -0,0 +1,130 @@
# EJSON
EJSON is an extension of JSON to support more types. It supports all JSON-safe types, as well as:
- **Date** (JavaScript `Date`)
- **Binary** (JavaScript `Uint8Array` or the
result of [`EJSON.newBinary`](#EJSON-newBinary))
- **Special numbers** (JavaScript `NaN`, `Infinity`, and `-Infinity`)
- **Regular expressions** (JavaScript `RegExp`)
- **User-defined types** (see [`EJSON.addType`](#EJSON-addType). For example,
[`Mongo.ObjectID`](./collections.md#Mongo-ObjectID) is implemented this way.)
All EJSON serializations are also valid JSON. For example an object with a date
and a binary buffer would be serialized in EJSON as:
```json
{
"d": { "$date": 1358205756553 },
"b": { "$binary": "c3VyZS4=" }
}
```
Meteor supports all built-in EJSON data types in publishers, method arguments
and results, Mongo databases, and [`Session`](./session.md) variables.
<ApiBox name="EJSON.parse" />
<ApiBox name="EJSON.stringify" hasCustomExample/>
```js
import { EJSON } from "meteor/ejson";
EJSON.stringify(
{ num: 42, someProp: "foo" },
options // this param is optional
);
```
<ApiBox name="EJSON.fromJSONValue" />
<ApiBox name="EJSON.toJSONValue" />
<ApiBox name="EJSON.equals" />
<ApiBox name="EJSON.clone" />
<ApiBox name="EJSON.newBinary" />
Buffers of binary data are represented by `Uint8Array` instances on JavaScript
platforms that support them. On implementations of JavaScript that do not
support `Uint8Array`, binary data buffers are represented by standard arrays
containing numbers ranging from 0 to 255, and the `$Uint8ArrayPolyfill` key
set to `true`.
<ApiBox name="EJSON.isBinary" />
<ApiBox name="EJSON.addType" hasCustomExample/>
The factory function passed to the `EJSON.addType` method should create an instance of our custom type and initialize it with values from an object passed as the first argument of the factory function. Here is an example:
```js
class Distance {
constructor(value, unit) {
this.value = value;
this.unit = unit;
}
// Convert our type to JSON.
toJSONValue() {
return {
value: this.value,
unit: this.unit,
};
}
// Unique type name.
typeName() {
return "Distance";
}
}
EJSON.addType("Distance", function fromJSONValue(json) {
return new Distance(json.value, json.unit);
});
EJSON.stringify(new Distance(10, "m"));
// Returns '{"$type":"Distance","$value":{"value":10,"unit":"m"}}'
```
When you add a type to EJSON, Meteor will be able to use that type in:
- publishing objects of your type if you pass them to publish handlers.
- allowing your type in the return values or arguments to
[methods](./meteor.md#methods).
- storing your type client-side in Minimongo.
- allowing your type in [`Session`](./session.md) variables.
Instances of your type must implement [`typeName`](#EJSON-CustomType-typeName) and
[`toJSONValue`](#EJSON-CustomType-toJSONValue) methods, and may implement
[`clone`](#EJSON-CustomType-clone) and [`equals`](#EJSON-CustomType-equals) methods if the
default implementations are not sufficient.
<ApiBox name="EJSON.CustomType#typeName" hasCustomExample instanceName="CustomType"/>
<ApiBox name="EJSON.CustomType#toJSONValue" hasCustomExample instanceName="CustomType"/>
For example, the `toJSONValue` method for
[`Mongo.ObjectID`](./collections.md#Mongo-ObjectID) could be:
```js
function () {
return this.toHexString();
}
```
<ApiBox name="EJSON.CustomType#clone" hasCustomExample instanceName="CustomType"/>
If your type does not have a `clone` method, `EJSON.clone` will use
[`toJSONValue`](#EJSON-CustomType-toJSONValue) and the factory instead.
<ApiBox name="EJSON.CustomType#equals" hasCustomExample instanceName="CustomType"/>
The `equals` method should define an [equivalence relation](http://en.wikipedia.org/wiki/Equivalence_relation). It should have
the following properties:
- _Reflexivity_ - for any instance `a`: `a.equals(a)` must be true.
- _Symmetry_ - for any two instances `a` and `b`: `a.equals(b)` if and only if `b.equals(a)`.
- _Transitivity_ - for any three instances `a`, `b`, and `c`: `a.equals(b)` and `b.equals(c)` implies `a.equals(c)`.
If your type does not have an `equals` method, `EJSON.equals` will compare the
result of calling [`toJSONValue`](#EJSON-CustomType-toJSONValue) instead.

View File

@@ -0,0 +1,119 @@
# Reactive Dict
A ReactiveDict stores an arbitrary set of key-value pairs. Use it to manage
internal state in your components, ie. like the currently selected item in a list.
Each key is individully reactive such that calling `set` for a key will
invalidate any Computations that called `get` with that key, according to the
usual contract for reactive data sources.
That means if you call [`ReactiveDict#get`](#ReactiveDict-get)`('currentList')`
from inside a Blaze template helper, the template will automatically be rerendered
whenever [`ReactiveDict#set`](#ReactiveDict-set)`('currentList', x)` is called.
To use `ReactiveDict`, add the `reactive-dict` package to your project by running
in your terminal:
```bash
meteor add reactive-dict
```
<ApiBox name="ReactiveDict" />
If you provide a name to its constructor, its contents will be saved across Hot
Code Push client code updates.
<ApiBox name="ReactiveDict#set" instanceName="reactiveDict" hasCustomExample/>
Example:
```js
import { ReactiveDict } from "meteor/reactive-dict";
import { Tracker } from "meteor/tracker";
import { Meteor } from "meteor/meteor";
const state = new ReactiveDict();
state.set("currentRoomId", "random");
Tracker.autorun(() => {
Meteor.subscribe("chatHistory", { room: state.get("currentRoomId") });
});
// Causes the function passed to `Tracker.autorun` to be rerun, so that the
// 'chatHistory' subscription is moved to the room 'general'.
state.set("currentRoomId", "general");
```
`ReactiveDict.set` can also be called with an object of keys and values, which is
equivalent to calling `ReactiveDict.set` individually on each key/value pair.
```js
import { ReactiveDict } from "meteor/reactive-dict";
const state = new ReactiveDict();
state.set({
a: "foo",
b: "bar",
});
```
<ApiBox name="ReactiveDict#setDefault" instanceName="reactiveDict" />
This is useful in initialization code, to avoid re-initializing your state
every time a new version of your app is loaded.
<ApiBox name="ReactiveDict#get" instanceName="reactiveDict" />
Example in Blaze:
::: code-group
```html [main.html]
<template name="main">
<p>We've always been at war with {{theEnemy}}.</p>
<button class="change-enemy">Change Enemy</button>
</template>
```
```js [main.js]
Template.main.onCreated(function () {
this.state = new ReactiveDict();
this.state.set("enemy", "Eastasia");
});
Template.main.helpers({
theEnemy() {
const inst = Template.instance();
return inst.state.get("enemy");
},
});
Template.main.events({
"click .change-enemy"(event, inst) {
inst.state.set("enemy", "Eurasia");
},
});
// Clicking the button will change the page to say "We've always been at war with Eurasia"
```
:::
<ApiBox name="ReactiveDict#delete" instanceName="reactiveDict" />
<ApiBox name="ReactiveDict#equals" instanceName="reactiveDict" />
If value is a scalar, then these two expressions do the same thing:
```js
import { ReactiveDict } from "meteor/reactive-dict";
const state = new ReactiveDict();
// ...
state.get("key") === value;
state.equals("key", value);
```
However, the second is recommended, as it triggers fewer invalidations
(template redraws), making your program more efficient.
<ApiBox name="ReactiveDict#all" instanceName="reactiveDict" />
<ApiBox name="ReactiveDict#clear" instanceName="reactiveDict" />
<ApiBox name="ReactiveDict#destroy" instanceName="reactiveDict" />

View File

@@ -0,0 +1,41 @@
# ReactiveVar
To use `ReactiveVar`, add the `reactive-var` package to your project by running
in your terminal:
```bash
meteor add reactive-var
```
<ApiBox name="ReactiveVar" />
A ReactiveVar holds a single value that can be get and set, such that calling
`set` will invalidate any Computations that called `get`, according to the
usual contract for reactive data sources.
A ReactiveVar is similar to a Session variable, with a few differences:
- ReactiveVars don't have global names, like the "foo" in `Session.get('foo')`.
Instead, they may be created and used locally, for example attached to a
template instance, as in: `this.foo.get()`.
- ReactiveVars are not automatically migrated across hot code pushes,
whereas Session state is.
- ReactiveVars can hold any value, while Session variables are limited to
JSON or EJSON.
An important property of ReactiveVars &mdash; which is sometimes a
reason for using one &mdash; is that setting the value to the same
value as before has no effect; it does not trigger any invalidations.
So if one autorun sets a ReactiveVar, and another autorun gets the
ReactiveVar, a re-run of the first autorun won't necessarily trigger
the second. By default, only primitive values are compared this way,
while calling `set` on an argument that is an _object_ (not a
primitive) always counts as a change. You can configure this behavior
using the `equalsFunc` argument.
<ApiBox name="ReactiveVar#get" instanceName="reactiveVar" />
<ApiBox name="ReactiveVar#set" instanceName="reactiveVar" />

410
v3-docs/docs/api/Tracker.md Normal file
View File

@@ -0,0 +1,410 @@
# Tracker
Meteor has a simple dependency tracking system which allows it to
automatically rerun templates and other computations whenever
[`Session`](./session.md) variables, database queries, and other data
sources change.
Unlike most other systems, you don't have to manually declare these
dependencies & it "just works". The mechanism is simple and
efficient. When you call a function that supports reactive updates
(such as a database query), it automatically saves the current
Computation object, if any (representing, for example, the current
template being rendered). Later, when the data changes, the function
can "invalidate" the Computation, causing it to rerun (rerendering the
template).
Applications will find [`Tracker.autorun`](#Tracker-autorun) useful, while more
advanced facilities such as `Tracker.Dependency` and `onInvalidate`
callbacks are intended primarily for package authors implementing new
reactive data sources.
<ApiBox name="Tracker.autorun" hasCustomExample/>
`Tracker.autorun` allows you to run a function that depends on reactive data
sources, in such a way that if there are changes to the data later,
the function will be rerun.
For example, you can monitor a cursor (which is a reactive data
source) and aggregate it into a session variable:
```js
Tracker.autorun(() => {
const oldest = _.max(Monkeys.find().fetch(), (monkey) => {
return monkey.age;
});
if (oldest) {
Session.set('oldest', oldest.name);
}
});
```
Or you can wait for a session variable to have a certain value, and do
something the first time it does, calling `stop` on the computation to
prevent further rerunning:
```js
Tracker.autorun((computation) => {
if (!Session.equals('shouldAlert', true)) {
return;
}
computation.stop();
alert('Oh no!');
});
```
The function is invoked immediately, at which point it may alert and
stop right away if `shouldAlert` is already true. If not, the
function is run again when `shouldAlert` becomes true.
A change to a data dependency does not cause an immediate rerun, but
rather "invalidates" the computation, causing it to rerun the next
time a flush occurs. A flush will occur automatically as soon as
the system is idle if there are invalidated computations. You can
also use [`Tracker.flush`](#Tracker-flush) to cause an immediate flush of
all pending reruns.
If you nest calls to `Tracker.autorun`, then when the outer call stops or
reruns, the inner call will stop automatically. Subscriptions and
observers are also automatically stopped when used as part of a
computation that is rerun, allowing new ones to be established. See
[`Meteor.subscribe`](./meteor.md#Meteor-subscribe) for more information about
subscriptions and reactivity.
If the initial run of an autorun throws an exception, the computation
is automatically stopped and won't be rerun.
### Tracker.autorun and async callbacks
`Tracker.autorun` can accept an `async` callback function.
To preserve reactivity for the reactive variables inside the async callback function, you must use a `Tracker.withComputation` call as described below:
<ApiBox name="Tracker.withComputation" />
```javascript
Tracker.autorun(async function example1(computation) {
// Code before the first await will stay reactive.
reactiveVar1.get(); // This will trigger a rerun.
let links = await LinksCollection.findAsync({}).fetch(); // First async call will stay reactive.
// Code after the first await looses Tracker.currentComputation: no reactivity.
reactiveVar2.get(); // This won't trigger a rerun.
// You can bring back reactivity with the Tracker.withCompuation wrapper:
let users = await Tracker.withComputation(computation, () => Meteor.users.findAsync({}).fetch());
// Code below will again not be reactive, so you will need another Tracker.withComputation.
const value = Tracker.withComputation(computation, () => reactiveVar3.get()); // This will trigger a rerun.
});
```
As a rule of thumb, you are okay with wrapping all reactive statements inside a `Tracker.withComputation` to preserve current computation.
But it comes at a performance cost - it should be used only where needed.
Reason behind is, that an await implicitly *"moves"* the code below in a Promise resolved function. When this function runs (after it has been fetched from the micro task queue), `Tracker.withComputation` preserves the reference to the computation of the `Tracker.autorun`.
The `react-meteor-data` package uses `Tracker.withComputation` to make the `useTracker` accept async callbacks.
More can be seen [here](https://github.com/meteor/react-packages/tree/master/packages/react-meteor-data#maintaining-the-reactive-context)
### Using async callbacks in versions of Meteor prior to 2.10
`Tracker.autorun` can accept an `async` callback function.
However, the async call back function will only be dependent on reactive functions called prior to any called functions that return a promise.
Example 1 - autorun `example1()` **is not** dependent on reactive changes to the `Meteor.users` collection. Because it is dependent on nothing reactive it will run only once:
```javascript
Tracker.autorun(async function example1() {
let asyncData = await asyncDataFunction();
let users = Meteor.users.find({}).fetch();
});
```
However, simply changing the order so there are no `async` calls prior to the reactive call to `Meteor.users.find`, will make the async autorun `example2()` dependent on reactive changes to the `Meteor.users` collection.
Example 2 - autorun `example2()` **is** dependent on reactive changes to the Meteor.users collection. Changes to the `Meteor.users` collection will cause a rerun of `example2()`:
```javascript
Tracker.autorun(async function example2() {
let users = Meteor.users.find({}).fetch();
let asyncData = await asyncDataFunction();
});
```
<ApiBox name="Tracker.flush" />
Normally, when you make changes (like writing to the database),
their impact (like updating the DOM) is delayed until the system is
idle. This keeps things predictable &mdash; you can know that the DOM
won't go changing out from under your code as it runs. It's also one
of the things that makes Meteor fast.
`Tracker.flush` forces all of the pending reactive updates to complete.
For example, if an event handler changes a Session
variable that will cause part of the user interface to rerender, the
handler can call `flush` to perform the rerender immediately and then
access the resulting DOM.
An automatic flush occurs whenever the system is idle which performs
exactly the same work as `Tracker.flush`. The flushing process consists
of rerunning any invalidated computations. If additional
invalidations happen while flushing, they are processed as part of the
same flush until there is no more work to be done. Callbacks
registered with [`Tracker.afterFlush`](#Tracker-afterFlush) are called
after processing outstanding invalidations.
It is illegal to call `flush` from inside a `flush` or from a running
computation.
The [Tracker manual](https://github.com/meteor/docs/blob/master/long-form/tracker-manual.md#the-flush-cycle)
describes the motivation for the flush cycle and the guarantees made by
`Tracker.flush` and `Tracker.afterFlush`.
<ApiBox name="Tracker.nonreactive" />
Calls `func` with `Tracker.currentComputation` temporarily set to `null`
and returns `func`'s own return value. If `func` accesses reactive data
sources, these data sources will never cause a rerun of the enclosing
computation.
<ApiBox name="Tracker.active" />
This value is useful for data source implementations to determine
whether they are being accessed reactively or not.
<ApiBox name="Tracker.inFlush" />
This value indicates, whether a flush is in progress or not.
<ApiBox name="Tracker.currentComputation" />
It's very rare to need to access `currentComputation` directly. The
current computation is used implicitly by
[`Tracker.active`](#Tracker-active) (which tests whether there is one),
[`dependency.depend()`](#Tracker-Dependency-depend) (which registers that it depends on a
dependency), and [`Tracker.onInvalidate`](#Tracker-onInvalidate) (which
registers a callback with it).
<ApiBox name="Tracker.onInvalidate" />
See [`computation.onInvalidate`](#Tracker-Computation-onInvalidate) for more
details.
<ApiBox name="Tracker.afterFlush" />
Functions scheduled by multiple calls to `afterFlush` are guaranteed
to run in the order that `afterFlush` was called. Functions are
guaranteed to be called at a time when there are no invalidated
computations that need rerunning. This means that if an `afterFlush`
function invalidates a computation, that computation will be rerun
before any other `afterFlush` functions are called.
## Tracker.Computation {#tracker_computation}
A Computation object represents code that is repeatedly rerun in
response to reactive data changes. Computations don't have return
values; they just perform actions, such as rerendering a template on
the screen. Computations are created using [`Tracker.autorun`](#Tracker-autorun).
Use [`stop`](#Tracker-Computation-stop) to prevent further rerunning of a
computation.
Each time a computation runs, it may access various reactive data
sources that serve as inputs to the computation, which are called its
dependencies. At some future time, one of these dependencies may
trigger the computation to be rerun by invalidating it. When this
happens, the dependencies are cleared, and the computation is
scheduled to be rerun at flush time.
The *current computation*
([`Tracker.currentComputation`](#Tracker-currentComputation)) is the
computation that is currently being run or rerun (computed), and the
one that gains a dependency when a reactive data source is accessed.
Data sources are responsible for tracking these dependencies using
[`Tracker.Dependency`](#Tracker-Dependency) objects.
Invalidating a computation sets its `invalidated` property to true
and immediately calls all of the computation's `onInvalidate`
callbacks. When a flush occurs, if the computation has been invalidated
and not stopped, then the computation is rerun by setting the
`invalidated` property to `false` and calling the original function
that was passed to `Tracker.autorun`. A flush will occur when the current
code finishes running, or sooner if `Tracker.flush` is called.
Stopping a computation invalidates it (if it is valid) for the purpose
of calling callbacks, but ensures that it will never be rerun.
Example:
```js
// If we're in a computation, then perform some clean-up when the current
// computation is invalidated (rerun or stopped).
if (Tracker.active) {
Tracker.onInvalidate(() => {
x.destroy();
y.finalize();
});
}
```
<ApiBox name="Tracker.Computation#stop" instanceName="computation"/>
Stopping a computation is irreversible and guarantees that it will
never be rerun. You can stop a computation at any time, including
from the computation's own run function. Stopping a computation that
is already stopped has no effect.
Stopping a computation causes its `onInvalidate` callbacks to run
immediately if it is not currently invalidated, as well as its
`stop` callbacks.
Nested computations are stopped automatically when their enclosing
computation is rerun.
<ApiBox name="Tracker.Computation#invalidate"instanceName="computation" />
Invalidating a computation marks it to be rerun at
[flush time](#Tracker-flush), at
which point the computation becomes valid again. It is rare to
invalidate a computation manually, because reactive data sources
invalidate their calling computations when they change. Reactive data
sources in turn perform this invalidation using one or more
[`Tracker.Dependency`](#Tracker-Dependency) objects.
Invalidating a computation immediately calls all `onInvalidate`
callbacks registered on it. Invalidating a computation that is
currently invalidated or is stopped has no effect. A computation can
invalidate itself, but if it continues to do so indefinitely, the
result will be an infinite loop.
<ApiBox name="Tracker.Computation#onInvalidate" instanceName="computation" />
`onInvalidate` registers a one-time callback that either fires
immediately or as soon as the computation is next invalidated or
stopped. It is used by reactive data sources to clean up resources or
break dependencies when a computation is rerun or stopped.
To get a callback after a computation has been recomputed, you can
call [`Tracker.afterFlush`](#Tracker-afterFlush) from `onInvalidate`.
<ApiBox name="Tracker.Computation#onStop" instanceName="computation"/>
<ApiBox name="Tracker.Computation#stopped" instanceName="computation"/>
<ApiBox name="Tracker.Computation#invalidated"instanceName="computation"/>
This property is initially false. It is set to true by `stop()` and
`invalidate()`. It is reset to false when the computation is
recomputed at flush time.
<ApiBox name="Tracker.Computation#firstRun" instanceName="computation"/>
This property is a convenience to support the common pattern where a
computation has logic specific to the first run.
<ApiBox name="Tracker.Computation#firstRunPromise" instanceName="computation" />
`Computation.firstRunPromise` will be set to the result of the call of the autorun function after the initial computation has been completed. If the autorun function is an async function, it'll then contain its promise, thus making the completion of the execution await-able. That allows us to manually synchronize autoruns like this:
```js
await Tracker.autorun(async () => {
await Meteor.userAsync();
(...more async code...)
}).firstRunPromise;
await Tracker.autorun(async () => {
await asyncSomeOrOther();
(...more async code...)
}).firstRunPromise;
```
For a better developer experience `firstRunPromise` is automatically appended to your async `autorun` calls so you don't have to write them yourself. Meaning this also works:
```js
await Tracker.autorun(async () => {
await Meteor.userAsync();
(...more async code...)
});
await Tracker.autorun(async () => {
await asyncSomeOrOther();
(...more async code...)
});
```
## Tracker.Dependency {#Tracker-Dependency}
A Dependency represents an atomic unit of reactive data that a
computation might depend on. Reactive data sources such as Session or
Minimongo internally create different Dependency objects for different
pieces of data, each of which may be depended on by multiple
computations. When the data changes, the computations are
invalidated.
Dependencies don't store data, they just track the set of computations to
invalidate if something changes. Typically, a data value will be
accompanied by a Dependency object that tracks the computations that depend
on it, as in this example:
```js
let weather = 'sunny';
const weatherDep = new Tracker.Dependency();
function getWeather() {
weatherDep.depend();
return weather;
}
function setWeather(newWeather) {
weather = newWeather;
// Note: We could add logic here to only call `changed` if the new value is
// different from the old value.
weatherDep.changed();
}
```
This example implements a weather data source with a simple getter and
setter. The getter records that the current computation depends on
the `weatherDep` dependency using `depend()`, while the setter
signals the dependency to invalidate all dependent computations by
calling `changed()`.
The reason Dependencies do not store data themselves is that it can be
useful to associate multiple Dependencies with the same piece of data.
For example, one Dependency might represent the result of a database
query, while another might represent just the number of documents in
the result. A Dependency could represent whether the weather is sunny
or not, or whether the temperature is above freezing.
[`Session.equals`](./session.md#Session-equals) is implemented this way for
efficiency. When you call `Session.equals('weather', 'sunny')`, the
current computation is made to depend on an internal Dependency that
does not change if the weather goes from, say, `rainy` to `cloudy`.
Conceptually, the only two things a Dependency can do are gain a
dependent and change.
A Dependency's dependent computations are always valid (they have
`invalidated === false`). If a dependent is invalidated at any time,
either by the Dependency itself or some other way, it is immediately
removed.
See the [Tracker manual](https://github.com/meteor/docs/blob/master/long-form/tracker-manual.md#creating-a-reactive-value-using-trackerdependency)
to learn how to create a reactive data source using `Tracker.Dependency`.
<ApiBox name="Tracker.Dependency#changed" instanceName="dependency"/>
<ApiBox name="Tracker.Dependency#depend" instanceName="dependency"/>
`dependency.depend()` is used in reactive data source implementations to record
the fact that `dependency` is being accessed from the current computation.
<ApiBox name="Tracker.Dependency#hasDependents" instanceName="dependency"/>
For reactive data sources that create many internal Dependencies,
this function is useful to determine whether a particular Dependency is
still tracking any dependency relationships or if it can be cleaned up
to save memory.

View File

@@ -0,0 +1,950 @@
# Accounts
## Accounts-base {#accounts-base}
The Meteor Accounts system builds on top of the `userId` support in
[`publish`](./meteor#Subscription-userId) and [`methods`](./meteor#methods-userId). The core
packages add the concept of user documents stored in the database, and
additional packages add [secure password authentication](#passwords),
[integration with third party login services](#Meteor-loginWith%3CExternalService%3E),
and a [pre-built userinterface](/packages/accounts-ui.html).
The basic Accounts system is in the `accounts-base` package, but
applications typically include this automatically by adding one of the
login provider packages: `accounts-password`, `accounts-facebook`,
`accounts-github`, `accounts-google`, `accounts-meetup`,
`accounts-twitter`, or `accounts-weibo`.
Read more about customizing user accounts in the [Accounts](http://guide.meteor.com/accounts.html) article in the Meteor Guide.
<ApiBox name="Meteor.user" hasCustomExample/>
Retrieves the user record for the current user from
the [`Meteor.users`](#Meteor-users) collection.
On the client, the available fields will be those that
are published from the server (other fields won't be available on the
client). By default the server publishes `username`, `emails`, and
`profile` (writable by user). See [`Meteor.users`](#Meteor-users) for more on
the fields used in user documents.
On the server, this will fetch the record from the database. To improve the
latency of a method that uses the user document multiple times, save the
returned record to a variable instead of re-calling `Meteor.user()`.
Fetching the full user document can cause unnecessary database usage on the
server and over-reactivity on the client, particularly if you store lots of
custom data on it. Therefore it is recommended to use the `options`
parameter to only fetch the fields you need:
```js
import { Meteor } from "meteor/meteor";
const userName = Meteor.user({ fields: { "profile.name": 1 } }).profile.name;
```
<ApiBox name="Meteor.userAsync" hasCustomExample/>
Same as [`Meteor.user`](#Meteor-user), but returns a promise and is available on the server.
```js
import { Meteor } from "meteor/meteor";
const user = await Meteor.userAsync();
```
<ApiBox name="Meteor.userId" />
<ApiBox name="Meteor.users" />
This collection contains one document per registered user. Here's an example
user document:
```js
{
_id: 'QwkSmTCZiw5KDx3L6', // Meteor.userId()
username: 'cool_kid_13', // Unique name
emails: [
// Each email address can only belong to one user.
{ address: 'cool@example.com', verified: true },
{ address: 'another@different.com', verified: false }
],
createdAt: new Date('Wed Aug 21 2013 15:16:52 GMT-0700 (PDT)'),
profile: {
// The profile is writable by the user by default.
name: 'Joe Schmoe'
},
services: {
facebook: {
id: '709050', // Facebook ID
accessToken: 'AAACCgdX7G2...AbV9AZDZD'
},
resume: {
loginTokens: [
{ token: '97e8c205-c7e4-47c9-9bea-8e2ccc0694cd',
when: 1349761684048 }
]
}
}
}
```
A user document can contain any data you want to store about a user. Meteor
treats the following fields specially:
- `username`: a unique String identifying the user.
- `emails`: an Array of Objects with keys `address` and `verified`;
an email address may belong to at most one user. `verified` is
a Boolean which is true if the user has [verified the address](#Accounts-verifyEmail) with a token sent over email.
- `createdAt`: the Date at which the user document was created.
- `profile`: an Object which the user can create and update with any data.
Do not store anything on `profile` that you wouldn't want the user to edit
unless you have a deny rule on the `Meteor.users` collection.
- `services`: an Object containing data used by particular
login services. For example, its `reset` field contains
tokens used by [forgot password](#Accounts-forgotPassword) links,
and its `resume` field contains tokens used to keep you
logged in between sessions.
Like all [Mongo.Collection](./collections.md)s, you can access all
documents on the server, but only those specifically published by the server are
available on the client. You can also use all Collection methods, for instance
`Meteor.users.remove` on the server to delete a user.
By default, the current user's `username`, `emails` and `profile` are
published to the client. You can publish additional fields for the
current user with:
::: code-group
```js [server.js]
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find(
{ _id: this.userId },
{
fields: { other: 1, things: 1 },
}
);
} else {
this.ready();
}
});
```
```js [client.js]
Meteor.subscribe("userData");
```
:::
If the autopublish package is installed, information about all users
on the system is published to all clients. This includes `username`,
`profile`, and any fields in `services` that are meant to be public
(eg `services.facebook.id`,
`services.twitter.screenName`). Additionally, when using autopublish
more information is published for the currently logged in user,
including access tokens. This allows making API calls directly from
the client for services that allow this.
Users are by default allowed to specify their own `profile` field with
[`Accounts.createUser`](#Accounts-createUser) and modify it with
`Meteor.users.update`. To allow users to edit additional fields, use
[`Meteor.users.allow`](./collections.md#Mongo-Collection-allow). To forbid users from making any modifications to
their user document:
```js
import { Meteor } from "meteor/meteor";
Meteor.users.deny({ update: () => true });
```
<ApiBox name="Meteor.loggingIn" />
For example, [the `accounts-ui` package](../packages/accounts-ui.md) uses this to display an
animation while the login request is being processed.
<ApiBox name="Meteor.loggingOut" />
<ApiBox name="Meteor.logout" />
<ApiBox name="Meteor.logoutOtherClients" />
For example, when called in a user's browser, connections in that browser
remain logged in, but any other browsers or DDP clients logged in as that user
will be logged out.
<ApiBox name="Meteor.loginWithPassword" />
If there are multiple users with a username or email only differing in case, a case sensitive match is required. Although `createUser` won't let you create users with ambiguous usernames or emails, this could happen with existing databases or if you modify the users collection directly.
This method can fail throwing one of the following errors:
- "Unrecognized options for login request [400]" if `user` or `password` is undefined.
- "Match failed [400]" if `user` isn't an Object or String, or `password` isn't a String.
- "User not found [403]" if the email or username provided in `user` doesn't belong to a registered user.
- "Incorrect password [403]" if the password provided is incorrect.
- "User has no password set [403]" if `user` doesn't have a password.
This function is provided by the `accounts-password` package. See the
[Passwords](#passwords) section below.
<ApiBox name="Meteor.loginWith<ExternalService>" />
Available functions are:
- `Meteor.loginWithMeteorDeveloperAccount`
- `Meteor.loginWithFacebook`
- `options` may also include [Facebook's `auth_type` parameter](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#reaskperms)
- `Meteor.loginWithGithub`
- `Meteor.loginWithGoogle`
- `options` may also include [Google's additional URI parameters](https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters)
- `Meteor.loginWithMeetup`
- `Meteor.loginWithTwitter`
- `options` may also include [Twitter's `force_login` parameter](https://dev.twitter.com/oauth/reference/get/oauth/authenticate)
- `Meteor.loginWithWeibo`
These functions initiate the login process with an external
service (eg: Facebook, Google, etc), using OAuth. When called they open a new pop-up
window that loads the provider's login page. Once the user has logged in
with the provider, the pop-up window is closed and the Meteor client
logs in to the Meteor server with the information provided by the external
service.
<h3 id="requestpermissions" name="requestpermissions">Requesting Permissions</h3>
In addition to identifying the user to your application, some services
have APIs that allow you to take action on behalf of the user. To
request specific permissions from the user, pass the
`requestPermissions` option the login function. This will cause the user
to be presented with an additional page in the pop-up dialog to permit
access to their data. The user's `accessToken` &mdash; with permissions
to access the service's API &mdash; is stored in the `services` field of
the user document. The supported values for `requestPermissions` differ
for each login service and are documented on their respective developer
sites:
- Facebook: <http://developers.facebook.com/docs/authentication/permissions/>
- GitHub: <http://developer.github.com/v3/oauth/#scopes>
- Google: <https://developers.google.com/identity/protocols/googlescopes>
- Meetup: <http://www.meetup.com/meetup_api/auth/#oauth2-scopes>
- Twitter, Weibo, Meteor developer accounts: `requestPermissions` currently not supported
External login services typically require registering and configuring
your application before use. The easiest way to do this is with the
[`accounts-ui` package](../packages/accounts-ui.md) which presents a step-by-step guide
to configuring each service. However, the data can be also be entered
manually in the `ServiceConfiguration.configurations` collection, which
is exported by the `service-configuration` package.
## Configuring Services {#service-configuration}
First, add the service configuration package:
```bash
meteor add service-configuration
```
Then, inside the server of your app (this example is for the Weebo service), import `ServiceConfiguration`:
```js
import { ServiceConfiguration } from "meteor/service-configuration";
ServiceConfiguration.configurations.upsert(
{ service: "weibo" },
{
$set: {
loginStyle: "popup",
clientId: "1292962797", // See table below for correct property name!
secret: "75a730b58f5691de5522789070c319bc",
},
}
);
```
Since Meteor 2.7 you no longer need to manually set the configuration and instead can use Meteor settings by setting your services under `Meteor.settings.packages.service-configuration.<service>`. All the properties can be set under the service and will be added to the database as is, so make sure that they are correct. For the example above, the settings would look like:
```json
{
"packages": {
"service-configuration": {
"weibo": {
"loginStyle": "popup",
"clientId": "1292962797",
"secret": "75a730b58f5691de5522789070c319bc"
}
}
}
}
```
The correct property name to use for the API identifier (i.e. `clientId` in the above example) depends on the login service being used, so be sure to use the correct one:
| Property Name | Services |
| ------------- | -------------------------------------------------------- |
| `appId` | Facebook |
| `clientId` | Github, Google, Meetup, Meteor Developer Accounts, Weibo |
| `consumerKey` | Twitter |
Additionally, each external service has its own login provider package and login function. For
example, to support GitHub login, run the following in your terminal:
```bash
meteor add accounts-github
```
and use the `Meteor.loginWithGithub` function:
```js
import { Meteor } from "meteor/meteor";
Meteor.loginWithGithub(
{
requestPermissions: ["user", "public_repo"],
},
(error) => {
if (error) {
Session.set("errorMessage", error.reason || "Unknown error");
}
}
);
```
Login service configuration is sent from the server to the client over DDP when
your app starts up; you may not call the login function until the configuration
is loaded. The function `Accounts.loginServicesConfigured()` is a reactive data
source that will return true once the login service is configured; you should
not make login buttons visible or active until it is true.
Ensure that your [`$ROOT_URL`](./meteor.md#Meteor-absoluteUrl) matches the authorized
domain and callback URL that you configure with the external service (for
instance, if you are running Meteor behind a proxy server, `$ROOT_URL` should be
the externally-accessible URL, not the URL inside your proxy).
## Manual service configuration {#manual-service-configuration}
You can use `Accounts.loginServiceConfiguration` to view and edit the settings collection:
```js
import { Accounts } from "meteor/accounts-base";
Accounts.loginServiceConfiguration.find();
```
## Popup versus redirect flow {#popup-vs-redirect-flow}
When configuring OAuth login with a provider (such as Facebook or Google), Meteor lets you choose a popup- or redirect-based flow. In a popup-based flow, when a user logs in, they will be prompted to login at the provider in a popup window. In a redirect-based flow, the user's whole browser window will be redirected to the login provider, and the window will redirect back to your app when the login is completed.
You can also pick which type of login to do by passing an option to [`Meteor.loginWith<ExternalService>`](#Meteor-loginWith%3CExternalService%3E)
Usually, the popup-based flow is preferable because the user will not have to reload your whole app at the end of the login flow. However, the popup-based flow requires browser features such as `window.close` and `window.opener` that are not available in all mobile environments. In particular, we recommend using `Meteor.loginWith<ExternalService>({ loginStyle: 'redirect' })` in the following environments:
- Inside UIWebViews (when your app is loaded inside a mobile app)
- In Safari on iOS8 (`window.close` is not supported due to a bug)
<ApiBox name="currentUser" />
<ApiBox name="loggingIn" />
<ApiBox name="Accounts.ui.config" hasCustomExample/>
Example:
```js
import { Accounts } from "meteor/accounts-base";
Accounts.ui.config({
requestPermissions: {
facebook: ["user_likes"],
github: ["user", "repo"],
},
requestOfflineToken: {
google: true,
},
passwordSignupFields: "USERNAME_AND_OPTIONAL_EMAIL",
});
```
Since Meteor 2.7 you can configure these in your Meteor settings under `Meteor.settings.public.packages.accounts-ui-unstyled`.
## Multi-server {#multi-server}
The `accounts-base` package exports two constructors, called
`AccountsClient` and `AccountsServer`, which are used to create the
`Accounts` object that is available on the client and the server,
respectively.
This predefined `Accounts` object (along with similar convenience methods
of `Meteor`, such as [`Meteor.logout`](#Meteor-logout)) is sufficient to
implement most accounts-related logic in Meteor apps. Nevertheless, these
two constructors can be instantiated more than once, to create multiple
independent connections between different accounts servers and their
clients, in more complicated authentication situations.
<ApiBox name="AccountsCommon"/>
The `AccountsClient` and `AccountsServer` classes share a common
superclass, `AccountsCommon`. Methods defined on
`AccountsCommon.prototype` will be available on both the client and the
server, via the predefined `Accounts` object (most common) or any custom
`accountsClientOrServer` object created using the `AccountsClient` or
`AccountsServer` constructors (less common).
Here are a few of those methods:
<ApiBox name="AccountsCommon#userId" instanceName="accountsCommon"/>
<ApiBox name="AccountsCommon#user" instanceName="accountsCommon"/>
<ApiBox name="AccountsCommon#config" instanceName="accountsCommon"/>
From Meteor 2.5 you can set these in your Meteor settings under `Meteor.settings.packages.accounts-base`. Note that due to the nature of settings file you won't be able to set parameters that require functions.
<ApiBox name="AccountsCommon#onLogin" instanceName="accountsCommon"/>
See description of [AccountsCommon#onLoginFailure](#AccountsCommon-onLoginFailure)
for details.
<ApiBox name="AccountsCommon#onLoginFailure" instanceName="accountsCommon"/>
Either the `onLogin` or the `onLoginFailure` callbacks will be called
for each login attempt. The `onLogin` callbacks are called after the
user has been successfully logged in. The `onLoginFailure` callbacks are
called after a login attempt is denied.
These functions return an object with a single method, `stop`. Calling
`stop()` unregisters the callback.
On the server, the callbacks get a single argument, the same attempt info
object as [`validateLoginAttempt`](#AccountsServer-validateLoginAttempt). On the
client, the callback argument is an object containing a single `error`
property set to the `Error`-object which was received from the failed login
attempt.
<ApiBox name="AccountsCommon#onLogout" instanceName="accountsCommon" hasCustomExample/>
On the server, the `func` callback receives a single argument with the object below. On the
client, no arguments are passed.
```js
import { AccountsCommon } from "meteor/accounts-base";
const options = {
//...
};
const accountsCommon = new AccountsCommon(options);
accountsCommon.onLogout(({ user, connection, collection }) => {
console.log(user);
// ˆˆˆˆˆˆ The Meteor user object of the user which just logged out
console.log(connection);
// ˆˆˆˆˆˆ The connection object the request came in on. See
// `Meteor.onConnection` for details.
console.log(collection);
// ˆˆˆˆˆˆ The `collection` The name of the Mongo.Collection or the
// Mongo.Collection object to hold the users.
});
```
<ApiBox name="AccountsClient"/>
At most one of `options.connection` and `options.ddpUrl` should be
provided in any instantiation of `AccountsClient`. If neither is provided,
`Meteor.connection` will be used as the `.connection` property of the
`AccountsClient` instance.
Note that `AccountsClient` is currently available only on the client, due
to its use of browser APIs such as `window.localStorage`. In principle,
though, it might make sense to establish a client connection from one
server to another remote accounts server. Please [let us
know](https://github.com/meteor/meteor/wiki/Contributing-to-Meteor#feature-requests)
if you find yourself needing this server-to-server functionality.
These methods are defined on `AccountsClient.prototype`, and are thus
available only on the client:
<ApiBox name="AccountsClient#loggingIn" instanceName="accountsClient"/>
<ApiBox name="AccountsClient#logout" instanceName="accountsClient"/>
<ApiBox name="AccountsClient#logoutOtherClients" instanceName="accountsClient"/>
<ApiBox name="AccountsServer"/>
These methods are defined on `AccountsServer.prototype`, and are thus
available only on the server:
<ApiBox name="AccountsServer#validateNewUser" instanceName="accountsServer"/>
This can be called multiple times. If any of the functions return `false` or
throw an error, the new user creation is aborted. To set a specific error
message (which will be displayed by [`accounts-ui`](../packages/accounts-ui.md)), throw a new
[`Meteor.Error`](./meteor#meteor-api).
Example:
```js
import { Accounts } from "meteor/accounts-base";
// Validate username, sending a specific error message on failure.
Accounts.validateNewUser((user) => {
if (user.username && user.username.length >= 3) {
return true;
} else {
throw new Meteor.Error(403, "Username must have at least 3 characters");
}
});
// Validate username, without a specific error message.
Accounts.validateNewUser((user) => {
return user.username !== "root";
});
```
If the user is being created as part of a login attempt from a client (eg,
calling [`Accounts.createUser`](#Accounts-createUser) from the client, or
[logging in for the first time with an external
service](#meteor_loginwithexternalservice)), these callbacks are called _before_
the [`Accounts.validateLoginAttempt`](#Accounts-validateLoginAttempt)
callbacks. If these callbacks succeed but those fail, the user will still be
created but the connection will not be logged in as that user.
<ApiBox name="AccountsServer#onCreateUser" instanceName="accountsServer" hasCustomExample/>
Use this when you need to do more than simply accept or reject new user
creation. With this function you can programatically control the
contents of new user documents.
The function you pass will be called with two arguments: `options` and
`user`. The `options` argument comes
from [`Accounts.createUser`](#Accounts-createUser) for
password-based users or from an external service login flow. `options` may come
from an untrusted client so make sure to validate any values you read from
it. The `user` argument is created on the server and contains a
proposed user object with all the automatically generated fields
required for the user to log in, including the `_id`.
The function should return the user document (either the one passed in or a
newly-created object) with whatever modifications are desired. The returned
document is inserted directly into the [`Meteor.users`](#Meteor-users) collection.
The default create user function simply copies `options.profile` into
the new user document. Calling `onCreateUser` overrides the default
hook. This can only be called once.
Example:
```js
import { Accounts } from "meteor/accounts-base";
// Support for playing D&D: Roll 3d6 for dexterity.
Accounts.onCreateUser((options, user) => {
const customizedUser = Object.assign(
{
dexterity: _.random(1, 6) + _.random(1, 6) + _.random(1, 6),
},
user
);
// We still want the default hook's 'profile' behavior.
if (options.profile) {
customizedUser.profile = options.profile;
}
return customizedUser;
});
```
<ApiBox name="AccountsServer#validateLoginAttempt" instanceName="accountsServer" hasCustomExample/>
Call `validateLoginAttempt` with a callback to be called on login
attempts. It returns an object with a single method, `stop`. Calling
`stop()` unregisters the callback.
When a login attempt is made, the registered validate login callbacks
are called with a single argument, you can check the example:
```js
import { AccountsServer } from "meteor/accounts-base";
const options = {
//...
};
const accountsServer = new AccountsServer(options);
accountsServer.validateLoginAttempt(
({
type, // String
allowed, // Boolean
error, // Error
user, // Object
connection, // Object
collection, // Object
methodName, // String
methodArguments, // Array<String>
}) => {
console.log(type);
// ˆˆˆˆˆˆ The service name, such as "password" or "twitter".
console.log(allowed);
// ˆˆˆˆˆˆ Whether this login is allowed and will be successful (if not aborted
// by any of the validateLoginAttempt callbacks). False if the login
// will not succeed (for example, an invalid password or the login was
// aborted by a previous validateLoginAttempt callback).
console.log(error);
// ˆˆˆˆˆˆ When `allowed` is false, the exception describing why the login
// failed. It will be a `Meteor.Error` for failures reported to the
// user (such as invalid password), and can be a another kind of
// exception for internal errors.
console.log(user);
// ˆˆˆˆˆˆ When it is known which user was attempting to login,
// the Meteor user object. This will always be present for successful logins.
console.log(connection);
// ˆˆˆˆˆˆ The `connection` object the request came in on. See
// [`Meteor.onConnection`](#meteor_onconnection) for details.
console.log(collection);
// ˆˆˆˆˆˆ The `collection` The name of the Mongo.Collection or the
// Mongo.Collection object to hold the users.
console.log(methodName);
// ˆˆˆˆˆˆ The name of the Meteor method being used to login.
// For example, "login", "loginWithPassword", or "loginWith<ExternalService>".
console.log(methodArguments);
// ˆˆˆˆˆˆ An array of the arguments passed to the login method.
// For example, `["username", "password"]`
}
);
```
A validate login callback must return a truthy value for the login to
proceed. If the callback returns a falsy value or throws an
exception, the login is aborted. Throwing a `Meteor.Error` will
report the error reason to the user.
All registered validate login callbacks are called, even if one of the callbacks
aborts the login. The later callbacks will see the `allowed` field set to
`false` since the login will now not be successful. This allows later callbacks
to override an error from a previous callback; for example, you could override
the "Incorrect password" error with a different message.
Validate login callbacks that aren't explicitly trying to override a previous
error generally have no need to run if the attempt has already been determined
to fail, and should start with
```js
if (!attempt.allowed) {
return false;
}
```
<ApiBox name="AccountsServer#beforeExternalLogin" instanceName="accountsServer" hasCustomExample/>
Use this hook if you need to validate that user from an external service should
be allowed to login or create account.
```js
import { AccountsServer } from "meteor/accounts-base";
const options = {
//...
};
const accountsServer = new AccountsServer(options);
accountsServer.beforeExternalLogin(({ type, data, user }) => {
console.log(type);
// ˆˆˆˆˆˆ The service name, such as "google" or "twitter". Is a String
console.log(data);
// ˆˆˆˆˆˆ Data retrieved from the service (eg: email, name, etc)
// Is an Object.
console.log(user);
// ˆˆˆˆˆˆ If user was found in the database that matches the criteria from the service,
// their data will be provided here. Is an Object.
});
```
You should return a `Boolean` value, `true` if the login/registration should
proceed or `false` if it should terminate. In case of termination
the login attempt will throw an error `403`, with the message: `Login forbidden`.
<ApiBox name="AccountsServer#setAdditionalFindUserOnExternalLogin" hasCustomExample instanceName="accountsServer"/>
When allowing your users to authenticate with an external service, the process will
eventually call `Accounts.updateOrCreateUserFromExternalService`. By default, this
will search for a user with the `service.<servicename>.id`, and if not found will
create a new user. As that is not always desirable, you can use this hook as an
escape hatch to look up a user with a different selector, probably by `emails.address` or `username`. Note the function will only be called if no user was found with the
`service.<servicename>.id` selector.
The function will be called with a single argument, the info object:
```js
import { AccountsServer } from "meteor/accounts-base";
const options = {
//...
};
const accountsServer = new AccountsServer(options);
accountsServer.setAdditionalFindUserOnExternalLogin(
({ serviceName, serviceData, options }) => {
// serviceName: String
// The external service name, such as "google" or "twitter".
// serviceData: Object
// The data returned by the service oauth request.
// options: Object
// An optional arugment passed down from the oauth service that may contain
// additional user profile information. As the data in `options` comes from an
// external source, make sure you validate any values you read from it.
}
);
```
The function should return either a user document or `undefined`. Returning a user
will result in the populating the `service.<servicename>` in your user document,
while returning `undefined` will result in a new user account being created.
If you would prefer that a new account not be created, you could throw an error
instead of returning.
Example:
```js
// If a user has already been created, and used their Google email, this will
// allow them to sign in with the Meteor.loginWithGoogle method later, without
// creating a new user.
Accounts.setAdditionalFindUserOnExternalLogin(
({ serviceName, serviceData }) => {
if (serviceName === "google") {
// Note: Consider security implications. If someone other than the owner
// gains access to the account on the third-party service they could use
// the e-mail set there to access the account on your app.
// Most often this is not an issue, but as a developer you should be aware
// of how bad actors could play.
return Accounts.findUserByEmail(serviceData.email);
}
}
);
```
<ApiBox name="AccountsServer#registerLoginHandler" instanceName="accountsServer"/>
Use this to register your own custom authentication method. This is also used by all of the other inbuilt accounts packages to integrate with the accounts system.
There can be multiple login handlers that are registered. When a login request is made, it will go through all these handlers to find its own handler.
The registered handler callback is called with a single argument, the `options` object which comes from the login method. For example, if you want to login with a plaintext password, `options` could be `{ user: { username: <username> }, password: <password> }`,or `{ user: { email: <email> }, password: <password> }`.
The login handler should return `undefined` if it's not going to handle the login request or else the login result object.
<h2 id="accounts_rate_limit">Rate Limiting</h2>
By default, there are rules added to the [`DDPRateLimiter`](./DDPRateLimiter.md)
that rate limit logins, new user registration and password reset calls to a
limit of 5 requests per 10 seconds per session. These are a basic solution
to dictionary attacks where a malicious user attempts to guess the passwords
of legitimate users by attempting all possible passwords.
These rate limiting rules can be removed by calling
`Accounts.removeDefaultRateLimit()`. Please see the
[`DDPRateLimiter`](./DDPRateLimiter.md) docs for more information.
<ApiBox name="AccountsServer#addDefaultRateLimit" instanceName="accountsServer"/>
<ApiBox name="AccountsServer#removeDefaultRateLimit" instanceName="accountsServer"/>
## Passwords {#passwords}
The `accounts-password` package contains a full system for password-based
authentication. In addition to the basic username and password-based
sign-in process, it also supports email-based sign-in including
address verification and password recovery emails.
The Meteor server stores passwords using the
[bcrypt](http://en.wikipedia.org/wiki/Bcrypt) algorithm. This helps
protect against embarrassing password leaks if the server's database is
compromised.
To add password support to your application, run this command in your terminal:
```bash
meteor add accounts-password
```
> In addition to configuring the [`email`](./email.md) package's `MAIL_URL`, it is critical that you set proper values (specifically the `from` address) in [`Accounts.emailTemplates`](#Accounts-emailTemplates) to ensure proper delivery of e-mails!
You can construct your own user interface using the
functions below, or use the [`accounts-ui` package](../packages/accounts-ui.md) to
include a turn-key user interface for password-based sign-in.
<ApiBox name="Accounts.createUser" />
On the client, this function logs in as the newly created user on
successful completion. On the server, it returns the newly created user
id.
On the client, you must pass `password` and at least one of `username` or `email` &mdash; enough information for the user to be able to log in again later. If there are existing users with a username or email only differing in case, `createUser` will fail. The callback's `error.reason` will be `'Username already exists.'` or `'Email already exists.'` In the latter case, the user can then either [login](accounts.html#Meteor-loginWithPassword) or [reset their password](#Accounts-resetPassword).
On the server, you do not need to specify `password`, but the user will not be able to log in until it has a password (eg, set with [`Accounts.setPassword`](#Accounts-setPassword)). To create an account without a password on the server and still let the user pick their own password, call `createUser` with the `email` option and then call [`Accounts.sendEnrollmentEmail`](#Accounts-sendEnrollmentEmail). This will send the user an email with a link to set their initial password.
By default the `profile` option is added directly to the new user document. To
override this behavior, use [`Accounts.onCreateUser`](#Accounts-onCreateUser).
This function is only used for creating users with passwords. The external
service login flows do not use this function.
Instead of modifying documents in the [`Meteor.users`](#Meteor-users) collection
directly, use these convenience functions which correctly check for case
insensitive duplicates before updates.
<ApiBox name="Accounts.createUserAsync" />
<ApiBox name="Accounts.createUserVerifyingEmail" />
<ApiBox name="Accounts.setUsername" />
<ApiBox name="Accounts.addEmail" />
By default, an email address is added with `{ verified: false }`. Use
[`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail) to send an
email with a link the user can use to verify their email address.
<ApiBox name="Accounts.removeEmail" />
<ApiBox name="Accounts.verifyEmail" />
If the user trying to verify the email has 2FA enabled, this error will be thrown:
- "Email verified, but user not logged in because 2FA is enabled [2fa-enabled]": No longer signing in the user automatically if the user has 2FA enabled.
This function accepts tokens passed into the callback registered with
[`Accounts.onEmailVerificationLink`](#Accounts-onEmailVerificationLink).
<ApiBox name="Accounts.findUserByUsername" />
<ApiBox name="Accounts.findUserByEmail" />
Use the below functions to initiate password changes or resets from the server
or the client.
<ApiBox name="Accounts.changePassword" />
<ApiBox name="Accounts.forgotPassword" />
This triggers a call
to [`Accounts.sendResetPasswordEmail`](#Accounts-sendResetPasswordEmail)
on the server. When the user visits the link in this email, the callback
registered with [`Accounts.onResetPasswordLink`](#Accounts-onResetPasswordLink)
will be called.
If you are using the [`accounts-ui` package](../packages/accounts-ui.md), this is handled
automatically. Otherwise, it is your responsibility to prompt the user for the
new password and call `resetPassword`.
<ApiBox name="Accounts.resetPassword" />
This function accepts tokens passed into the callbacks registered with
[`AccountsClient#onResetPasswordLink`](#Accounts-onResetPasswordLink) and
[`Accounts.onEnrollmentLink`](#Accounts-onEnrollmentLink).
If the user trying to reset the password has 2FA enabled, this error will be thrown:
- "Changed password, but user not logged in because 2FA is enabled [2fa-enabled]": No longer signing in the user automatically if the user has 2FA enabled.
<ApiBox name="Accounts.setPassword" />
<ApiBox name="Accounts.sendResetPasswordEmail" />
When the user visits the link in this email, the callback registered with
[`AccountsClient#onResetPasswordLink`](#Accounts-onResetPasswordLink) will be called.
To customize the contents of the email, see
[`Accounts.emailTemplates`](#Accounts-emailTemplates).
<ApiBox name="Accounts.sendEnrollmentEmail" />
When the user visits the link in this email, the callback registered with
[`Accounts.onEnrollmentLink`](#Accounts-onEnrollmentLink) will be called.
To customize the contents of the email, see
[`Accounts.emailTemplates`](#Accounts-emailTemplates).
<ApiBox name="Accounts.sendVerificationEmail" />
When the user visits the link in this email, the callback registered with
[`Accounts.onEmailVerificationLink`](#Accounts-onEmailVerificationLink) will
be called.
To customize the contents of the email, see
[`Accounts.emailTemplates`](#Accounts-emailTemplates).
<ApiBox name="Accounts.onResetPasswordLink" />
<ApiBox name="Accounts.onEnrollmentLink" />
<ApiBox name="Accounts.onEmailVerificationLink" />
<ApiBox name="Accounts.emailTemplates" />
This is an `Object` with several fields that are used to generate text/html
for the emails sent by `sendResetPasswordEmail`, `sendEnrollmentEmail`,
and `sendVerificationEmail`.
Set the fields of the object by assigning to them:
- `from`: (**required**) A `String` with an [RFC5322](http://tools.ietf.org/html/rfc5322) From
address. By default, the email is sent from `no-reply@example.com`. **If you
want e-mails to send correctly, this should be changed to your own domain
as most e-mail providers will reject mail sent from `example.com`.**
- `siteName`: The public name of your application. Defaults to the DNS name of
the application (eg: `awesome.meteor.com`).
- `headers`: An `Object` for custom email headers as described in
[`Email.send`](./email.md#Email-send).
- `resetPassword`: An `Object` with the fields:
- `from`: A `Function` used to override the `from` address defined
by the `emailTemplates.from` field.
- `subject`: A `Function` that takes a user object and returns
a `String` for the subject line of a reset password email.
- `text`: An optional `Function` that takes a user object and a url, and
returns the body text for a reset password email.
- `html`: An optional `Function` that takes a user object and a
url, and returns the body html for a reset password email.
- `enrollAccount`: Same as `resetPassword`, but for initial password setup for
new accounts.
- `verifyEmail`: Same as `resetPassword`, but for verifying the users email
address.
Example:
```js
import { Accounts } from "meteor/accounts-base";
Accounts.emailTemplates.siteName = "AwesomeSite";
Accounts.emailTemplates.from = "AwesomeSite Admin <accounts@example.com>";
Accounts.emailTemplates.enrollAccount.subject = (user) => {
return `Welcome to Awesome Town, ${user.profile.name}`;
};
Accounts.emailTemplates.enrollAccount.text = (user, url) => {
return (
"You have been selected to participate in building a better future!" +
" To activate your account, simply click the link below:\n\n" +
url
);
};
Accounts.emailTemplates.resetPassword.from = () => {
// Overrides the value set in `Accounts.emailTemplates.from` when resetting
// passwords.
return "AwesomeSite Password Reset <no-reply@example.com>";
};
Accounts.emailTemplates.verifyEmail = {
subject() {
return "Activate your account now!";
},
text(user, url) {
return `Hey ${user}! Verify your e-mail by following this link: ${url}`;
},
};
```
<h3 id="enabling-2fa">Enable 2FA for this package</h3>
You can add 2FA to your login flow by
using the package [accounts-2fa](../packages/accounts-2fa.md).
You can find an example showing how this would look like [here](../packages/accounts-2fa.md#working-with-accounts-password).

107
v3-docs/docs/api/app.md Normal file
View File

@@ -0,0 +1,107 @@
# Mobile Configuration
If your Meteor application targets mobile platforms such as iOS or
Android, you can configure your app's metadata and build process
in a special top-level file called
`mobile-config.js` which is *not* included in your application and is used only
for this configuration.
The code snippet below is an example `mobile-config.js` file. The rest of this
section will explain the specific API commands in greater detail.
```js
// This section sets up some basic app metadata, the entire section is optional.
App.info({
id: 'com.example.matt.uber',
name: 'über',
description: 'Get über power in one button click',
author: 'Matt Development Group',
email: 'contact@example.com',
website: 'http://example.com'
});
// Set up resources such as icons and launch screens.
App.icons({
'iphone_2x': 'icons/icon-60@2x.png',
'iphone_3x': 'icons/icon-60@3x.png',
// More screen sizes and platforms...
});
// Before Meteor 2.6 we had to pass device specific splash screens for iOS, but this behavior was dropped in favor of story board images.
App.launchScreens({
// iOS
// For most cases you will only need to use the 'ios_universal' and 'ios_universal_3x'.
'ios_universal': { src: 'splash/Default@2x.png', srcDarkMode: 'splash/Default@2x~dark.png' }, // (2732x2732) - All @2x devices, if device/mode specific is not declared
'ios_universal_3x': 'splash/Default@3x.png', // (2208x2208) - All @3x devices, if device/mode specific is not declared
// If you still want to use a universal splash, but want to fine-tune for the device mode (landscape, portrait), then use the following keys:
'Default@2x~universal~comany': 'splash/Default@2x~universal~comany.png', // (1278x2732) - All @2x devices in portrait mode.
'Default@2x~universal~comcom': 'splash/Default@2x~universal~comcom.png', // (1334x750) - All @2x devices in landscape (narrow) mode.
'Default@3x~universal~anycom': 'splash/Default@3x~universal~anycom.png', // (2208x1242) - All @3x devices in landscape (wide) mode.
'Default@3x~universal~comany': 'splash/Default@3x~universal~comany.png', // (1242x2208) - All @3x devices in portrait mode.
// However, if you need to fine tune the splash screens for the device idiom (iPhone, iPad, etc).
'Default@2x~iphone~anyany': 'splash/Default@2xiphoneanyany.png', // (1334x1334) - iPhone SE/6s/7/8/XR
'Default@2x~iphone~comany': 'splash/Default@2xiphonecomany.png', // (750x1334) - iPhone SE/6s/7/8/XR - portrait mode
'Default@2x~iphone~comcom': 'splash/Default@2xiphonecomcom.png', // (1334x750) - iPhone SE/6s/7/8/XR - landscape (narrow) mode
'Default@3x~iphone~anyany': 'Default@3xiphoneanyany.png', // (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max
'Default@3x~iphone~anycom': { src: 'splash/Default@3xiphoneanycom.png', srcDarkMode: 'splash/Default@3xiphoneanycom~dark.png' }, // (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - landscape (wide) mode
'Default@3x~iphone~comany': 'Default@3xiphonecomany.png', // (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - portrait mode
'Default@2x~ipad~anyany': 'Default@2xipadanyany.png', // (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9"
'Default@2x~ipad~comany': 'Default@2xipadcomany.png', // (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - portrait mode
// Android
'android_universal': 'splash/android_universal.png', // (320x480)
});
// Set PhoneGap/Cordova preferences.
App.setPreference('BackgroundColor', '0xff0000ff');
App.setPreference('HideKeyboardFormAccessoryBar', true);
App.setPreference('Orientation', 'default');
App.setPreference('Orientation', 'all', 'ios');
// Pass preferences for a particular PhoneGap/Cordova plugin.
App.configurePlugin('com.phonegap.plugins.facebookconnect', {
APP_ID: '1234567890',
API_KEY: 'supersecretapikey'
});
// Add custom tags for a particular PhoneGap/Cordova plugin to the end of the
// generated config.xml. 'Universal Links' is shown as an example here.
App.appendToConfig(`
<universal-links>
<host name="localhost:3000" />
</universal-links>
`);
```
<ApiBox name="App.info" />
<ApiBox name="App.setPreference" />
<ApiBox name="App.accessRule" />
For example this Cordova whitelist syntax:
```xml
<access origin="https://www.google-analytics.com" />
<allow-navigation href="https://example.com" />
```
is equivalent to:
```js
App.accessRule('https://www.google-analytics.com');
App.accessRule('https://example.com', { type: 'navigation' });
```
<ApiBox name="App.configurePlugin" />
> Note: When using `App.configurePlugin` to re-configure a plugin which has been previously configured, the changes may not be reflected without manually clearing the existing Cordova build. To clear the existing Cordova build, remove the `.meteor/local/cordova-build` directory and re-build the application using either `meteor run` or `meteor build`.
<ApiBox name="App.icons" />
<ApiBox name="App.launchScreens" />
<ApiBox name="App.appendToConfig" />
<ApiBox name="App.addResourceFile" />
> Note: The resource file is copied in two steps : from the **src** of your meteor project to the root of the cordova project, then to the **target**

View File

@@ -0,0 +1,23 @@
# Assets
> Currently, it is not possible to import `Assets` as an ES6 module. Any of the `Assets` methods below can simply be called directly in any Meteor server code.
`Assets` allows server code in a Meteor application to access static server
assets, which are located in the `private` subdirectory of an application's
tree. Assets are not processed as source files and are copied directly
into your application's bundle.
<ApiBox name="Assets.getText"/>
<ApiBox name="Assets.getBinary"/>
<ApiBox name="Assets.absoluteFilePath"/>
Static server assets are included by placing them in the application's `private`
subdirectory. For example, if an application's `private` subdirectory includes a
directory called `nested` with a file called `data.txt` inside it, then server
code can read `data.txt` by running:
```js
const data = Assets.getText('nested/data.txt');
```
Note: Packages can only access their own assets. If you need to read the assets of a different package, or of the enclosing app, you need to get a reference to that package's `Assets` object.

View File

@@ -0,0 +1,6 @@
# Blaze
How to use Blaze, Meteor's reactive rendering engine.
This documentation has moved to the [Blaze Community Site](http://blazejs.org/api/templates).

233
v3-docs/docs/api/check.md Normal file
View File

@@ -0,0 +1,233 @@
# Check
Documentation on how to use check, Meteor's type checking library.
The `check` package includes pattern checking functions useful for checking the types and structure
of variables and an [extensible library of patterns](#matchpatterns) to specify which types you are
expecting.
To add `check` (or `Match`) to your application, run this command in your terminal:
```bash
meteor add check
```
<ApiBox name="check" hasCustomExample/>
Meteor methods and publish functions can take arbitrary [EJSON](./EJSON.md) types as arguments, but most
functions expect their arguments to be of a particular type. `check` is a lightweight function for
checking that arguments and other values are of the expected type. For example:
```js [server.js]
import { check } from "meteor/check";
import { Meteor } from "meteor/meteor";
Meteor.publish("chatsInRoom", function (roomId) {
// Make sure `roomId` is a string, not an arbitrary Mongo selector object.
check(roomId, String);
return Chats.find({ room: roomId });
});
Meteor.methods({
addChat(roomId, message) {
check(roomId, String);
check(message, {
text: String,
timestamp: Date,
// Optional, but if present must be an array of strings.
tags: Match.Maybe([String]),
});
// Do something with the message...
},
});
```
If the match fails, `check` throws a `Match.Error` describing how it failed. If
this error gets sent over the wire to the client, it will appear only as
`Meteor.Error(400, 'Match Failed')`. The failure details will be written to the
server logs but not revealed to the client.
<ApiBox name="Match.test" hasCustomExample/>
`Match.test` can be used to identify if a variable has a certain structure.
```js
import { Match } from "meteor/check";
// Will return true for `{ foo: 1, bar: 'hello' }` or similar.
Match.test(value, { foo: Match.Integer, bar: String });
// Will return true if `value` is a string.
Match.test(value, String);
// Will return true if `value` is a string or an array of numbers.
Match.test(value, Match.OneOf(String, [Number]));
```
This can be useful if you have a function that accepts several different kinds
of objects, and you want to determine which was passed in.
## Match Patterns { #matchpatterns }
The following patterns can be used as pattern arguments to
[`check`](#check) and `Match.test`:
### `Match.Any`
Matches any value.
```js
import { Match } from "meteor/check";
// Will return true for any value.
Match.test(value, Match.Any);
```
### `String`, `Number`, `Boolean`, `undefined`, `null`
Matches a primitive of the given type.
```js
import { Match } from "meteor/check";
let result;
// Will return true if `value` is a string.
result = Match.test(value, String);
console.log(result); // true
// Will return true if `value` is a number.
result = Match.test(value, Number);
console.log(result); // true
// Will return true if `value` is a boolean.
result = Match.test(value, Boolean);
console.log(result); // true
```
### `Match.Integer`
Matches a signed 32-bit integer. Doesn't match `Infinity`, `-Infinity`, or `NaN`.
```js
import { Match } from "meteor/check";
let result;
// Will return true if `value` is an integer.
result = Match.test(value, Match.Integer);
console.log(result); // true
```
### `[pattern]` { #arraypattern }
A one-element array matches an array of elements, each of which match
_pattern_. For example, `[Number]` matches a (possibly empty) array of numbers;
`[Match.Any]` matches any array.
```js
import { Match } from "meteor/check";
let result;
// Will return true if `value` is an array of numbers.
result = Match.test(value, [Number]);
console.log(result); // true
```
### `{ key1: pattern1, key2: pattern2, ... }`
Matches an Object with the given keys, with values matching the given patterns.
If any _pattern_ is a `Match.Maybe` or `Match.Optional`, that key does not need to exist
in the object. The value may not contain any keys not listed in the pattern.
The value must be a plain Object with no special prototype.
```js
import { Match } from "meteor/check";
let result;
// Will return true if `value` is an object with keys 'foo' and 'bar'.
result = Match.test(value, { foo: String, bar: Number });
console.log(result); // true
```
### `Match.ObjectIncluding({ key1: pattern1, key2: pattern2, ... })`
Matches an Object with the given keys; the value may also have other keys
with arbitrary values.
```js
import { Match } from "meteor/check";
let result;
// Will return true if `value` is an object with keys 'foo' and 'bar'.
result = Match.test(value, Match.ObjectIncluding({ foo: String, bar: Number }));
console.log(result); // true
```
### `Object`
Matches any plain Object with any keys; equivalent to
`Match.ObjectIncluding({})`.
```js
import { Match } from "meteor/check";
let result;
// Will return true if `value` is an object.
result = Match.test(value, Object);
console.log(result); // true
```
### `Match.Maybe(pattern)` { #maybepattern }
Matches either `undefined`, `null`, or _pattern_. If used in an object, matches only if the key is
not set as opposed to the value being set to `undefined` or `null`. This set of conditions was
chosen because `undefined` arguments to Meteor Methods are converted to `null` when sent over the
wire.
```js
import { Match, check } from "meteor/check";
// In an object
const pattern = { name: Match.Maybe(String) };
check({ name: "something" }, pattern); // OK
check({}, pattern); // OK
check({ name: undefined }, pattern); // Throws an exception
check({ name: null }, pattern); // Throws an exception
// Outside an object
check(null, Match.Maybe(String)); // OK
check(undefined, Match.Maybe(String)); // OK
```
### `Match.Optional(pattern)` { #optionalpattern }
Behaves like `Match.Maybe` except it doesn't accept `null`. If used in an object, the behavior is
identical to `Match.Maybe`.
### `Match.OneOf(pattern1, pattern2, ...)`
Matches any value that matches at least one of the provided patterns.
### Any constructor function (eg, `Date`)
Matches any element that is an instance of that type.
```js
import { Match } from "meteor/check";
let result;
// Will return true if `value` is a Date.
result = Match.test(value, Date);
```
### `Match.Where(condition)`
Calls the function _condition_ with the value as the argument. If _condition_
returns true, this matches. If _condition_ throws a `Match.Error` or returns
false, this fails. If _condition_ throws any other error, that error is thrown
from the call to `check` or `Match.test`. Examples:
```js
import { Match, check } from "meteor/check";
check(buffer, Match.Where(EJSON.isBinary));
const NonEmptyString = Match.Where((x) => {
check(x, String);
return x.length > 0;
});
check(arg, NonEmptyString);
```

File diff suppressed because it is too large Load Diff

166
v3-docs/docs/api/email.md Normal file
View File

@@ -0,0 +1,166 @@
# Email
The `email` package allows sending email from a Meteor app. To use it, add the
package to your project by running in your terminal:
```bash
meteor add email
```
There are two ways on how to setup the package for sending e-mail.
First is to set `MAIL_URL`. The server reads from the `MAIL_URL` environment
variable to determine how to send mail. The `MAIL_URL` should reference an
[SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) server and
use the form `smtp://USERNAME:PASSWORD@HOST:PORT` or
`smtps://USERNAME:PASSWORD@HOST:PORT`. The `smtps://` form (the `s` is for
"secure") should be used if the mail server requires TLS/SSL (and does not use
`STARTTLS`) and is most common on port 465. Connections which start unencrypted
prior to being upgraded to TLS/SSL (using `STARTTLS`) typically use port 587
(and _sometimes_ 25) and should use `smtp://`. For more information see the
[Nodemailer docs](https://nodemailer.com/smtp/)
Second, if you are using a one of the [supported services](https://community.nodemailer.com/smtp/well-known/#supported-services) you can setup the sending options in your app settings like this:
```json
{
"packages": {
"email": {
"service": "Mailgun",
"user": "postmaster@meteor.com",
"password": "superDuperPassword"
}
}
}
```
The package will take care of the rest.
> If you use a supported service the package will try to match to supported service and use the stored settings instead.
> You can force this by switching protocol like `smtp` to the name of the service.
> Though you should only use this as a stop-gap measure and instead set the settings properly.
If neither option is set, `Email.send` outputs the message to standard output
instead.
> Package setting is only available since Email v2.2
<ApiBox name="Email.send" hasCustomExample/>
You must provide the `from` option and at least one of `to`, `cc`, and `bcc`;
all other options are optional.
`Email.send` only works on the server. Here is an example of how a
client could use a server method call to send an email. (In an actual
application, you'd need to be careful to limit the emails that a
client could send, to prevent your server from being used as a relay
by spammers.)
::: code-group
```js [server.js]
import { Meteor } from "meteor/meteor";
import { Email } from "meteor/email";
import { check } from "meteor/check";
// Server: Define a method that the client can call.
Meteor.methods({
sendEmail({ to, from, subject, text }) {
// Make sure that all arguments are strings.
check([to, from, subject, text], [String]);
Email.send({ to, from, subject, text });
},
});
```
```js [client.js]
import { Meteor } from "meteor/meteor";
// Client: Asynchronously send an email.
Meteor.callAsync("sendEmail", {
to: "Alice <alice@example.com>",
from: "bob@example.com",
subject: "Hello from Meteor!",
text: "This is a test of Email.send.",
});
```
:::
<ApiBox name="Email.sendAsync" hasCustomExample/>
`sendAsync` only works on the server. It has the same behavior as `Email.send`, but returns a Promise.
If you defined `Email.customTransport`, the `callAsync` method returns the return value from the `customTransport` method or a Promise, if this method is async.
```js
// Server: Define a method that the client can call.
Meteor.methods({
sendEmail({ to, from, subject, text }) {
// Make sure that all arguments are strings.
check([to, from, subject, text], [String]);
return Email.sendAsync({ to, from, subject, text }).catch((err) => {
// do something with the error
});
},
});
```
<ApiBox name="Email.hookSend" hasCustomExample/>
```js
import { Email } from 'meteor/email'
Email.hookSend(({to}) => {
if (to === 'ceo@company.com') {
console.log(`Let's not send email to the CEO`);
return false;
}
return true;
})
```
`hookSend` is a convenient hook if you want to: prevent sending certain emails,
send emails via your own integration instead of the default one provided by
Meteor, or do something else with the data. This is especially useful
if you want to intercept emails sent by core packages like accounts-password
or other packages where you can't modify the email code.
The hook function will receive an object with the options for Nodemailer.
<ApiBox name="Email.customTransport" hasCustomExample/>
> `Email.customTransport` is only available since Email v2.2
There are scenarios when you have your own transport set up, be it an SDK
for your mailing service or something else. This is where `customTransport`
comes in. If you set this function all sending events will be passed to it
(after `hookSend` is run) with an object of the options passed into `send`
function with addition of `packageSettings` key which will pass in package settings
set in your app settings (if any). It is up to you what you do in that function
as it will override the original sending function.
Here is a simple example with Mailgun:
```javascript
import { Email } from 'meteor/email'
import { Log } from 'meteor/logging'
import Mailgun from 'mailgun-js'
Email.customTransport = (data) => {
// `options.packageSettings` are settings from `Meteor.settings.packages.email`
// The rest of the options are from Email.send options
const mailgun = Mailgun({ apiKey: data.packageSettings.mailgun.privateKey, domain: 'mg.mygreatapp.com' })
// Since the data object that we receive already includes the correct key names for sending
// we can just pass it to the mailgun sending message.
mailgun.messages().send(data, (error, body) => {
if (error) Log.error(error)
if (body) Log.info(body)
})
}
```
> Note that this also overrides the development display of messages in console
> so you might want to differentiate between production and development for
> setting this function.

View File

@@ -0,0 +1,2 @@
<ApiMap />

958
v3-docs/docs/api/meteor.md Normal file
View File

@@ -0,0 +1,958 @@
# Meteor API
Meteor global object has many functions and properties for handling utilities, network and much more.
### Core APIs {#core}
<ApiBox name="Meteor.startup" hasCustomExample/>
On a server, the function will run as soon as the server process is
finished starting. On a client, the function will run as soon as the DOM
is ready. Code wrapped in `Meteor.startup` always runs after all app
files have loaded, so you should put code here if you want to access
shared variables from other files.
The `startup` callbacks are called in the same order as the calls to
`Meteor.startup` were made.
On a client, `startup` callbacks from packages will be called
first, followed by `<body>` templates from your `.html` files,
followed by your application code.
::: code-group
```js [server.js]
import { Meteor } from "meteor/meteor";
import { LinksCollection } from "/imports/api/links";
Meteor.startup(async () => {
// If the Links collection is empty, add some data.
if ((await LinksCollection.find().countAsync()) === 0) {
await LinksCollection.insertAsync({
title: "Do the Tutorial",
url: "https://www.meteor.com/tutorials/react/creating-an-app",
});
}
});
```
```js [client.js]
import React from "react";
import { createRoot } from "react-dom/client";
import { Meteor } from "meteor/meteor";
import { App } from "/imports/ui/App";
// Setup react root
Meteor.startup(() => {
const container = document.getElementById("react-target");
const root = createRoot(container);
root.render(<App />);
});
```
:::
<ApiBox name="Meteor.wrapAsync" />
<ApiBox name="Meteor.defer" />
<ApiBox name="Meteor.absoluteUrl" />
<ApiBox name="Meteor.settings" />
<ApiBox name="Meteor.release" />
<ApiBox name="Meteor.isClient" />
<ApiBox name="Meteor.isServer" />
::: danger
`Meteor.isServer` can be used to limit where code runs, but it does not prevent code from
being sent to the client. Any sensitive code that you dont want served to the client,
such as code containing passwords or authentication mechanisms,
should be kept in the `server` directory.
:::
<ApiBox name="Meteor.isCordova" />
<ApiBox name="Meteor.isDevelopment" />
<ApiBox name="Meteor.isProduction" />
<ApiBox name="Meteor.isModern" />
<ApiBox name="Meteor.isTest" />
<ApiBox name="Meteor.isAppTest" />
<ApiBox name="Meteor.isPackageTest" />
<ApiBox name="Meteor.gitCommitHash" />
### Method APIs {#methods}
Meteor Methods are Remote Procedure Calls (RPCs) are functions defined by `Meteor.methods`
and called by [`Meteor.call`](#Meteor-call).
<ApiBox name="Meteor.methods" hasCustomExample/>
The most basic way to define a method is to provide a function:
::: code-group
```js [server.js]
import { Meteor } from "meteor/meteor";
Meteor.methods({
sum(a, b) {
return a + b;
},
});
```
```js [client.js]
import { Meteor } from "meteor/meteor";
const result = await Meteor.callAsync("sum", 1, 2);
console.log(result); // 3
```
:::
You can use `Meteor.methods` to define multiple methods at once.
You can think of `Meteor.methods` as a way of defining a remote object that is your server API.
A more complete example:
::: code-group
```js [server.js]
import { Meteor } from "meteor/meteor";
import { check } from "meteor/check";
import { LinksCollection } from "/imports/api/links";
Meteor.methods({
async addLink(link) {
check(link, String); // check if the link is a string
// Do stuff...
const linkID = await LinksCollection.insertAsync(link);
if (/* you want to throw an error */) {
throw new Meteor.Error('Something is wrong', "Some details");
}
return linkID;
},
bar() {
// Do other stuff...
return 'baz';
}
});
```
```js [client.js]
import React from "react";
import { Meteor } from "meteor/meteor";
function Component() {
const addLink = () =>
Meteor.callAsync(
"addLink",
"https://www.meteor.com/tutorials/react/creating-an-app"
);
return (
<div>
<button onClick={addLink}>Add Link</button>
</div>
);
}
```
:::
Calling `methods` on the server defines functions that can be called remotely by
clients. They should return an [EJSON](./EJSON)-able value or throw an
exception. Inside your method invocation, `this` is bound to a method
invocation object, which provides the following:
- `isSimulation`: a boolean value, true if this invocation is a stub.
- `unblock`: when called, allows the next method from this client to
begin running.
- `userId`: the id of the current user.
- `setUserId`: a function that associates the current client with a user.
- `connection`: on the server, the [connection](#Meteor-onConnection) this method call was received on.
Calling `methods` on the client defines _stub_ functions associated with
server methods of the same name. You don't have to define a stub for
your method if you don't want to. In that case, method calls are just
like remote procedure calls in other systems, and you'll have to wait
for the results from the server.
If you do define a stub, when a client invokes a server method it will
also run its stub in parallel. On the client, the return value of a
stub is ignored. Stubs are run for their side-effects: they are
intended to _simulate_ the result of what the server's method will do,
but without waiting for the round trip delay. If a stub throws an
exception it will be logged to the console.
You use methods all the time, because the database mutators
([`insert`](./collections#Mongo-Collection-insert), [`update`](./collections#Mongo-Collection-update), [`remove`](./collections#Mongo-Collection-remove)) are implemented
as methods. When you call any of these functions on the client, you're invoking
their stub version that update the local cache, and sending the same write
request to the server. When the server responds, the client updates the local
cache with the writes that actually occurred on the server.
You don't have to put all your method definitions into a single `Meteor.methods`
call; you may call it multiple times, as long as each method has a unique name.
If a client calls a method and is disconnected before it receives a response,
it will re-call the method when it reconnects. This means that a client may
call a method multiple times when it only means to call it once. If this
behavior is problematic for your method, consider attaching a unique ID
to each method call on the client, and checking on the server whether a call
with this ID has already been made. Alternatively, you can use
[`Meteor.apply`](#Meteor-apply) with the noRetry option set to true.
Read more about methods and how to use them in the [Methods](http://guide.meteor.com/methods.html) article in the Meteor Guide.
<ApiBox name="Meteor.isAsyncCall" hasCustomExample/>
This method can be used to determine if the current method invocation is
asynchronous. It returns true if the method is running on the server and came from
an async call(`Meteor.callAsync`)
::: code-group
```js [server.js]
import { Meteor } from "meteor/meteor";
Meteor.methods({
async foo() {
return Meteor.isAsyncCall();
},
});
```
```js [client.js]
import { Meteor } from "meteor/meteor";
const result = await Meteor.callAsync("foo");
console.log(result); // true
Meteor.call("foo", (err, result) => {
console.log(result); // false
});
```
:::
## this.userId {#methods-userId}
The user id is an arbitrary string &mdash; typically the id of the user record
in the database. You can set it with the `setUserId` function. If you're using
the [Meteor accounts system](./accounts.md) then this is handled for you.
```js
import { Meteor } from "meteor/meteor";
Meteor.methods({
foo() {
console.log(this.userId);
},
});
```
## this.setUserId {#methods-setUserId}
Call this function to change the currently logged-in user on the
connection that made this method call. This simply sets the value of
`userId` for future method calls received on this connection. Pass
`null` to log out the connection.
If you are using the [built-in Meteor accounts system](./accounts) then this
should correspond to the `_id` field of a document in the
[`Meteor.users`](./accounts.md#Meteor-user) collection.
`setUserId` is not retroactive. It affects the current method call and
any future method calls on the connection. Any previous method calls on
this connection will still see the value of `userId` that was in effect
when they started.
If you also want to change the logged-in user on the client, then after calling
`setUserId` on the server, call `Meteor.connection.setUserId(userId)` on the
client.
```js
import { Meteor } from "meteor/meteor";
Meteor.methods({
foo() {
this.setUserId("some-id");
},
});
```
## this.connection {#methods-connection}
Access inside a method invocation. The [connection](#Meteor-onConnection) that this method was received on.
null if the method is not associated with a connection,
eg. a server initiated method call. Calls to methods
made from a server method which was in turn initiated from the client share the same
connection.
<ApiBox name="Meteor.Error" />
For example:
::: code-group
```js [server.js]
import { Meteor } from "meteor/meteor";
// on the server, pick a code unique to this error
// the reason field should be a useful debug message
Meteor.methods({
methodName() {
throw new Meteor.Error(
"logged-out",
"The user must be logged in to post a comment."
);
},
});
```
```js [client.js]
import { Meteor } from "meteor/meteor";
// on the client
Meteor.call("methodName", function (error) {
// identify the error
if (error && error.error === "logged-out") {
// show a nice error message
Session.set("errorMessage", "Please log in to post a comment.");
}
});
```
:::
If you want to return an error from a method, throw an exception. Methods can
throw any kind of exception. But `Meteor.Error` is the only kind of error that
a server will send to the client. If a method function throws a different
exception, then it will be mapped to a sanitized version on the
wire. Specifically, if the `sanitizedError` field on the thrown error is set to
a `Meteor.Error`, then that error will be sent to the client. Otherwise, if no
sanitized version is available, the client gets
`Meteor.Error(500, 'Internal server error')`.
<ApiBox name="Meteor.call" hasCustomExample/>
This is how to invoke a method with a sync stub. It will run the method on the server. If a
stub is available, it will also run the stub on the client. (See also
[`Meteor.apply`](#Meteor-apply), which is identical to `Meteor.call` except that
you specify the parameters as an array instead of as separate arguments and you
can specify a few options controlling how the method is executed.)
If you include a callback function as the last argument (which can't be
an argument to the method, since functions aren't serializable), the
method will run asynchronously: it will return nothing in particular and
will not throw an exception. When the method is complete (which may or
may not happen before `Meteor.call` returns), the callback will be
called with two arguments: `error` and `result`. If an error was thrown,
then `error` will be the exception object. Otherwise, `error` will be
`undefined` and the return value (possibly `undefined`) will be in `result`.
```js
// Asynchronous call
Meteor.call('foo', 1, 2, (error, result) => { ... });
```
If you do not pass a callback on the server, the method invocation will
block until the method is complete. It will eventually return the
return value of the method, or it will throw an exception if the method
threw an exception. (Possibly mapped to 500 Server Error if the
exception happened remotely and it was not a `Meteor.Error` exception.)
```js
// Synchronous call
const result = Meteor.call("foo", 1, 2);
```
On the client, if you do not pass a callback and you are not inside a
stub, `call` will return `undefined`, and you will have no way to get
the return value of the method. That is because the client doesn't have
fibers, so there is not actually any way it can block on the remote
execution of a method.
Finally, if you are inside a stub on the client and call another
method, the other method is not executed (no RPC is generated, nothing
"real" happens). If that other method has a stub, that stub stands in
for the method and is executed. The method call's return value is the
return value of the stub function. The client has no problem executing
a stub synchronously, and that is why it's okay for the client to use
the synchronous `Meteor.call` form from inside a method body, as
described earlier.
Meteor tracks the database writes performed by methods, both on the client and
the server, and does not invoke `asyncCallback` until all of the server's writes
replace the stub's writes in the local cache. In some cases, there can be a lag
between the method's return value being available and the writes being visible:
for example, if another method still outstanding wrote to the same document, the
local cache may not be up to date until the other method finishes as well. If
you want to process the method's result as soon as it arrives from the server,
even if the method's writes are not available yet, you can specify an
`onResultReceived` callback to [`Meteor.apply`](#Meteor-apply).
<ApiBox name="Meteor.callAsync" />
`Meteor.callAsync` is just like `Meteor.call`, except that it'll return a promise that you need to solve to get the result.
<ApiBox name="Meteor.apply" />
`Meteor.apply` is just like `Meteor.call`, except that the method arguments are
passed as an array rather than directly as arguments, and you can specify
options about how the client executes the method.
<ApiBox name="Meteor.applyAsync" />
`Meteor.applyAsync` is just like `Meteor.apply`, except it is an async function, and it will consider that the stub is async.
### Publish and subscribe {#pubsub}
These functions control how Meteor servers publish sets of records and
how clients can subscribe to those sets.
<ApiBox name="Meteor.publish" hasCustomExample>
To publish records to clients, call `Meteor.publish` on the server with
two parameters: the name of the record set, and a _publish function_
that Meteor will call each time a client subscribes to the name.
Publish functions can return a
[`Collection.Cursor`](./collections.md#mongo_cursor), in which case Meteor
will publish that cursor's documents to each subscribed client. You can
also return an array of `Collection.Cursor`s, in which case Meteor will
publish all of the cursors.
::: warning
If you return multiple cursors in an array, they currently must all be from
different collections. We hope to lift this restriction in a future release.
:::
</ApiBox>
```js
import { Meteor } from "meteor/meteor";
import { check } from "meteor/check";
import { Rooms } from "/imports/api/Rooms";
import { Messages } from "/imports/api/Messages";
// Server: Publish the `Rooms` collection, minus secret info...
Meteor.publish("rooms", function () {
return Rooms.find(
{},
{
fields: { secretInfo: 0 },
}
);
});
// ...and publish secret info for rooms where the logged-in user is an admin. If
// the client subscribes to both publications, the records are merged together
// into the same documents in the `Rooms` collection. Note that currently object
// values are not recursively merged, so the fields that differ must be top
// level fields.
Meteor.publish("adminSecretInfo", function () {
return Rooms.find(
{ admin: this.userId },
{
fields: { secretInfo: 1 },
}
);
});
// Publish dependent documents and simulate joins.
Meteor.publish("roomAndMessages", function (roomId) {
check(roomId, String);
return [
Rooms.find(
{ _id: roomId },
{
fields: { secretInfo: 0 },
}
),
Messages.find({ roomId }),
];
});
```
Alternatively, a publish function can directly control its published record set
by calling the functions [`added`](#Subscription-added) (to add a new document to the
published record set), [`changed`](#Subscription-changed) (to change or clear some
fields on a document already in the published record set), and
[`removed`](#Subscription-removed) (to remove documents from the published record
set). These methods are provided by `this` in your publish function.
If a publish function does not return a cursor or array of cursors, it is
assumed to be using the low-level `added`/`changed`/`removed` interface, and it
**must also call [`ready`](#Subscription-ready) once the initial record set is
complete**.
::: code-group
```js [collections.js]
import { Mongo } from "meteor/mongo";
export const Rooms = new Mongo.Collection("rooms");
export const SecretData = new Mongo.Collection("messages");
```
```js [server.js]
import { Meteor } from "meteor/meteor";
import { check } from "meteor/check";
import { Rooms, SecretData } from "/imports/api/collections";
// Publish the current size of a collection.
Meteor.publish("countsByRoom", function (roomId) {
check(roomId, String);
let count = 0;
let initializing = true;
// `observeChanges` only returns after the initial `added` callbacks have run.
// Until then, we don't want to send a lot of `changed` messages—hence
// tracking the `initializing` state.
const handle = Messages.find({ roomId }).observeChanges({
added: (id) => {
count += 1;
if (!initializing) {
this.changed("counts", roomId, { count });
}
},
removed: (id) => {
count -= 1;
this.changed("counts", roomId, { count });
},
// We don't care about `changed` events.
});
// Instead, we'll send one `added` message right after `observeChanges` has
// returned, and mark the subscription as ready.
initializing = false;
this.added("counts", roomId, { count });
this.ready();
// Stop observing the cursor when the client unsubscribes. Stopping a
// subscription automatically takes care of sending the client any `removed`
// messages.
this.onStop(() => handle.stop());
});
// Sometimes publish a query, sometimes publish nothing.
Meteor.publish("secretData", function () {
if (this.userId === "superuser") {
return SecretData.find();
} else {
// Declare that no data is being published. If you leave this line out,
// Meteor will never consider the subscription ready because it thinks
// you're using the `added/changed/removed` interface where you have to
// explicitly call `this.ready`.
return [];
}
});
```
```js [client.js]
import { Meteor } from "meteor/meteor";
import { Mongo } from "meteor/mongo";
import { Session } from "meteor/session";
// Declare a collection to hold the count object.
const Counts = new Mongo.Collection("counts");
// Subscribe to the count for the current room.
Tracker.autorun(() => {
Meteor.subscribe("countsByRoom", Session.get("roomId"));
});
// Use the new collection.
const roomCount = Counts.findOne(Session.get("roomId")).count;
console.log(`Current room has ${roomCount} messages.`);
```
::: warning
Meteor will emit a warning message if you call `Meteor.publish` in a
project that includes the `autopublish` package. Your publish function
will still work.
:::
Read more about publications and how to use them in the
[Data Loading](http://guide.meteor.com/data-loading.html) article in the Meteor Guide.
<ApiBox name="Subscription#userId" />
This is constant. However, if the logged-in user changes, the publish
function is rerun with the new value, assuming it didn't throw an error at the previous run.
<ApiBox name="Subscription#added" />
<ApiBox name="Subscription#changed" />
<ApiBox name="Subscription#removed" />
<ApiBox name="Subscription#ready" />
<ApiBox name="Subscription#onStop" />
If you call [`observe`](./collections.md#Mongo-Cursor-observe) or [`observeChanges`](./collections.md#Mongo-Cursor-observeChanges) in your
publish handler, this is the place to stop the observes.
<ApiBox name="Subscription#error" />
<ApiBox name="Subscription#stop" />
<ApiBox name="Subscription#connection" />
<ApiBox name="Meteor.subscribe" hasCustomExample/>
When you subscribe to a record set, it tells the server to send records to the
client. The client stores these records in local [Minimongo collections](./collections.md), with the same name as the `collection`
argument used in the publish handler's [`added`](#Subscription-added),
[`changed`](#Subscription-changed), and [`removed`](#Subscription-removed)
callbacks. Meteor will queue incoming records until you declare the
[`Mongo.Collection`](./collections.md) on the client with the matching
collection name.
```js
// It's okay to subscribe (and possibly receive data) before declaring the
// client collection that will hold it. Assume 'allPlayers' publishes data from
// the server's 'players' collection.
Meteor.subscribe("allPlayers");
// The client queues incoming 'players' records until the collection is created:
const Players = new Mongo.Collection("players");
```
The client will see a document if the document is currently in the published
record set of any of its subscriptions. If multiple publications publish a
document with the same `_id` for the same collection the documents are merged for
the client. If the values of any of the top level fields conflict, the resulting
value will be one of the published values, chosen arbitrarily.
::: warning
Currently, when multiple subscriptions publish the same document _only the top
level fields_ are compared during the merge. This means that if the documents
include different sub-fields of the same top level field, not all of them will
be available on the client. We hope to lift this restriction in a future release.
:::
The `onReady` callback is called with no arguments when the server [marks the subscription as ready](#Subscription-ready). The `onStop` callback is called with
a [`Meteor.Error`](#Meteor-Error) if the subscription fails or is terminated by
the server. If the subscription is stopped by calling `stop` on the subscription
handle or inside the publication, `onStop` is called with no arguments.
`Meteor.subscribe` returns a subscription handle, which is an object with the
following properties:
```ts
import { Meteor } from "meteor/meteor";
const handle = Meteor.subscribe("allPlayers");
handle.ready(); // True when the server has marked the subscription as ready
handle.stop(); // Stop this subscription and unsubscribe from the server
handle.subscriptionId; // The id of the subscription this handle is for.
```
When you run Meteor.subscribe inside of Tracker.autorun, the handles you get will always have the same subscriptionId field.
You can use this to deduplicate subscription handles if you are storing them in some data structure.
If you call `Meteor.subscribe` within a reactive computation,
for example using
[`Tracker.autorun`](./Tracker#Tracker-autorun), the subscription will automatically be
cancelled when the computation is invalidated or stopped; it is not necessary
to call `stop` on
subscriptions made from inside `autorun`. However, if the next iteration
of your run function subscribes to the same record set (same name and
parameters), Meteor is smart enough to skip a wasteful
unsubscribe/resubscribe. For example:
```js
Tracker.autorun(() => {
Meteor.subscribe("chat", { room: Session.get("currentRoom") });
Meteor.subscribe("privateMessages");
});
```
This subscribes you to the chat messages in the current room and to your private
messages. When you change rooms by calling `Session.set('currentRoom',
'newRoom')`, Meteor will subscribe to the new room's chat messages,
unsubscribe from the original room's chat messages, and continue to
stay subscribed to your private messages.
## Publication strategies
> The following features are available from Meteor 2.4 or `ddp-server@2.5.0`
Once you start scaling your application you might want to have more control on how the data from publications is being handled on the client.
There are three publications strategies:
#### SERVER_MERGE
`SERVER_MERGE` is the default strategy. When using this strategy, the server maintains a copy of all data a connection is subscribed to.
This allows us to only send deltas over multiple publications.
#### NO_MERGE_NO_HISTORY
The `NO_MERGE_NO_HISTORY` strategy results in the server sending all publication data directly to the client.
It does not remember what it has previously sent to client and will not trigger removed messages when a subscription is stopped.
This should only be chosen for special use cases like send-and-forget queues.
#### NO_MERGE
`NO_MERGE` is similar to `NO_MERGE_NO_HISTORY` but the server will remember the IDs it has
sent to the client so it can remove them when a subscription is stopped.
This strategy can be used when a collection is only used in a single publication.
When `NO_MERGE` is selected the client will be handling gracefully duplicate events without throwing an exception.
Specifically:
- When we receive an added message for a document that is already present in the client's collection, it will be changed.
- When we receive a change message for a document that is not in the client's collection, it will be added.
- When we receive a removed message for a document that is not in the client's collection, nothing will happen.
You can import the publication strategies from `DDPServer`.
```js
import { DDPServer } from "meteor/ddp-server";
const { SERVER_MERGE, NO_MERGE_NO_HISTORY, NO_MERGE } =
DDPServer.publicationStrategies;
```
You can use the following methods to set or get the publication strategy for publications:
<ApiBox name="setPublicationStrategy" hasCustomExample/>
For the `foo` collection, you can set the `NO_MERGE` strategy as shown:
```js
import { DDPServer } from "meteor/ddp-server";
Meteor.server.setPublicationStrategy(
"foo",
DDPServer.publicationStrategies.NO_MERGE
);
```
<ApiBox name="getPublicationStrategy" />
### Server connections {#connections}
Functions to manage and inspect the network connection between the Meteor client and server.
<ApiBox name="Meteor.status" hasCustomExample/>
```js
import { Meteor } from "meteor/meteor";
const status = Meteor.status();
console.log(status);
// ^^^^
// {
// connected: Boolean,
// status: String,
// retryCount: Number,
// retryTime: Number,
// reason: String,
// }
```
Status object has the following fields:
- `connected` - _*Boolean*_ : True if currently connected to the server. If false, changes and
method invocations will be queued up until the connection is reestablished.
- `status` - _*String*_: Describes the current reconnection status. The possible
values are `connected` (the connection is up and
running), `connecting` (disconnected and trying to open a
new connection), `failed` (permanently failed to connect; e.g., the client
and server support different versions of DDP), `waiting` (failed
to connect and waiting to try to reconnect) and `offline` (user has disconnected the connection).
- `retryCount` - _*Number*_: The number of times the client has tried to reconnect since the
connection was lost. 0 when connected.
- `retryTime` - _*Number or undefined*_: The estimated time of the next reconnection attempt. To turn this
into an interval until the next reconnection, This key will be set only when `status` is `waiting`.
You canuse this snippet:
```js
retryTime - new Date().getTime();
```
- `reason` - _*String or undefined*_: If `status` is `failed`, a description of why the connection failed.
<ApiBox name="Meteor.reconnect" />
<ApiBox name="Meteor.disconnect" />
Call this method to disconnect from the server and stop all
live data updates. While the client is disconnected it will not receive
updates to collections, method calls will be queued until the
connection is reestablished, and hot code push will be disabled.
Call [Meteor.reconnect](#Meteor-reconnect) to reestablish the connection
and resume data transfer.
This can be used to save battery on mobile devices when real time
updates are not required.
<ApiBox name="Meteor.onConnection" hasCustomExample/>
```js
import { Meteor } from "meteor/meteor";
const handle = Meteor.onConnection((connection) => {
console.log(connection);
// ^^^^^^^^^^^
// {
// id: String,
// close: Function,
// onClose: Function,
// clientAddress: String,
// httpHeaders: Object,
// }
});
handle.stop(); // Unregister the callback
```
`onConnection` returns an object with a single method `stop`. Calling
`stop` unregisters the callback, so that this callback will no longer
be called on new connections.
The callback is called with a single argument, the server-side
`connection` representing the connection from the client. This object
contains the following fields:
- `id` - _*String*_: A globally unique id for this connection.
- `close` - _*Function*_: Close this DDP connection. The client is free to reconnect, but will
receive a different connection with a new `id` if it does.
- `onClose` - _*Function*_: Register a callback to be called when the connection is closed.
If the connection is already closed, the callback will be called immediately.
- `clientAddress` - _*String*_: The IP address of the client in dotted form (such as `127.0.0.1`). If you're running your Meteor server behind a proxy (so that clients
are connecting to the proxy instead of to your server directly),
you'll need to set the `HTTP_FORWARDED_COUNT` environment variable
for the correct IP address to be reported by `clientAddress`.
Set `HTTP_FORWARDED_COUNT` to an integer representing the number of
proxies in front of your server. For example, you'd set it to `1`
when your server was behind one proxy.
- `httpHeaders` - _*Object*_: When the connection came in over an HTTP transport (such as with
Meteor's default SockJS implementation), this field contains
whitelisted HTTP headers.
Cookies are deliberately excluded from the headers as they are a
security risk for this transport. For details and alternatives, see
the [SockJS documentation](https://github.com/sockjs/sockjs-node#authorisation).
> Currently when a client reconnects to the server (such as after
> temporarily losing its Internet connection), it will get a new
> connection each time. The `onConnection` callbacks will be called
> again, and the new connection will have a new connection `id`.
> In the future, when client reconnection is fully implemented,
> reconnecting from the client will reconnect to the same connection on
> the server: the `onConnection` callback won't be called for that
> connection again, and the connection will still have the same
> connection `id`.
<ApiBox name="DDP.connect" hasCustomExample/>
```js
import { DDP } from "meteor/ddp-client";
import { Mongo } from "meteor/mongo";
import { Meteor } from "meteor/meteor";
const options = {...};
const otherServer = DDP.connect("http://example.com", options);
otherServer.call("foo.from.other.server", 1, 2, function (err, result) {
// ...
});
Metepr.call("foo.from.this.server", 1, 2, function (err, result) {
// ...
});
const remoteColl = new Mongo.Collection("collectionName", { connection: otherServer });
remoteColl.find(...);
```
To call methods on another Meteor application or subscribe to its data
sets, call `DDP.connect` with the URL of the application.
`DDP.connect` returns an object which provides:
- `subscribe` -
Subscribe to a record set. See
[Meteor.subscribe](#Meteor-subscribe).
- `call` -
Invoke a method. See [Meteor.call](#Meteor-call).
- `apply` -
Invoke a method with an argument array. See
[Meteor.apply](#Meteor-apply).
- `methods` -
Define client-only stubs for methods defined on the remote server. See
[Meteor.methods](#Meteor-methods).
- `status` -
Get the current connection status. See
[Meteor.status](#Meteor-status).
- `reconnect` -
See [Meteor.reconnect](#Meteor-reconnect).
- `disconnect` -
See [Meteor.disconnect](#Meteor-disconnect).
By default, clients open a connection to the server from which they're loaded.
When you call `Meteor.subscribe`, `Meteor.status`, `Meteor.call`, and
`Meteor.apply`, you are using a connection back to that default
server.
<ApiBox name="DDP.onReconnect" />
## Timers { #timers }
Meteor uses global environment variables
to keep track of things like the current request's user. To make sure
these variables have the right values, you need to use
`Meteor.setTimeout` instead of `setTimeout` and `Meteor.setInterval`
instead of `setInterval`.
These functions work just like their native JavaScript equivalents.
If you call the native function, you'll get an error stating that Meteor
code must always run within a Fiber, and advising to use
`Meteor.bindEnvironment`.
<ApiBox name="Meteor.setTimeout" />
Returns a handle that can be used by `Meteor.clearTimeout`.
<ApiBox name="Meteor.setInterval" />
Returns a handle that can be used by `Meteor.clearInterval`.
<ApiBox name="Meteor.clearTimeout" />
<ApiBox name="Meteor.clearInterval" />
## Enviroment variables {#envs}
teor runs most app code within Fibers, which allows keeping track of the context a function is running in. `Meteor.EnvironmentVariable` works with `Meteor.bindEnvironment`, promises, and many other Meteor API's to preserve the context in async code. Some examples of how it is used in Meteor are to store the current user in methods, and record which arguments have been checked when using `audit-argument-checks`.
```js
import { Meteor } from "meteor/meteor";
const currentRequest = new Meteor.EnvironmentVariable();
function log(message) {
const requestId = currentRequest.get() || "None";
console.log(`[${requestId}]`, message);
}
currentRequest.withValue("12345", () => {
log("Handling request"); // Logs: [12345] Handling request
});
```
<ApiBox name="Meteor.EnvironmentVariable" />
<ApiBox name="Meteor.EnvironmentVariableAsync" />
<ApiBox name="Meteor.EnvironmentVariable.get" />
<ApiBox name="Meteor.EnvironmentVariable.withValue" />
<ApiBox name="Meteor.bindEnvironment" />

482
v3-docs/docs/api/package.md Normal file
View File

@@ -0,0 +1,482 @@
# Package.js
A package is a directory containing a package.js file, which
contains roughly three major sections: a basic description, a package
definition, and a test definition. By default, the directory name is the name of
the package.
The `package.js` file below is an example of how to use the packaging API. The
rest of this section will explain the specific API commands in greater detail.
```js
// Information about this package:
Package.describe({
// Short two-sentence summary
summary: 'What this does',
// Version number
version: '1.0.0',
// Optional, default is package directory name
name: 'username:package-name',
// Optional GitHub URL to your source repository
git: 'https://github.com/something/something.git'
});
// This defines your actual package:
Package.onUse((api) => {
// If no version is specified for an `api.use` dependency, use the one defined
// in Meteor 1.12.1.
api.versionsFrom('1.12.1');
// Use the `underscore` package, but only on the server. Version not
// specified, so it will be as of Meteor 1.12.1.
api.use('underscore', 'server');
// Use `ostrio:flow-router-extra`, version 3.9.0 or newer.
api.use('ostrio:flow-router-extra@3.9.0');
// Give users of this package access to active-route's JavaScript helpers.
api.imply('zimme:active-route@2.3.2')
// Export the object `Email` to packages or apps that use this package.
api.export('Email', 'server');
// Specify the source code for the package.
api.addFiles('email.js', 'server');
// When using `ecmascript` or `modules` packages, you can use this instead of
// `api.export` and `api.addFiles`.
api.mainModule('email.js', 'server');
});
// This defines the tests for the package:
Package.onTest((api) => {
// Sets up a dependency on this package.
api.use('username:package-name');
// Use the Mocha test framework.
api.use('practicalmeteor:mocha@2.4.5_6');
// Specify the source code for the package tests.
api.addFiles('email_tests.js', 'server');
});
// This lets you use npm packages in your package:
Npm.depends({
simplesmtp: '0.3.10',
'stream-buffers': '0.2.5'
});
```
`api.mainModule` is documented in the [modules](../packages/modules.md#modular-application-structure) section.
Build plugins are created with
[`Package.registerBuildPlugin`](#PackageNamespace-registerBuildPlugin). See the
coffeescript package for an example. Build plugins are fully-fledged Meteor
programs in their own right and have their own namespace, package dependencies,
source files and npm requirements.
> You can use local packages to define custom build plugins
for your app, with one caveat. In published packages, build plugins are already
bundled with their transitive dependencies. So if you want a dependency of a
build plugin to be satisfied by a local package, you must use a local copy of
the package that defines the plugin (even if you make no changes to that
package) so that Meteor will pick up the local dependency.
> In a lifecycle of a package there might come time to end the development for various reasons, or
it gets superseded. In either case Meteor allows you to easily notify the users of the package by
setting the deprecated flag to true: `deprecated: true` in the package description. In addition, you
replace it with a string that tells the users where to find replacement or what to do.
Provide basic package information with `Package.describe(options)`. To publish a
package, you must define `summary` and `version`.
<ApiBox name="PackageNamespace#describe" instanceName="api"/>
Define dependencies and expose package methods with the
`Package.onUse` handler. This section lets you define what packages your package
depends on, what packages are implied by your package, and what object your
package is exported to.
<ApiBox name="PackageNamespace#onUse" instanceName="api"/>
<ApiBox name="PackageAPI#versionsFrom" instanceName="api"/>
> Choose Meteor versions carefully. First determine the minimum version of Meteor you need for the API you use in your package.
This should be based on specific needs of your package like needed the *Async calls, which would require minimum version to be
at least 2.8. Another example are where packages had a major version bump, for example this has happened with the accounts packages
in Meteor 2.3. If you want to be backward and forward compatible it is good to include Meteor version before 2.3 and then 2.3.6 in the array.
A general recommendation for most compatibility for accounts packages (unless you need API that was affected in Meteor 2.3) is to have the following
array in `versionsFrom`: `['1.12.1', '2.3.6', '2.8.1']`, this gives us the widest range. For general packages you can leave out version `2.3.6`.
If you want the widest compatibility range it is recommended that the lowest be `1.12.1` and that you also include another
version near the current version of Meteor.
<ApiBox name="PackageAPI#use" instanceName="api"/>
<ApiBox name="PackageAPI#imply" instanceName="api"/>
<ApiBox name="PackageAPI#export" instanceName="api"/>
<ApiBox name="PackageAPI#addFiles" instanceName="api"/>
<ApiBox name="PackageAPI#addAssets" instanceName="api"/>
Set up your tests with the `Package.onTest` handler, which has an interface
that's parallel to that of the `onUse` handler. The tests will need to depend on
the package that you have just created. For example, if your package is the
`email` package, you have to call `api.use('email')` in order to test the
package.
If you used `meteor create` to set up your package, Meteor will create the
required scaffolding in `package.js`, and you'll only need to add unit test code
in the `_test.js` file that was created.
<ApiBox name="PackageNamespace#onTest" instanceName="api"/>
Meteor packages can include NPM packages and Cordova plugins by using
`Npm.depends` and `Cordova.depends` in the `package.js` file.
<ApiBox name="PackageNpm#depends" instanceName="api"/>
<ApiBox name="Npm.require" />
<ApiBox name="PackageCordova#depends" instanceName="api"/>
<ApiBox name="PackageNamespace#registerBuildPlugin" instanceName="api"/>
## Options
In some cases we need to offer options in packages where these options are not going to change in runtime.
We prefer to have these options defined in a configuration file instead of using JS code to call specific functions to define options in runtime.
For example, in `quave:collections` package you can force collections to be available only in the server like this:
```json
"packages": {
"quave:collections": {
"isServerOnly": true
}
}
```
We encourage every package author to follow this standard to offer options:
1. Use the official Meteor `settings` file
2. Inside the `settings` file read from a `Meteor`.`packages`.`<package name>`.`<your option name>`
> If it needs to be available in the client follow the same structure inside the `public` key.
You can use [quave:settings](https://github.com/quavedev/settings) package to read options in the format above already merging the private and public options.
This way we avoid having to call a specific code before another specific code in a package as the setting is stored in the settings, and the package can load it when necessary instead of relying on a specific order of calls from the developer in the app code.
> We've started to adopt this standard also in core packages on Meteor 1.10.2.
<ApiBox name="Plugin.registerSourceHandler" true />
## Build Plugins API {#build-plugin-api}
Meteor packages can provide build plugins - programs that integrate with the
build tool Isobuild used to compile and bundle your application.
Starting with Meteor 1.2, the API used to plug into the build process is called
"Build Plugins". There are 3 phases when a package's plugin can run: linting,
compilation and minification. Here is an overview of operations Isobuild
performs on the application and packages source:
1. Gather source files from the app folder or read `package.js` file for a
package.
2. Lint all source files and print the linting warnings.
3. Compile the source files like CoffeeScript, ES2015, Less, or Templates to plain
JavaScript and CSS.
4. Link the JavaScript files: wrap them into closures and provide necessary
package imports.
5. Minify JavaScript and CSS files. Can also include concatenation of all files.
Build plugins fill the phases 2, 3 and 5.
Usually build plugins implement a class that is given a list of files to
process. Commonly, such files have the following methods:
- `getContentsAsBuffer` - Returns the full contents of the file as a buffer.
- `getContentsAsString` - Returns the full contents of the file as a string.
- `getPackageName` - Returns the name of the package or `null` if the file is not in a package.
- `getPathInPackage` - Returns the relative path of file to the package or app root directory. The returned path always uses forward slashes.
- `getSourceHash` - Returns a hash string for the file that can be used to implement caching.
- `getArch` - Returns the architecture that is targeted while processing this file.
- `getBasename` - Returns the filename of the file.
- `getDirname` - Returns the directory path relative to the package or app root. The returned path always uses forward slashes.
- `error` - Call this method to raise a compilation or linting error for the file.
## Linters {#build-plugin-linters}
Linters are programs that check the code for undeclared variables or find code
that doesn't correspond to certain style guidelines. Some of the popular
examples of linters are [JSHint](http://jshint.com/about/) and
[ESLint](http://eslint.org/). Some of the non-JavaScript linter examples include
[CoffeeLint](https://github.com/clutchski/coffeelint) for CoffeeScript and
[CSSLint](http://csslint.net/) for CSS.
To register a linter build plugin in your package, you need to do a couple of
things in your `package.js`:
- depend on the `isobuild:linter-plugin@1.0.0` package
- register a build plugin: `Package.registerBuildPlugin({ name, sources, ... });`
(see [docs](#PackageNamespace-registerBuildPlugin))
In your build plugin sources, register a Linter Plugin: provide details such as
a name, list of extensions and filenames the plugin will handle and a factory
function that returns an instance of a linter class. Example:
```js
Plugin.registerLinter({
extensions: ['js'],
filenames: ['.linterrc']
}, () => new MyLinter);
```
In this example, we register a linter that runs on all `js` files and also reads
a file named `.linterrc` to get a configuration.
The `MyLinter` class should now implement the `processFilesForPackage`
method. The method should accept two arguments: a list of files and an options
object.
```js
class MyLinter {
processFilesForPackage(files, options) {
files.forEach((file) => {
// Lint the file.
const lint = lintFile(file.getContentsAsString());
if (lint) {
// If there are linting errors, output them.
const { message, line, column } = lint;
file.error({ message, line, column });
}
});
}
}
```
The globals are passed in the options object so that the linters can omit the
warnings about the package imports that look like global variables.
Each file in the list is an object that has all the methods provided by all
build plugins, described above.
See an example of a linting plugin implemented in Core: [jshint](https://github.com/meteor/meteor/tree/devel/packages/jshint).
## Compilers {#build-plugin-compilers}
Compilers are programs that take the source files and output JavaScript or
CSS. They also can output parts of HTML that is added to the `<head>` tag and
static assets. Examples for the compiler plugins are: CoffeeScript, Babel.js,
JSX compilers, Pug templating compiler and others.
To register a compiler plugin in your package, you need to do the following in
your `package.js` file:
- depend on the `isobuild:compiler-plugin@1.0.0` package
- register a build plugin: `Package.registerBuildPlugin({ name, sources, ... });`
(see [docs](#PackageNamespace-registerBuildPlugin))
In your build plugin source, register a Compiler Plugin: similar to other types
of build plugins, provide the details, extensions and filenames and a factory
function that returns an instance of the compiler. Ex.:
```js
Plugin.registerCompiler({
extensions: ['pug', 'tpl.pug'],
filenames: []
}, () => new PugCompiler);
```
The compiler class must implement the `processFilesForTarget` method that is
given the source files for a target (server or client part of the package/app).
```js
class PugCompiler {
processFilesForTarget(files) {
files.forEach((file) => {
// Process and add the output.
const output = compilePug(file.getContentsAsString());
file.addJavaScript({
data: output,
path: `${file.getPathInPackage()}.js`
});
});
}
}
```
Besides the common methods available on the input files' class, the following
methods are available:
- `getExtension` - Returns the extension that matched the compiler plugin. The
longest prefix is preferred.
- `getDeclaredExports` - Returns a list of symbols declared as exports in this
target. The result of `api.export('symbol')` calls in target's control file
such as package.js.
- `getDisplayPath` Returns a relative path that can be used to form error
messages or other display properties. Can be used as an input to a source map.
- `addStylesheet` - Web targets only. Add a stylesheet to the document. Not
available for linter build plugins.
- `addJavaScript` - Add JavaScript code. The code added will only see the
namespaces imported by this package as runtime dependencies using
['api.use'](#PackageAPI-use). If the file being compiled was added with the
bare flag, the resulting JavaScript won't be wrapped in a closure.
- `addAsset` - Add a file to serve as-is to the browser or to include on the
browser, depending on the target. On the web, it will be served at the exact
path requested. For server targets, it can be retrieved using
`Assets.getText` or `Assets.getBinary`.
- `addHtml` - Works in web targets only. Add markup to the `head` or `body`
section of the document.
- `hmrAvailable` - Returns true if the file can be updated with HMR. Among other things,
it checks if HMR supports the current architecture and build mode, and that the unibuild
uses the `hot-module-replacement` package. There are rare situations where `hmrAvailable`
returns true, but when more information is available later in the build process Meteor
decides the file can not be updated with HMR.
- `readAndWatchFileWithHash` - Accepts an absolute path, and returns { contents, hash }
Makes sure Meteor watches the file so any changes to it will trigger a rebuild
Meteor implements a couple of compilers as Core packages, good examples would be
the
[Blaze templating](https://github.com/meteor/meteor/tree/devel/packages/templating)
package and the
[ecmascript](https://github.com/meteor/meteor/tree/devel/packages/ecmascript)
package (compiles ES2015+ to JavaScript that can run in the browsers).
## Minifiers {#build-plugin-minifiers}
Minifiers run last after the sources has been compiled and JavaScript code has
been linked. Minifiers are only ran for the client programs (`web.browser` and
`web.cordova`).
There are two types of minifiers one can add: a minifier processing JavaScript
(registered extensions: `['js']`) and a minifier processing CSS (extensions:
`['css']`).
To register a minifier plugin in your package, add the following in your
`package.js` file:
- depend on `isobuild:minifier-plugin@1.0.0` package
- register a build plugin: `Package.registerBuildPlugin({ name, sources, ... });`
(see [docs](#PackageNamespace-registerBuildPlugin))
In your build plugin source, register a Minifier Plugin. Similar to Linter and
Compiler plugin, specify the interested extensions (`css` or `js`). The factory
function returns an instance of the minifier class.
```js
Plugin.registerMinifier({
extensions: ['js']
}, () => new UglifyJsMinifier);
```
The minifier class must implement the method `processFilesForBundle`. The first
argument is a list of processed files and the options object specifies if the
minifier is ran in production mode or development mode.
::: info
This method can be asynchronous. If it returns a Promise, the build process will
wait for it to resolve before continuing.
:::
```js
class UglifyJsMinifier {
processFilesForBundle(files, options) {
const { minifyMode } = options;
if (minifyMode === 'development') {
// Don't minify in development.
file.forEach((file) => {
file.addJavaScript({
data: file.getContentsAsBuffer(),
sourceMap: file.getSourceMap(),
path: file.getPathInBundle()
});
});
return;
}
// Minify in production.
files.forEach((file) => {
file.addJavaScript({
data: uglifyjs.minify(file.getContentsAsBuffer()),
path: file.getPathInBundle()
});
});
}
}
```
In this example, we re-add the same files in the development mode to avoid
unnecessary work and then we minify the files in production mode.
Besides the common input files' methods, these methods are available:
- `getPathInBundle` - returns a path of the processed file in the bundle.
- `getSourcePath` - returns absolute path of the input file if available, or null.
- `getSourceMap` - returns the source-map for the processed file if there is such.
- `addJavaScript` - same as compilers
- `addStylesheet` - same as compilers
- `readAndWatchFileWithHash` - only available for css minifiers. Same as compilers.
Right now, Meteor Core ships with the `standard-minifiers` package that can be
replaced with a custom one. The
[source](https://github.com/meteor/meteor/tree/devel/packages/standard-minifiers)
of the package is a good example how to build your own minification plugin.
In development builds, minifiers must meet these requirements to not prevent hot module replacement:
- Call `addJavasScript` once for each file to add the file's contents
- The contents of the files are not modified
In the future Meteor will allow minifiers to concatenate or modify files in development without affected hot module replacement.
## Caching {#build-plugin-caching}
Since the API allows build plugins to process multiple files at once, we encourage package authors to implement at least some in-memory caching for their plugins. Using the `getSourceHash` function for linters and compilers will allow quick incremental recompilations if the file is not reprocessed even when the contents didn't change.
For the fast rebuilds between the Isobuild process runs, plugins can implement on-disk caching. If a plugin implements the `setDiskCacheDirectory` method, it will be called from time to time with a new path on disk where the plugin can write its offline cache. The folder is correctly reset when the plugin is rebuilt or cache should be invalidated for any reason (for example, picked package versions set has changed).
### Caching Compiler {#build-plugin-caching-compiler}
There is a core package called `caching-compiler` that implements most of the common logic of keeping both in-memory and on-disk caches. The easiest way to implement caching correctly is to subclass the `CachingCompiler` or `MultiFileCachingCompiler` class from this package in your build plugin. `CachingCompiler` is for compilers that consider each file completely independently; `MultiFileCachingCompiler` is for compilers that allow files to reference each other. To get this class in your plugin namespace, add a dependency to the plugin definition:
```js
Package.registerBuildPlugin({
name: 'compileGG',
use: ['caching-compiler@1.0.0'],
sources: ['plugin/compile-gg.js']
});
```
## Accessing File System {#build-plugin-file-system}
Since the build plugins run as part of the Meteor tool, they follow the same file-system access convention - all file system paths always look like a Unix path: using forward slashes and having a root at '/', even on Windows. For example: paths `/usr/bin/program` and `/C/Program Files/Program/program.exe` are valid paths, and `C:\Program Files\Program\program.exe` is not.
So whenever you get a path in your build plugin implementation, via `getPathInPackage` or in an argument of the `setDiskCacheDirectory` method, the path will be a Unix path.
Now, on running on Windows, the usual node modules `fs` and `path` expect to get a DOS path. To assist you to write correct code, the `Plugin` symbol provides its own versions of `fs` and `path` that you can use instead (note that all methods on `fs` are fiberized and sync versions prefer using Fibers rather than freezing the whole event loop).
Also `Plugin` provides helper functions `convertToStandardPath` and `convertToOSPath` to convert to a Unix path or to the path expected by the node libraries regardless of the path origin.
Example:
```js
// On Windows
const fs = Plugin.fs;
const path = Plugin.path;
const filePath = path.join('/C/Program Files', 'Program/file.txt');
console.log(filePath); // Prints '/C/Program Files/Program/file.txt'
fs.writeFileSync(filePath, 'Hello.'); // Writes to 'C:\Program Files\Program\file.txt'
console.log(Plugin.convertToOsPath(filePath)); // Prints 'C:\Program Files\Program\file.txt'
```
## Isobuild Feature Packages {#isobuild-features}
Starting with Meteor 1.2, packages can declare that they need a version of the Meteor tool whose Isobuild build system supports a certain feature. For example, packages must write `api.use('isobuild:compiler-plugin@1.0.0')` in order to call `Plugin.registerCompiler`. This means that a package can transition from the old `registerSourceHandler` API to `registerCompiler` and Version Solver will properly prevent the `registerCompiler` version from being chosen by older tools that don't know how to handle it.
This is the known Isobuild feature "packages" sorted by the first release of Meteor which supports them.
### Introduced in Meteor 1.2s {#isobuild-features-1.2}
- `compiler-plugin@1.0.0`: Allows use of `Plugin.registerCompiler`.
- `linter-plugin@1.0.0`: Allows use of `Plugin.registerLinter`.
- `minifier-plugin@1.0.0`: Allows use of `Plugin.registerMinifier`.
- `isopack-2@1.0.0`: This package is published only in `isopack-2` format and won't work in versions
of Meteor that don't support that format.
- `prod-only@1.0.0`: Allows use of the `prodOnly` flag in `Package.describe`.
- `isobuild:cordova@5.4.0`: This package depends on a specific version of Cordova, most likely as a result of the Cordova plugins it depends on.

View File

@@ -0,0 +1,152 @@
[//]: # (Do not edit this file by hand.)
[//]: # (This is a generated file.)
[//]: # (If you want to change something in this file)
[//]: # (go to meteor/docs/generators/packages-listing)
# Core Packages
### [blaze](https://github.com/meteor/blaze) {#blaze}
### [react-packages](https://github.com/meteor/react-packages) {#react-packages}
### [accounts-2fa](https://github.com/meteor/meteor/tree/devel/packages/accounts-2fa) {#accounts-2fa}
### [accounts-base](https://github.com/meteor/meteor/tree/devel/packages/accounts-base) {#accounts-base}
### [accounts-facebook](https://github.com/meteor/meteor/tree/devel/packages/accounts-facebook) {#accounts-facebook}
### [accounts-github](https://github.com/meteor/meteor/tree/devel/packages/accounts-github) {#accounts-github}
### [accounts-google](https://github.com/meteor/meteor/tree/devel/packages/accounts-google) {#accounts-google}
### [accounts-meetup](https://github.com/meteor/meteor/tree/devel/packages/accounts-meetup) {#accounts-meetup}
### [accounts-meteor-developer](https://github.com/meteor/meteor/tree/devel/packages/accounts-meteor-developer) {#accounts-meteor-developer}
### [accounts-oauth](https://github.com/meteor/meteor/tree/devel/packages/accounts-oauth) {#accounts-oauth}
### [accounts-password](https://github.com/meteor/meteor/tree/devel/packages/accounts-password) {#accounts-password}
### [accounts-passwordless](https://github.com/meteor/meteor/tree/devel/packages/accounts-passwordless) {#accounts-passwordless}
### [accounts-twitter](https://github.com/meteor/meteor/tree/devel/packages/accounts-twitter) {#accounts-twitter}
### [accounts-ui](https://github.com/meteor/meteor/tree/devel/packages/accounts-ui) {#accounts-ui}
### [accounts-ui-unstyled](https://github.com/meteor/meteor/tree/devel/packages/accounts-ui-unstyled) {#accounts-ui-unstyled}
### [accounts-weibo](https://github.com/meteor/meteor/tree/devel/packages/accounts-weibo) {#accounts-weibo}
### [allow-deny](https://github.com/meteor/meteor/tree/devel/packages/allow-deny) {#allow-deny}
### [audit-argument-checks](https://github.com/meteor/meteor/tree/devel/packages/audit-argument-checks) {#audit-argument-checks}
### [autopublish](https://github.com/meteor/meteor/tree/devel/packages/autopublish) {#autopublish}
### [autoupdate](https://github.com/meteor/meteor/tree/devel/packages/autoupdate) {#autoupdate}
### [babel-compiler](https://github.com/meteor/meteor/tree/devel/packages/babel-compiler) {#babel-compiler}
### [babel-runtime](https://github.com/meteor/meteor/tree/devel/packages/babel-runtime) {#babel-runtime}
### [base64](https://github.com/meteor/meteor/tree/devel/packages/base64) {#base64}
### [binary-heap](https://github.com/meteor/meteor/tree/devel/packages/binary-heap) {#binary-heap}
### [boilerplate-generator](https://github.com/meteor/meteor/tree/devel/packages/boilerplate-generator) {#boilerplate-generator}
### [boilerplate-generator-tests](https://github.com/meteor/meteor/tree/devel/packages/boilerplate-generator-tests) {#boilerplate-generator-tests}
### [browser-policy](https://github.com/meteor/meteor/tree/devel/packages/browser-policy) {#browser-policy}
### [browser-policy-common](https://github.com/meteor/meteor/tree/devel/packages/browser-policy-common) {#browser-policy-common}
### [browser-policy-content](https://github.com/meteor/meteor/tree/devel/packages/browser-policy-content) {#browser-policy-content}
### [browser-policy-framing](https://github.com/meteor/meteor/tree/devel/packages/browser-policy-framing) {#browser-policy-framing}
### [caching-compiler](https://github.com/meteor/meteor/tree/devel/packages/caching-compiler) {#caching-compiler}
### [callback-hook](https://github.com/meteor/meteor/tree/devel/packages/callback-hook) {#callback-hook}
### [check](https://github.com/meteor/meteor/tree/devel/packages/check) {#check}
### [constraint-solver](https://github.com/meteor/meteor/tree/devel/packages/constraint-solver) {#constraint-solver}
### [core-runtime](https://github.com/meteor/meteor/tree/devel/packages/core-runtime) {#core-runtime}
### [crosswalk](https://github.com/meteor/meteor/tree/devel/packages/crosswalk) {#crosswalk}
### [ddp](https://github.com/meteor/meteor/tree/devel/packages/ddp) {#ddp}
### [ddp-client](https://github.com/meteor/meteor/tree/devel/packages/ddp-client) {#ddp-client}
### [ddp-common](https://github.com/meteor/meteor/tree/devel/packages/ddp-common) {#ddp-common}
### [ddp-rate-limiter](https://github.com/meteor/meteor/tree/devel/packages/ddp-rate-limiter) {#ddp-rate-limiter}
### [ddp-server](https://github.com/meteor/meteor/tree/devel/packages/ddp-server) {#ddp-server}
### [deprecated](https://github.com/meteor/meteor/tree/devel/packages/deprecated) {#deprecated}
### [dev-error-overlay](https://github.com/meteor/meteor/tree/devel/packages/dev-error-overlay) {#dev-error-overlay}
### [diff-sequence](https://github.com/meteor/meteor/tree/devel/packages/diff-sequence) {#diff-sequence}
### [disable-oplog](https://github.com/meteor/meteor/tree/devel/packages/disable-oplog) {#disable-oplog}
### [dynamic-import](https://github.com/meteor/meteor/tree/devel/packages/dynamic-import) {#dynamic-import}
### [ecmascript](https://github.com/meteor/meteor/tree/devel/packages/ecmascript) {#ecmascript}
### [ecmascript-runtime](https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime) {#ecmascript-runtime}
### [ecmascript-runtime-client](https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime-client) {#ecmascript-runtime-client}
### [ecmascript-runtime-server](https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime-server) {#ecmascript-runtime-server}
### [ejson](https://github.com/meteor/meteor/tree/devel/packages/ejson) {#ejson}
### [email](https://github.com/meteor/meteor/tree/devel/packages/email) {#email}
### [es5-shim](https://github.com/meteor/meteor/tree/devel/packages/es5-shim) {#es5-shim}
### [facebook-config-ui](https://github.com/meteor/meteor/tree/devel/packages/facebook-config-ui) {#facebook-config-ui}
### [facebook-oauth](https://github.com/meteor/meteor/tree/devel/packages/facebook-oauth) {#facebook-oauth}
### [facts-base](https://github.com/meteor/meteor/tree/devel/packages/facts-base) {#facts-base}
### [facts-ui](https://github.com/meteor/meteor/tree/devel/packages/facts-ui) {#facts-ui}
### [fetch](https://github.com/meteor/meteor/tree/devel/packages/fetch) {#fetch}
### [force-ssl](https://github.com/meteor/meteor/tree/devel/packages/force-ssl) {#force-ssl}
### [force-ssl-common](https://github.com/meteor/meteor/tree/devel/packages/force-ssl-common) {#force-ssl-common}
### [geojson-utils](https://github.com/meteor/meteor/tree/devel/packages/geojson-utils) {#geojson-utils}
### [github-config-ui](https://github.com/meteor/meteor/tree/devel/packages/github-config-ui) {#github-config-ui}
### [github-oauth](https://github.com/meteor/meteor/tree/devel/packages/github-oauth) {#github-oauth}
### [google-config-ui](https://github.com/meteor/meteor/tree/devel/packages/google-config-ui) {#google-config-ui}
### [google-oauth](https://github.com/meteor/meteor/tree/devel/packages/google-oauth) {#google-oauth}
### [hot-code-push](https://github.com/meteor/meteor/tree/devel/packages/hot-code-push) {#hot-code-push}
### [hot-module-replacement](https://github.com/meteor/meteor/tree/devel/packages/hot-module-replacement) {#hot-module-replacement}
### [id-map](https://github.com/meteor/meteor/tree/devel/packages/id-map) {#id-map}
### [insecure](https://github.com/meteor/meteor/tree/devel/packages/insecure) {#insecure}
### [inter-process-messaging](https://github.com/meteor/meteor/tree/devel/packages/inter-process-messaging) {#inter-process-messaging}
### [launch-screen](https://github.com/meteor/meteor/tree/devel/packages/launch-screen) {#launch-screen}
### [localstorage](https://github.com/meteor/meteor/tree/devel/packages/localstorage) {#localstorage}
### [logging](https://github.com/meteor/meteor/tree/devel/packages/logging) {#logging}
### [logic-solver](https://github.com/meteor/meteor/tree/devel/packages/logic-solver) {#logic-solver}
### [meetup-config-ui](https://github.com/meteor/meteor/tree/devel/packages/meetup-config-ui) {#meetup-config-ui}
### [meetup-oauth](https://github.com/meteor/meteor/tree/devel/packages/meetup-oauth) {#meetup-oauth}
### [meteor](https://github.com/meteor/meteor/tree/devel/packages/meteor) {#meteor}
### [meteor-base](https://github.com/meteor/meteor/tree/devel/packages/meteor-base) {#meteor-base}
### [meteor-developer-config-ui](https://github.com/meteor/meteor/tree/devel/packages/meteor-developer-config-ui) {#meteor-developer-config-ui}
### [meteor-developer-oauth](https://github.com/meteor/meteor/tree/devel/packages/meteor-developer-oauth) {#meteor-developer-oauth}
### [meteor-tool](https://github.com/meteor/meteor/tree/devel/packages/meteor-tool) {#meteor-tool}
### [minifier-css](https://github.com/meteor/meteor/tree/devel/packages/minifier-css) {#minifier-css}
### [minifier-js](https://github.com/meteor/meteor/tree/devel/packages/minifier-js) {#minifier-js}
### [minimongo](https://github.com/meteor/meteor/tree/devel/packages/minimongo) {#minimongo}
### [mobile-experience](https://github.com/meteor/meteor/tree/devel/packages/mobile-experience) {#mobile-experience}
### [mobile-status-bar](https://github.com/meteor/meteor/tree/devel/packages/mobile-status-bar) {#mobile-status-bar}
### [modern-browsers](https://github.com/meteor/meteor/tree/devel/packages/modern-browsers) {#modern-browsers}
### [modules](https://github.com/meteor/meteor/tree/devel/packages/modules) {#modules}
### [modules-runtime](https://github.com/meteor/meteor/tree/devel/packages/modules-runtime) {#modules-runtime}
### [modules-runtime-hot](https://github.com/meteor/meteor/tree/devel/packages/modules-runtime-hot) {#modules-runtime-hot}
### [mongo](https://github.com/meteor/meteor/tree/devel/packages/mongo) {#mongo}
### [mongo-dev-server](https://github.com/meteor/meteor/tree/devel/packages/mongo-dev-server) {#mongo-dev-server}
### [mongo-id](https://github.com/meteor/meteor/tree/devel/packages/mongo-id) {#mongo-id}
### [mongo-livedata](https://github.com/meteor/meteor/tree/devel/packages/mongo-livedata) {#mongo-livedata}
### [npm-mongo](https://github.com/meteor/meteor/tree/devel/packages/npm-mongo) {#npm-mongo}
### [oauth](https://github.com/meteor/meteor/tree/devel/packages/oauth) {#oauth}
### [oauth-encryption](https://github.com/meteor/meteor/tree/devel/packages/oauth-encryption) {#oauth-encryption}
### [oauth1](https://github.com/meteor/meteor/tree/devel/packages/oauth1) {#oauth1}
### [oauth2](https://github.com/meteor/meteor/tree/devel/packages/oauth2) {#oauth2}
### [ordered-dict](https://github.com/meteor/meteor/tree/devel/packages/ordered-dict) {#ordered-dict}
### [package-stats-opt-out](https://github.com/meteor/meteor/tree/devel/packages/package-stats-opt-out) {#package-stats-opt-out}
### [package-version-parser](https://github.com/meteor/meteor/tree/devel/packages/package-version-parser) {#package-version-parser}
### [promise](https://github.com/meteor/meteor/tree/devel/packages/promise) {#promise}
### [random](https://github.com/meteor/meteor/tree/devel/packages/random) {#random}
### [rate-limit](https://github.com/meteor/meteor/tree/devel/packages/rate-limit) {#rate-limit}
### [react-fast-refresh](https://github.com/meteor/meteor/tree/devel/packages/react-fast-refresh) {#react-fast-refresh}
### [reactive-dict](https://github.com/meteor/meteor/tree/devel/packages/reactive-dict) {#reactive-dict}
### [reactive-var](https://github.com/meteor/meteor/tree/devel/packages/reactive-var) {#reactive-var}
### [reload](https://github.com/meteor/meteor/tree/devel/packages/reload) {#reload}
### [reload-safetybelt](https://github.com/meteor/meteor/tree/devel/packages/reload-safetybelt) {#reload-safetybelt}
### [retry](https://github.com/meteor/meteor/tree/devel/packages/retry) {#retry}
### [routepolicy](https://github.com/meteor/meteor/tree/devel/packages/routepolicy) {#routepolicy}
### [server-render](https://github.com/meteor/meteor/tree/devel/packages/server-render) {#server-render}
### [service-configuration](https://github.com/meteor/meteor/tree/devel/packages/service-configuration) {#service-configuration}
### [session](https://github.com/meteor/meteor/tree/devel/packages/session) {#session}
### [sha](https://github.com/meteor/meteor/tree/devel/packages/sha) {#sha}
### [shell-server](https://github.com/meteor/meteor/tree/devel/packages/shell-server) {#shell-server}
### [socket-stream-client](https://github.com/meteor/meteor/tree/devel/packages/socket-stream-client) {#socket-stream-client}
### [standard-minifier-css](https://github.com/meteor/meteor/tree/devel/packages/standard-minifier-css) {#standard-minifier-css}
### [standard-minifier-js](https://github.com/meteor/meteor/tree/devel/packages/standard-minifier-js) {#standard-minifier-js}
### [standard-minifiers](https://github.com/meteor/meteor/tree/devel/packages/standard-minifiers) {#standard-minifiers}
### [static-html](https://github.com/meteor/meteor/tree/devel/packages/static-html) {#static-html}
### [test-helpers](https://github.com/meteor/meteor/tree/devel/packages/test-helpers) {#test-helpers}
### [test-in-browser](https://github.com/meteor/meteor/tree/devel/packages/test-in-browser) {#test-in-browser}
### [test-in-console](https://github.com/meteor/meteor/tree/devel/packages/test-in-console) {#test-in-console}
### [test-server-tests-in-console-once](https://github.com/meteor/meteor/tree/devel/packages/test-server-tests-in-console-once) {#test-server-tests-in-console-once}
### [tinytest](https://github.com/meteor/meteor/tree/devel/packages/tinytest) {#tinytest}
### [tinytest-harness](https://github.com/meteor/meteor/tree/devel/packages/tinytest-harness) {#tinytest-harness}
### [tracker](https://github.com/meteor/meteor/tree/devel/packages/tracker) {#tracker}
### [twitter-config-ui](https://github.com/meteor/meteor/tree/devel/packages/twitter-config-ui) {#twitter-config-ui}
### [twitter-oauth](https://github.com/meteor/meteor/tree/devel/packages/twitter-oauth) {#twitter-oauth}
### [typescript](https://github.com/meteor/meteor/tree/devel/packages/typescript) {#typescript}
### [underscore](https://github.com/meteor/meteor/tree/devel/packages/underscore) {#underscore}
### [underscore-tests](https://github.com/meteor/meteor/tree/devel/packages/underscore-tests) {#underscore-tests}
### [url](https://github.com/meteor/meteor/tree/devel/packages/url) {#url}
### [webapp](https://github.com/meteor/meteor/tree/devel/packages/webapp) {#webapp}
### [webapp-hashing](https://github.com/meteor/meteor/tree/devel/packages/webapp-hashing) {#webapp-hashing}
### [weibo-config-ui](https://github.com/meteor/meteor/tree/devel/packages/weibo-config-ui) {#weibo-config-ui}
### [weibo-oauth](https://github.com/meteor/meteor/tree/devel/packages/weibo-oauth) {#weibo-oauth}

145
v3-docs/docs/api/session.md Normal file
View File

@@ -0,0 +1,145 @@
# Session
Documentation of Meteor's client-side session API.
`Session` provides a global object on the client that you can use to
store an arbitrary set of key-value pairs. Use it to store things like
the currently selected item in a list.
What's special about `Session` is that it's reactive. If
you call [`Session.get`](#Session-get)`('currentList')`
from inside a template, the template will automatically be rerendered
whenever [`Session.set`](#Session-set)`('currentList', x)` is called.
To add `Session` to your application, run this command in your terminal:
```bash
meteor add session
```
<ApiBox name="Session.set" hasCustomExample/>
```js
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { Meteor } from 'meteor/meteor';
Tracker.autorun(() => {
Meteor.subscribe('chatHistory', { room: Session.get('currentRoomId') });
});
// Causes the function passed to `Tracker.autorun` to be rerun, so that the
// 'chatHistory' subscription is moved to the room 'home'.
Session.set('currentRoomId', 'home');
```
`Session.set` can also be called with an object of keys and values, which is
equivalent to calling `Session.set` individually on each key/value pair.
```js
Session.set({
a: 'foo',
b: 'bar'
});
```
<ApiBox name="Session.setDefault"/>
This is useful in initialization code, to avoid re-initializing a session
variable every time a new version of your app is loaded.
<ApiBox name="Session.get" hasCustomExample/>
This example in Blaze.js
::: code-group
```html [main.html]
<template name="main">
<p>We've always been at war with {{theEnemy}}.</p>
</template>
```
```js [main.js]
Template.main.helpers({
theEnemy() {
return Session.get('enemy');
}
});
Session.set('enemy', 'Eastasia');
// Page will say "We've always been at war with Eastasia"
Session.set('enemy', 'Eurasia');
// Page will change to say "We've always been at war with Eurasia"
```
:::
<ApiBox name="Session.equals"/>
If value is a scalar, then these two expressions do the same thing:
```js
Session.get('key') === value
Session.equals('key', value)
```
...but the second one is always better. It triggers fewer invalidations
(template redraws), making your program more efficient.
This example in Blaze.js:
::: code-group
```html [main.html]
<template name="postsView">
{{#each posts}}
{{> postItem}}
{{/each}}
</template>
<template name="postItem">
<div class="{{postClass}}">{{title}}</div>
</template>
```
```js [main.js]
Template.postsView.helpers({
posts() {
return Posts.find();
}
});
Template.postItem.helpers({
postClass() {
return Session.equals('selectedPost', this._id)
? 'selected'
: '';
}
});
Template.postItem.events({
'click'() {
Session.set('selectedPost', this._id);
}
});
```
Using Session.equals here means that when the user clicks
on an item and changes the selection, only the newly selected
and the newly unselected items are re-rendered.
If Session.get had been used instead of Session.equals, then
when the selection changed, all the items would be re-rendered.
For object and array session values, you cannot use `Session.equals`; instead,
you need to use the `underscore` package and write
`_.isEqual(Session.get(key), value)`.

View File

@@ -0,0 +1,5 @@
# Templates
Documentation of Meteor's client-side session API.
This documentation has moved to the [Blaze Community Site](http://blazejs.org/api/templates).

View File

@@ -0,0 +1,121 @@
# Environment Variables
List of environment variables that you can use with your Meteor application.
## BIND_IP
(_production_)
Bind the application server to a specific network interface by IP address, for example: `BIND_IP=192.168.0.2`.
See also: [`PORT`](#port).
> In development, this can be accomplished with `meteor run --port a.b.c.d:port`.
## DDP_DEFAULT_CONNECTION_URL
(_develoment, production_)
There are some situations where it is valuable for the meteor client to use a different DDP server than the `ROOT_URL` server.
Setting `DDP_DEFAULT_CONNECTION_URL` when running a meteor server (development: `meteor run` or production: `node main.js`) will set the DDP server to the value in `DDP_DEFAULT_CONNECTION_URL`.
Setting `DDP_DEFAULT_CONNECTION_URL` when building (`meteor build`) will define the DDP server for `cordova` builds.
## DISABLE_WEBSOCKETS
(_development, production_)
In the event that your own deployment platform does not support WebSockets, or you are confident that you will not benefit from them, setting this variable with `DISABLE_WEBSOCKETS=1` will explicitly disable WebSockets and forcibly resort to the fallback polling-mechanism, instead of trying to detect this automatically.
## DISABLE_SOCKJS
(_development, production_)
Set `DISABLE_SOCKJS=1` if you want to use the native WebSocket implementation instead of SockJS on the client side, for example, if you want to use a custom WebSocket implementation (e.g. [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js/)) on the server side.
## HTTP_FORWARDED_COUNT
(_production_)
Set this to however many number of proxies you have running before your Meteor application. For example, if have an NGINX server acting as a proxy before your Meteor application, you would set `HTTP_FORWARDED_COUNT=1`. If you have a load balancer in front of that NGINX server, the count is 2.
## MAIL_URL
(_development, production_)
Use this variable to set the SMTP server for sending e-mails. [Postmark](https://www.postmarkapp.com), [Mandrill](https://www.mandrillapp.com), [MailGun](https://www.mailgun.com) and [SendGrid](https://www.sendgrid.com) (among others) are companies who can provide this service. The `MAIL_URL` contains all of the information for connecting to the SMTP server and, like a URL, should look like `smtp://user:pass@yourservice.com:587` or `smtps://user:pass@yourservice.com:465`.
The `smtp://` form is for mail servers which support encryption via `STARTTLS` or those that do not use encryption at all and is most common for servers on port 587 and _sometimes_ port 25. On the other hand, the `smtps://` form (the `s` stands for "secure") should be used if the server only supports TLS/SSL (and does not support connection upgrade with `STARTTLS`) and is most common for servers on port 465.
## METEOR_DISABLE_OPTIMISTIC_CACHING
(_production_)
When running `meteor build` or `meteor deploy` you can set `METEOR_DISABLE_OPTIMISTIC_CACHING=1` to speed up your build time.
Since optimistic in-memory caching is one of the more memory-intensive parts of the build system, setting the environment variable `METEOR_DISABLE_OPTIMISTIC_CACHING=1` can help improve memory usage during meteor build, which seems to improve the total build times. This configuration is perfectly safe because the whole point of optimistic caching is to keep track of previous results for future rebuilds, but in the case of meteor `build` or `deploy` there's only ever one initial build, so the extra bookkeeping is unnecessary.
## METEOR_PROFILE
(_development_)
In development, you may need to diagnose what has made builds start taking a long time. To get the callstack and times during builds, you can run `METEOR_PROFILE=1 meteor`.
## METEOR_PACKAGE_DIRS
(_development, production_)
Colon-delimited list of local package directories to look in, outside your normal application structure, for example: `METEOR_PACKAGE_DIRS="/usr/local/my_packages/"`. Note that this used to be `PACKAGE_DIRS` but was changed in Meteor 1.4.2.
## METEOR_SETTINGS
(_production_)
When running your bundled application in production mode, pass a string of JSON containing your settings with `METEOR_SETTINGS='{ "server_only_setting": "foo", "public": { "client_and_server_setting": "bar" } }'`.
> In development, this is accomplished with `meteor --settings [file.json]` in order to provide full-reactivity when changing settings. Those settings are simply passed as a string here. Please see the [Meteor.settings](../api/meteor#Meteor-settings) documentation for further information.
## METEOR_SQLITE_JOURNAL_MODE
(_development_)
The Meteor package catalog uses the `WAL` [SQLite Journal Mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) by default. The Journal mode for the package catalog can be modifed by setting `METEOR_SQLITE_JOURNAL_MODE`.
When running multiple concurrent meteor servers on [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/) some meteor developers have seen issues with the package catalog. Setting the environment variable `METEOR_SQLITE_JOURNAL_MODE=TRUNCATE` can overcome the issue.
## MONGO_OPLOG_URL
(_development, production_)
MongoDB server oplog URL. If you're using a replica set (which you should), construct this url like so: `MONGO_OPLOG_URL="mongodb://user:password@myserver.com:10139/local?replicaSet=(your replica set)&authSource=(your auth source)"`
## MONGO_URL
(_development, production_)
MongoDB server URL. Give a fully qualified URL (or comma-separated list of URLs) like `MONGO_URL="mongodb://user:password@myserver.com:10139"`. For more information see the [MongoDB docs](https://docs.mongodb.com/manual/reference/connection-string/).
## PORT
(_production_)
Which port the app should listen on, for example: `PORT=3030`
See also: [`BIND_IP`](#bind-ip).
> In development, this can be accomplished with `meteor run --port <port>`.
## ROOT_URL
(_development, production_)
Used to generate URLs to your application by, among others, the accounts package. Provide a full URL to your application like this: `ROOT_URL="https://www.myapp.com"`.
## TOOL_NODE_FLAGS
(_development, production_)
Used to pass flags/variables to Node inside Meteor build. For example you can use this to pass a link to icu data: `TOOL_NODE_FLAGS="--icu-data-dir=node_modules/full-icu"`
For full list of available flags see the [Node documentation](https://nodejs.org/dist/latest-v12.x/docs/api/cli.html).
## UNIX_SOCKET_GROUP
(_production_)
This overrides the default UNIX group of the socket file configured in `UNIX_SOCKET_PATH`. It can be set to a group name or a numerical gid.
## UNIX_SOCKET_PATH
(_production_)
Configure Meteor's HTTP server to listen on a UNIX socket file path (e.g. `UNIX_SOCKET_PATH=/tmp/meteor.sock`) instead of a TCP port. This is useful when running a local reverse proxy server like Nginx to handle client HTTP requests and direct them to your Meteor application. Leveraging UNIX domain sockets for local communication on the same host avoids the Operating System overhead required by TCP based communication and can also improve security. This UNIX socket file is created when Meteor starts and removed when Meteor exits.
## UNIX_SOCKET_PERMISSIONS
(_production_)
This overrides the default UNIX file permissions on the UNIX socket file configured in `UNIX_SOCKET_PATH`. For example, `UNIX_SOCKET_PERMISSIONS=660` would set read/write permissions for both the user and group.

1014
v3-docs/docs/cli/index.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
# Using Core Types
Using core types with zodern:types
For MeteorJS in its version 2.8.1 we have introduced to our core packages an integration with the [zodern:types](https://github.com/zodern/meteor-types) package.
This package allows you to use the TypeScript types for the Meteor core packages in your TypeScript code or JavaScript code.
in order to use the types you need to install the package by running the command:
```bash
meteor add zodern:types
```
And add the following line to your `tsconfig.json` file (if you do not have one, create one and add the code bellow):
```json
{
"compilerOptions": {
"preserveSymlinks": true,
"paths": {
"meteor/*": [
"node_modules/@types/meteor/*",
".meteor/local/types/packages.d.ts"
]
}
}
}
```
then run the command:
```bash
meteor lint
```
this will create a file within your .meteor folder that will have your types for the core packages.
You can continue to use your code as you did before, but now you can use the types for the core packages even if you are in JavaScript.
for more information about the package please visit the [zodern:types](https://github.com/zodern/meteor-types).

View File

@@ -0,0 +1,136 @@
<script setup lang="ts">
import Booleans from './helpers/Booleans.vue'
import Classes from './helpers/Classes.vue'
import Functions from './helpers/Functions.vue'
import Locus from './helpers/Locus.vue'
import ParamTable from './helpers/ParamTable.vue'
import { useData } from 'vitepress'
import jsdoc from '../data/data.js'
type Jsdoc = typeof jsdoc
type Params = keyof Jsdoc
const props = defineProps({
name: {
type: String,
required: true
},
hasCustomExample: {
type: Boolean,
default: false
},
instanceName: {
type: String,
default: 'this'
},
from: {
type: String,
default: ''
}
})
function getJsdoc<T extends Params>(key: T): Jsdoc[T] {
if (!jsdoc[key]) {
const { page } = useData()
throw new Error(`
jsdoc key: "${key}" not found.
Refer to data/data.js for available keys.
Error come from ${page.value.filePath}
`)
}
return jsdoc[key]
}
const ui = getJsdoc(props.name)
if (props.from) {
ui.module = props.from
}
const link = ui.longname.replace('.', '-').replace('#', '-')
const isBoolean = ui.type?.names?.at(0) === 'Boolean'
// if is constructor/class we change this to false;
let isFunction = ui.kind === 'function' || ui?.params?.length > 0;
const isClass = (() => {
if (ui.kind === 'class') {
isFunction = false
return true
}
return false;
})();
const isInstance = ui.scope === 'instance';
const showName = (longname) => {
if (isInstance) {
// what is before # becomes `this`
return longname.replace(/.*#/, `${props.instanceName}.`)
}
if (ui.ishelper) {
return `{{ ${longname} }}`
}
return longname
}
if (isFunction) {
for (const param of ui.params) {
if (!param.type) {
param.type = { names: [param.name] }
}
}
}
const gitHubSource = {
lineNumber: ui.lineno,
filePath: ui.filepath
}
const debug = (name) => {
if (ui.longname !== name) return
console.log(ui)
}
// debug('Mongo.Collection')
</script>
<template>
<div>
<h2 :id="link">
<a class="header-anchor" :href="'#' + link" :aria-label="'Permalink to &quot;' + ui.longname + '&quot;'"></a>
{{ showName(ui.longname) }}
<Locus v-if="ui.locus && ui.locus !== 'Anywhere'" :locus="ui.locus" />
</h2>
<h4>
Summary:
</h4>
<div v-html="ui.summary"></div>
<slot />
<ParamTable v-if="isFunction || isClass" :params="ui.params" :options="ui.options" :from="gitHubSource" />
<template v-if="!hasCustomExample">
<Booleans v-if="isBoolean" :memberof="ui.memberof" :from="ui.module" :longname="ui.longname" />
<Functions v-if="isFunction" :from="ui.module" :longname="ui.longname" :params="ui.params" :fnName="ui.name"
:memberof="isInstance ? instanceName : ui.memberof" :scope="ui.scope" :returns="ui.returns" />
<Classes v-if="isClass" :params="ui.params" :from="ui.module" :longname="ui.longname" />
</template>
</div>
</template>
<style scoped>
span {
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
h2 {
display: flex;
align-content: center;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,69 @@
<script setup>
import { useData } from "vitepress";
import { ref, computed, watchEffect } from 'vue'
import names from "../data/names.json";
import { createMap, filterMap } from "./scripts/map-maker.js";
const apiList = createMap(names)
const listRef = ref(apiList);
const search = ref("");
watchEffect(() => {
listRef.value = filterMap(search.value, apiList);
})
const shouldRedirect = (list, api) => {
if (list?.shouldGoTo) {
return `${list.shouldGoTo}`;
}
return api;
}
</script>
<template>
<main>
<label for="search">Search</label>
<br />
<input class="search" type="text" v-model="search" />
<div v-for="(list, api, index) in listRef" :key="index">
<h2 style="text-transform:capitalize;">
<a :href="shouldRedirect(list, api)" style="text-decoration: none;">
{{ api }}
</a>
</h2>
<div v-for="(links, key) in list" :key="key">
<!-- Should not render the shouldGoTo section -->
<div v-if="key !== 'shouldGoTo'">
<h5 v-for="(link) in links" :key="link">
<a :href="shouldRedirect(list, api) + '#' + link.replace('#', '-').replace('.', '-')">
{{ link }}
</a>
</h5>
</div>
</div>
</div>
</main>
</template>
<style scoped>
.search {
width: 100%;
height: 2rem;
border-radius: 0.5rem;
border: 1px solid #ccc;
padding: 0.5rem;
margin-bottom: 1rem;
}
a {
color: var(--vp-c-text-1);
}
a:hover {
color: var(--vp-c-brand-1);
text-decoration: underline;
}
</style>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const props = defineProps<{
from: string;
longname: string;
memberof: string;
}>()
</script>
<template>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span>
<pre class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { {{ props.memberof }} } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'meteor/{{ props.from }}'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">/** </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> {Boolean}</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ({{ props.longname }}) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // do something</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre>
</div>
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
const props = defineProps<{
isOpen: boolean;
}>()
</script>
<template>
<div :class="props.isOpen ? 'caret-btn-open ' : 'caret-btn-closed'" class="caret-btn " role="button"
aria-label="toggle section" tabindex="0"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"
viewBox="0 0 24 24" class="">
<path
d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z">
</path>
</svg>
</div>
</template>
<style scoped>
.caret-btn {
width: 18px;
height: 18px;
fill: currentColor;
transition: transform 0.25s;
display: inline-flex;
vertical-align: text-top;
}
.caret-btn-closed {
transform: rotate(0deg);
}
.caret-btn-open {
transform: rotate(90deg);
}
</style>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import { makePrimitiveHTML } from '../scripts/make-primitive-html';
const props = defineProps<{
from: string;
longname: string;
params: {
name: string;
type: { names: string[] };
description: string;
optional?: boolean;
}[];
}>()
const toCamelCase = (str: string) => {
if (str.includes(".")) {
str = str.split(".")[1];
}
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
}
const isOneLiner = props?.params?.length === 0;
</script>
<template>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span> <pre class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { {{ props.longname.split(".")[0] }} } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "meteor/{{ props.from }}""</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> {{ toCamelCase(props.longname) }}</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> {{ props.longname }}</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(<span v-show="isOneLiner">);</span></span>
<span class="line" v-for="(param, index) in props.params" :key="param.name"><span v-html="makePrimitiveHTML({ primitive: [param.type.names[0]], arr: props.params, index, isOptional: param.optional, name: param.name })"/></span>
<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
</code></pre>
</div>
</template>

View File

@@ -0,0 +1,56 @@
<script setup lang="ts">
import { makePrimitiveHTML, comment, typeComment } from '../scripts/make-primitive-html';
const props = defineProps<{
from: string;
longname: string;
memberof: string;
fnName: string;
params: {
name: string;
type: { names: string[] };
description: string;
optional: boolean;
}[];
scope: string;
returns?: {
type: { names: string[] }
}[]
}>()
const isOneLiner = props.params.length === 0;
function trimFnName(str: string) {
if (str.startsWith('.')) {
return str.slice(1);
}
return str;
}
function getInstanceName(longname: string) {
return longname.split("#")[0]
}
function getReturnType(returns: typeof props.returns) {
const type = returns?.[0]?.type?.names[0];
if (!type) return false;
return type.replace('.<', '<');
}
</script>
<template>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span>
<pre
class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line" v-if="props.scope !== 'instance' && props.from"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { {{ props.memberof.split(".")[0] }} } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "meteor/{{ props.from }}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line" v-if="props.scope === 'instance'" v-html="comment(`// ${props.memberof} is an instance of ${getInstanceName(props.longname)}`)"></span>
<span class="line" v-if="props.returns !== undefined" v-html="typeComment(getReturnType(props.returns))"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><span :style="{'display': props.returns === undefined ? 'none' : ''}"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const </span>result = </span>{{ props.memberof }}.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">{{ trimFnName(props.fnName) }}</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(<span v-show="isOneLiner">);</span></span></span>
<span class="line" v-for="(param, index) in props.params" :key="param.name"><span v-html="makePrimitiveHTML({ primitive: [param.type.names[0]], arr: props.params, index, isOptional: param.optional, name: param.name })"/></span>
<span v-show="!isOneLiner" class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span></code></pre>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<template>
<div class="warning custom-block">
<p>{{ props.locus }} only</p>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
locus: string;
}>()
</script>
<style scoped>
div {
margin: 0 !important;
padding: 5px;
}
</style>

View File

@@ -0,0 +1,132 @@
<script setup lang="ts">
import { Collapse } from 'vue-collapsed'
import { ref } from 'vue'
import Caret from './Caret.vue'
import { types } from 'util'
const copyArray = <T>(arr: T[]): T[] => {
const newArr: T[] = []
for (const item of arr) newArr.push(item)
return newArr
}
const props = defineProps<{
params: {
name: string;
type: { names: string[] };
description: string;
optional: boolean;
}[];
from: {
lineNumber: number;
filePath: string;
}
options?: { description: string, name: string, type: { names: string[] }; optional?: boolean }[]
}>()
const localArr = copyArray(props.params)
const hasOptions = ({ params }: typeof props) => {
for (const param of params) if (param.name === "options") return true
}
const isOptionsTableOpen = ref(false);
function toggleOptionsTable() {
isOptionsTableOpen.value = !isOptionsTableOpen.value
}
const showTypes = (types: string[]) => {
const typesArr = copyArray(types)
if (typesArr.length === 1) return typesArr[0]
const last = typesArr.pop()
return typesArr.join(", ") + " or " + last
}
const sourceCode = `https://github.com/meteor/meteor/blob/devel/packages/${props.from.filePath}#L${props.from.lineNumber}`
</script>
<template>
<div v-if="localArr.length > 0">
<header>
<h4>Arguments:</h4>
<a :href="sourceCode">
Source code
</a>
</header>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody>
<tr v-for="param in localArr" :key="param.name">
<td>{{ param.name }}</td>
<td>{{ showTypes(param.type.names) }}</td>
<template v-if="param.name === 'options'">
<td>
<span v-html="param.description"></span>
<button v-if="(props.options?.length || -1) > 0" type="button" @click="toggleOptionsTable">
{{ isOptionsTableOpen ? "Close" : "Open" }} options table
<Caret :is-open="isOptionsTableOpen" />
</button>
</td>
</template>
<template v-else>
<td v-html="param.description ?? `----`"></td>
</template>
<td>{{ param.optional ? "No" : "Yes" }}</td>
</tr>
</tbody>
</table>
<Collapse v-if="hasOptions(props) && props.options && props.options?.length > 0" :when="isOptionsTableOpen"
class="options-table">
<h4>Options:</h4>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody>
<tr v-for="param in props.options" :key="param.name">
<td>{{ param.name }}</td>
<td>{{ param.type.names[0] }}</td>
<td v-html="param.description ?? ``"></td>
<td> No </td>
</tr>
</tbody>
</table>
</Collapse>
</div>
</template>
<style scoped>
table {
text-align: center;
}
.options-table {
--easing-dur: calc(var(--vc-auto-duration) * 1.5) cubic-bezier(0.33, 1, 0.68, 1);
transition:
height var(--easing-dur),
background-color var(--easing-dur),
border-radius var(--easing-dur);
}
button:hover {
cursor: pointer;
color: var(--vp-c-brand-1);
}
header {
display: flex;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,109 @@
const primitiveDefault = {
function: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {}`,
string: (name) =>
`<span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "${name}"`,
number: () =>
'<span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 42',
boolean: () =>
'<span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> false',
object: (name) =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ${name}`,
array: () => '<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> []',
"array.<string>": () =>
'<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"string"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]',
"array.<object>": () =>
'<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}]',
"array.<ejsonable>": () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ${primitiveDefault.ejsonable(
{
pre: "[",
post: "]",
}
)}`,
// someProp: 'string', num: 42, bool: false, arr: [], obj: {}
ejsonable(obj) {
let pre, post;
if (typeof obj === "string") pre = "";
if (typeof obj === "object") {
pre = obj.pre;
post = obj.post;
}
if (pre.length > 1) pre = "";
// if (post.length > 1) post = "";
return `<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ${
pre ?? ``
}{ num:${primitiveDefault.number()}</span> , someProp:${primitiveDefault.string(
"foo"
)} </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}${
post ?? ``
}`;
},
ejson(pre, post) {
return primitiveDefault.ejsonable(pre, post);
},
jsoncompatible(pre, post) {
return primitiveDefault.ejsonable(pre, post);
},
promise: () =>
'<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Promise {</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}',
any: () => '<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> any',
error: (name) =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Error(${name})`,
mongoselector: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> MongoSelector`,
mongomodifier: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> MongoModifier`,
iterationcallback: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> IterationCallback`,
expresshandlerscallback: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ExpressHandlersCallback`,
addruntimeconfighookcallback: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> addRuntimeConfigHookCallback`,
addupdatednotifyhookcallback: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> addUpdatedNotifyHookCallback`,
'tracker.computationfunction': () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Tracker.ComputationFunction`,
computation: () =>
`<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Computation`,
func: () => primitiveDefault.function(),
'tracker.computation': () => primitiveDefault.computation(),
};
const comma = `<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span>`;
const br = `<br/>`;
const typeComment = (type) =>{
return `<span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">/** </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@returns</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> {${type}}</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> */</span>`
}
const comment = (text = " // this param is optional ") =>
`<span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">${text}</span>`;
const line = ({ html, pre, post } = { pre: "", post: "", html: "" }) =>
`${pre}<span class="line">${html}</span>${post}`;
const makePrimitiveHTML = ({ primitive, arr, index, isOptional, name }) => {
let n: string = primitive[0],
primitiveName = n.toLowerCase(),
value;
try {
value = primitiveDefault[primitiveName](name);
} catch (e) {
console.error("primitive that we got:", primitive);
throw new Error(
`primitive ${primitiveName} is not registred in the map, here is the error: ${e}`
);
}
if (arr.length > 1)
return line({
html: value + `${comma}</span>`,
pre: index === 0 ? "" : br,
post: isOptional ? comment() : "",
});
return value + `</span>`;
};
export { makePrimitiveHTML, comment, typeComment };

View File

@@ -0,0 +1,145 @@
const makeUnique = (arr) => [...new Set(arr)];
const makeModules = (names) => {
const modules = [];
// check for modules (names with no dots or hashes)
for (const name of names) {
if (name.includes("#")) continue;
if (name.includes(".")) continue;
modules.push(name);
}
return modules;
};
const makeApiList = (modules) => {
let currentModule = modules[0];
const apiList = {};
for (const name of modules) {
if (!apiList[currentModule]) {
apiList[currentModule] = {
[currentModule]: [],
};
}
if (name.includes(currentModule)) {
apiList[currentModule][name] = [];
continue;
}
currentModule = name;
}
return apiList;
};
/**
*
* @param {Array<string} names
* @returns {Object<string, Object<string, Array<string>>}
*/
export function createMap(names) {
/**
* @type {string[]}
*/
const modules = makeModules(names);
/**
* @type {Object<string, Object<string, Array<string>>}
*/
const apiList = makeApiList(modules);
const MODULES_TO_ADD = {
module: { module: [] },
Session: { Session: [] },
Random: { Random: [] },
Email: { Email: [] },
DDPRateLimiter: { DDPRateLimiter: [] },
};
Object.assign(apiList, MODULES_TO_ADD);
// populating map with links
for (const api of Object.keys(apiList)) {
const links = apiList[api];
for (const link of Object.keys(links)) {
const allApis = names.filter(
(name) => name.includes(link + ".") || name.includes(link + "#")
);
apiList[api][link] = makeUnique(allApis);
}
}
// break App and WebApp
const webApp = apiList.App.App.filter((name) => name.includes("WebApp"));
const app = apiList.App.App.filter((name) => !name.includes("WebApp"));
apiList.App.App = app;
apiList.WebApp = {
WebApp: webApp,
};
// delete missplaced apis
const TO_IGNORE = [
"addRuntimeConfigHookCallback(options)",
"addUpdatedNotifyHookCallback(options)",
"currentUser",
"expressHandlersCallback(req, res, next)",
"getPublicationStrategy",
"loggingIn",
"main",
"loggingOut",
"IterationCallback",
"CompileStep",
"Cordova",
"Plugin"
];
for (const key of Object.keys(apiList))
if (TO_IGNORE.includes(key)) delete apiList[key];
// reroute DDP to -> meteor.html#DDP-connect
apiList.DDP.shouldGoTo = '/api/meteor';
// Match -> check#Match
apiList.check.shouldGoTo = '/api/check';
// Mongo -> collections.html#Mongo
apiList.Mongo.shouldGoTo = '/api/collections';
// Npm -> package.html#Npm
apiList.Npm.shouldGoTo = '/api/package';
// Subscription -> meteor.html#Meteor-publish
apiList.Subscription.shouldGoTo = '/api/meteor';
// Module -> packages/hot-module-replacement.html
apiList.module.shouldGoTo = '/packages/hot-module-replacement';
// Random -> packages/random.html
apiList.Random.shouldGoTo = '/packages/random';
// WebApp -> packages/webapp.html
apiList.WebApp.shouldGoTo = '/packages/webapp';
return apiList;
}
/**
*
* @param {string} filter
* @returns {Object<string, Object<string, Array<string>>}
* @returns {Object<string, Object<string, Array<string>>}
*/
export function filterMap(filter, apiList) {
if (filter === "") {
return apiList;
}
const newList = {};
for (const api in apiList) {
const newLinks = {};
for (const key in apiList[api]) {
const links = apiList[api][key];
const newLinksArray = links.filter((link) => {
return link.toLowerCase().includes(filter.toLowerCase());
});
if (newLinksArray.length > 0) {
newLinks[key] = newLinksArray;
}
}
if (Object.keys(newLinks).length > 0) {
newList[api] = newLinks;
}
}
return newList;
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,36 @@
## vX.XX.X, 2023-XX-XX
### Highlights
* MongoDB Server 6.x Support
* Embedded Mongo now uses MongoDB 6.0.3
* Some pr [GH someone] [PR #number] // this will become -> [someone](https://github.com/someone) [PR](https://github.com/meteor/meteor/pull/number)
#### Breaking Changes
N/A
#### Internal API changes
N/A
#### Migration Steps
TODO
#### Meteor Version Release
* `Command line`:
- Corrected typo in XX XX
* `npm mongo @4.13.0`: // You can use @get-version to get the version of the package
- Updated MongoDB driver to version 4.13.0
#### Special thanks to
- [@XXX](https://github.com/XXXX).
For making this great framework even better!

View File

@@ -0,0 +1,48 @@
## Changelog Generator
This is a generator for the changelog, you must create a file with the name of
the version that you are generating the changelog for. The script will take care of the rest.
In this file you should follow the EXAMPLE.md file that is within this directory.
The script will generate a file called `history.gen.md` that will be used by the
`changelog.md` file to generate the changelog page.
To get which branches were merged into release you can search in the GitHub
repo by using this query:
```
is:pr base:<release-branch-name> is:merged
```
or in GH Cli:
```bash
gh pr list --state merged --base <release-branch-name>
```
note that it may not be as useful as the first one, since it will not show the
Authors and other related information.
## Why?
Computers with lower memory/ IDEs with high memory usage can have problems with
the changelog file(~10k lines). This is a way to reduce the memory usage of the changelog, also creating a more
organized changelog, since all the files will be reflecting at least one version.
## Update ordering.
If you want to make sure that the changelog is correcly ordered, take a look at the `order-packages.js` file.
to use it, run the command below:
```bash
node order-packages.js versions/3.0.md
```
or
```bash
node order-packages.js versions/<your-version>.md
```
it will update the file with the correct ordering(this will override the file).

View File

@@ -0,0 +1,81 @@
const fs = require("fs").promises;
// we want to get the strings that are between #### Breaking Changes and #### New Public API
// then we will create a map with the package name and the version for example:
//
// - `accounts-2fa@3.0.0`:
// - Some methods are now async. See below:
// - `Accounts._is2faEnabledForUser`
// - `(Meteor Method) - generate2faActivationQrCode`
// - `(Meteor Method) - enableUser2fa`
// - `(Meteor Method) - disableUser2fa`
// - `(Meteor Method) - has2faEnabled`
// will be converted to:
// {"accounts-2fa@3.0.0": ` - Some methods are now async. See below:
// - `Accounts._is2faEnabledForUser`
// - `(Meteor Method) - generate2faActivationQrCode`
// - `(Meteor Method) - enableUser2fa`
// - `(Meteor Method) - disableUser2fa`
// - `(Meteor Method) - has2faEnabled``
// }
// then we will iterate and order the packages in alphabetical order and write again to the file.
/**
*
* @param {string} path
* @returns {Promise<[string, null] | ["", Error]>}
*/
async function getFile(path) {
try {
const data = await fs.readFile(path, "utf8");
return [data, null];
} catch (e) {
console.error(e);
return ["", new Error("could not read file")];
}
}
async function main() {
const [filePath] = process.argv.slice(2);
const [code, error] = await getFile(filePath);
if (error) throw error;
const regex = /#### Breaking Changes([\s\S]*?)#### New Public API/gm;
const matches = code.match(regex).join("\n").split("\n");
let objectMap = {};
let currentWorkingPackage = "";
for (const line of matches) {
if (line.startsWith("-")) {
const packageName = line
.replace("-", "")
.replace("`:", "")
.replace("`", "")
.trim();
objectMap[packageName] = "";
currentWorkingPackage = packageName;
continue;
}
objectMap[currentWorkingPackage] += line + "\n";
}
// sorting acc
const result = Object.keys(objectMap)
.reduce((acc, key) => {
if (key === "") return acc;
acc.push({ key, value: objectMap[key]});
return acc;
}, [])
.sort((a, b) => a.key.localeCompare(b.key))
.reduce((acc, { key, value }) => {
return acc + `- \`${key}\`:\n${value}`;
}, "")
const newCode = code.replace(regex, `#### Breaking Changes\n\n${result}`);
await fs.writeFile(filePath, newCode);
}
main().then(() => console.log("done"));

View File

@@ -0,0 +1,110 @@
const _fs = require("fs");
const fs = _fs.promises;
function getPackageVersion(packageName) {
function getFile(path) {
try {
const data = _fs.readFileSync(path, "utf8");
return [data, null];
} catch (e) {
console.error(e);
return ["", e];
}
}
const [code, error] = getFile(`../../packages/${packageName}/package.js`);
if (error) return "";
for (const line of code.split(/\n/)) {
// verify if the line has a version
if (!line.includes("version:")) continue;
//Package.describe({
// summary: 'some description.',
// version: '1.2.3' <--- this is the line we want, we assure that it has a version in the previous if
//});
const [_, versionValue] = line.split(":");
if (!versionValue) continue;
const removeQuotes = (v) =>
v.trim().replace(",", "").replace(/'/g, "").replace(/"/g, "");
if (versionValue.includes("-"))
return removeQuotes(versionValue.split("-")[0]);
return removeQuotes(versionValue);
}
}
exports.generateChangelog = async () => {
try {
console.log("started concatenating files");
const files = await fs.readdir("./generators/changelog/versions", "utf8");
const filesStream = files
.map((file) => {
console.log(`reading file: ${file}`);
return {
fileName: file,
buf: fs.readFile(`./generators/changelog/versions/${file}`, "utf8"),
};
})
.map(async ({ buf, fileName }, index) => {
// first file we don't do anything
// Big file and does not follow the new standard
if (index === 0) return buf;
const content = (await buf).toString();
/**
* @type {Set<string>}
*/
const contribuitors = new Set();
// DSL Replacers
// [PR #123] -> [PR #123](https://github.com/meteor/meteor/pull/123)
// [GH meteor/meteor] -> [meteor/meteor](https://github.com/meteor/meteor)
// package-name@get-version -> package-name@1.3.3
const file = content
.replace(
/\[PR #(\d+)\]/g,
(_, number) =>
`[PR](https://github.com/meteor/meteor/pull/${number})`
)
.replace(/\[GH ([^\]]+)\]/g, (_, name) => {
contribuitors.add(name);
return `[${name}](https://github.com/${name})`;
})
.replace(
/([a-z0-9-]+)@get-version/g,
(_, name) => `${name}@${getPackageVersion(name)}`
);
// already have the contribuitors thanks in the file
if (
file.includes("#### Special thanks to") ||
file.includes("[//]: # (Do not edit this file by hand.)")
)
return file;
// add the contribuitors
const contribuitorsList = Array.from(contribuitors)
.map((name) => `- [@${name}](https://github.com/${name}).`)
.join("\n");
const doneFile = `${file}\n\n#### Special thanks to\n\n${contribuitorsList}\n\n`;
//SIDE EFFECTS
// so that this is not ran every time, we will update the last file.
// this is for the expensive part of the script
if (index === files.length - 2)
await fs.writeFile(
`./generators/changelog/versions/${fileName}`,
doneFile
);
return doneFile;
})
.reverse();
console.log("Giving some touches to the files");
const filesContent = await Promise.all(filesStream);
await fs.writeFile("./history.md", filesContent.join(""));
console.log("Finished :)");
} catch (e) {
console.log(e);
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
## v2.10.0, 2023-01-13
### Highlights
* Update skeletons to use React 18 [PR](https://github.com/meteor/meteor/pull/12419) by [StorytellerCZ](https://github.com/StorytellerCZ).
* Use MongoDB types instead of the homebuilt [PR](https://github.com/meteor/meteor/pull/12415) by [perbergland](https://github.com/perbergland).
* Fixing wrong type definitions in MongoDB package [PR](https://github.com/meteor/meteor/pull/12409) by [ebroder](https://github.com/ebroder).
* Typescript to version v4.7.4 [PR](https://github.com/meteor/meteor/pull/12393) by [StorytellerCZ](https://github.com/StorytellerCZ).
* Update test-in-browser dependencies [PR](https://github.com/meteor/meteor/pull/12384) by [harryadel](https://github.com/harryadel).
* Update boilerplate-generator-tests [PR](https://github.com/meteor/meteor/pull/12429) by [harryadel](https://github.com/harryadel).
* Replace double-ended-queue with denque [PR](https://github.com/meteor/meteor/pull/12430) by [harryadel](https://github.com/harryadel).
* Allow multiple runtime config and updated runtime hooks [PR](https://github.com/meteor/meteor/pull/12426) by [ebroder](https://github.com/ebroder).
* Added async forEach and clear for method Hooks [PR](https://github.com/meteor/meteor/pull/12427) by [Grubba27](https://github.com/Grubba27).
* Implemented async Tracker with explicit values [PR](https://github.com/meteor/meteor/pull/12294) by [radekmie](https://github.com/radekmie).
* Improved eslint config [PR](https://github.com/meteor/meteor/pull/12309) by [afrokick](https://github.com/afrokick).
#### Breaking Changes
N/A
#### Internal API changes
N/A
#### Migration Steps
N/A
#### Meteor Version Release
* `babel-compiler@7.10.2`:
- Updated @meteorjs/babel to version 7.18.0.
- Updated to typescript to version v4.7.4.
* `boilerplate-generator-tests@1.5.1`:
- Updated parse5 and turned streamToString into a local function.
* `callback-hook@1.5.0`
- Added forEachAsync.
* `ecmascript@0.16.5`
- Updated typescript to version 4.7.4.
* `Command line`:
- Updated React skeletons to use React 18
* `Meteor@1.11.0`:
- Replaced double-ended-queue with [denque](https://github.com/invertase/denque)
* `mongo@1.16.4`:
- Fixed wrong type definitions.
- switch to using MongoDB types instead of the homebuilt.
- Fixed wrong type definitions in MongoDB package related to dropIndexAsync
* `react-fast-refresh@0.2.5`:
- Updated react-refresh dependency.
* `test-in-browser@1.3.3`:
- Updated dependencies and removed unused libs.
* `Tracker@1.3.0`:
- Implemented async Tracker with explicit values
* `typescript@4.7.4`
- Updated typescript to version 4.7.4.
* `webapp@1.13.3`
- The forEach method on Hook will stop iterating unless the iterator function returns a truthy value.
Previously, this meant that only the first registered runtime config hook would be called.
* `@meteorjs/babel@7.18.0-beta.5`
- Updated typescript to version 4.7.4.
#### Special thanks to
- [@StorytellerCZ](https://github.com/StorytellerCZ).
- [@perbergland](https://github.com/perbergland).
- [@ebroder](https://github.com/ebroder).
- [@harryadel](https://github.com/harryadel).
- [@radekmie](https://github.com/radekmie).
- [@Grubba27](https://github.com/Grubba27).
- [@afrokick](https://github.com/afrokick).
For making this great framework even better!

View File

@@ -0,0 +1,137 @@
## v2.11.0, 2023-03-02
### Highlights
* MongoDB Server 6.x Support
* Embedded Mongo now uses MongoDB 6.0.3
* Optimized makeLookupFunction
by [radekmie](https://github.com/radekmie) [PR](https://github.com/meteor/meteor/pull/12462)
* In async wrappers, catch exceptions and reject
by [ebroder](https://github.com/ebroder) [PR](https://github.com/meteor/meteor/pull/12469)
* Bump Typescript to v4.9.4 by [Firfi](https://github.com/Firfi) [PR](https://github.com/meteor/meteor/pull/12465)
* Ensure the meteor.loginServiceConfiguration subscription always becomes ready
by [Torgen](https://github.com/Torgen) [PR](https://github.com/meteor/meteor/pull/12480)
* Deprecate appcache package
by [StorytellerCZ](https://github.com/StorytellerCZ) [PR](https://github.com/meteor/meteor/pull/12456)
* Made standard-minifier-css debuggable
by [softwarecreations](https://github.com/softwarecreations) [PR](https://github.com/meteor/meteor/pull/12478).
* Upgrading MongoDB Driver to v4.14
by [Grubba27](https://github.com/Grubba27) [PR](https://github.com/meteor/meteor/pull/12501)
* Remove Blaze dependency and types that live in blaze.d.ts
by [perbergland](https://github.com/perbergland) [PR](https://github.com/meteor/meteor/pull/12428)
* Switch typescript skeleton to zodern:types and test that it works by [GH ebroder] [PR #12510]
* Remove packages/*/.npm from gitignore and add missing .npm folders by [GH ebroder] [PR #12508]
* Add type definitions for async methods from Meteor 2.9 by [GH ebroder] [PR #12507]
* TypeScript skeleton fixes by [GH ebroder] [PR #12506]
* Fix TypeScript type dependencies for mongo, webapp, and underscore by [GH ebroder] [PR #12505]
* Improve specificity of types previously declared as "Object" by [GH ebroder] [PR #12520]
* Bump to Node 14.21.3 by [GH StorytellerCZ] [PR #12517]
#### Breaking Changes
`meteor mongo` command was removed due compatibility with MongoDB v6.x of `mongosh
for more information about MongoDB migration
read our [Migration Guide](https://guide.meteor.com/2.11-migration.html) for this version.
#### Internal API changes
App cache is now deprecated.
#### Migration Steps
Read our [Migration Guide](https://guide.meteor.com/2.11-migration.html) for this version.
#### Meteor Version Release
* `accounts-2fa@2.0.2`:
- removed .npm/package contents and added .gitignore
* `accounts-base@2.2.7`:
- Updated types to match async methods added in newer versions.
- Ensured the meteor.loginServiceConfiguration subscription always becomes ready, by adding a this.ready() call.
- Specified that previously were declared as "Object" types. More context can be seen in [PR #12520].
* `accounts-password@2.3.4`:
- Updated `Accounts.changePassword` and `Accounts.resetPassword` to be correctly verify if the new password is
valid.
- removed .npm/package contents and added .gitignore
* `appcache@1.2.8`
- Deprecated appcache
package. [applicationCache](https://developer.mozilla.org/en-US/docs/Web/API/Window/applicationCache), which this
package relies on, has been deprecated and is not available on the latest browsers.
* `babel-compiler@7.10.3`:
- Updated typescript to version 4.9.4.
* `ecmascript@0.16.6`:
- Updated typescript to version 4.9.4.
* `email@2.2.4`:
- Updated types to match async methods added in newer versions.
- Specified that previously were declared as "Object" types. More context can be seen in [PR #12520].
* `logging@1.3.2`:
- removed .npm/package contents and added .gitignore
* `Command line`:
- Corrected typo in vue skeleton.
- Command `meteor mongo` was removed due compatibility with MongoDB v6.x of `mongosh`
* `meteor@1.11.1`:
- updated types to removed unused Blaze types
- Specified that previously were declared as "Object" types. More context can be seen in [PR #12520].
* `minimongo@1.9.2`:
- Updated performance of makeLookupFunction
- In async wrappers, catch exceptions and reject
* `mongo@1.16.5`:
- In async wrappers, catch exceptions and reject
- Updated MongoDB types to match driver version 4.13.0 and MongoDB server version 6.0.3
- Specified that previously were declared as "Object" types. More context can be seen in [PR #12520].
- Now uses MongoDB v6.0.3
- Now uses Node v14.21.3
* `npm-mongo@4.14.0`:
- Updated MongoDB driver to version 4.14.0
* `oauth@2.2.0`:
- bumped cordova-plugin-inappbrowser to 5.0.0
* `react-fast-refresh@0.2.6`:
- removed .npm/package contents and added .gitignore
* `standard-minifier-css@1.9.0`:
- standard-minifier-css is now debuggable
* `tracker@1.3.1`:
- Added missing withComputation method in types
* `typescript@4.9.4`
- Updated typescript to version 4.9.4.
* `underscore@1.0.12`:
- Added dependency in types to underscore
* `webapp@1.13.4`:
- Added dependency in types to webapp(to connect)
- removed .npm/package contents and added .gitignore
* `@meteorjs/babel@7.18.0-beta.6`
- Updated typescript to version 4.9.4.
#### Special thanks to
- [@radekmie](https://github.com/radekmie).
- [@ebroder](https://github.com/ebroder).
- [@Firfi](https://github.com/Firfi).
- [@Torgen](https://github.com/Torgen).
- [@StorytellerCZ](https://github.com/StorytellerCZ).
- [@softwarecreations](https://github.com/softwarecreations).
- [@Grubba27](https://github.com/Grubba27).
For making this great framework even better!

View File

@@ -0,0 +1,150 @@
## v2.12.0, 2023-04-28
### Highlights
* Document main function in webapp by [harryadel](https://github.com/harryadel) [PR](https://github.com/meteor/meteor/pull/12579)
* Add undocument properties to docs by [harryadel](https://github.com/harryadel) [PR](https://github.com/meteor/meteor/pull/12563)
* Bump NPM versions for css minifiers by [wreiske](https://github.com/wreiske) [PR](https://github.com/meteor/meteor/pull/12562)
* Updated Email and Mongo package types by [ebroder](https://github.com/ebroder) [PR](https://github.com/meteor/meteor/pull/12554)
* Updated security.md by [jamauro](https://github.com/jamauro) [PR](https://github.com/meteor/meteor/pull/12461)
* Added addHtmlAttributeHook type on WebApp by [DblK](https://github.com/DblK) [PR](https://github.com/meteor/meteor/pull/12545)
* Added loginServiceConfiguration type on Accounts by [DblK](https://github.com/DblK) [PR](https://github.com/meteor/meteor/pull/12539)
* Add TS types for Mongo Collection countDocuments and estimatedDocumentCount by [ArthurHoaro](https://github.com/ArthurHoaro) [PR](https://github.com/meteor/meteor/pull/12533)
* Allow setting a custom ddp-rate-limit message per rule by [wreiske](https://github.com/wreiske) [PR](https://github.com/meteor/meteor/pull/12082)
* Updated MongoDB driver to 4.15 by [Grubba27](https://github.com/Grubba27) [PR](https://github.com/meteor/meteor/pull/12583)
* Adding warn with env variable when using old apis vy [Grubba27](https://github.com/Grubba27) [PR](https://github.com/meteor/meteor/pull/12585)
* Fix syntax for legacy client by [zodern](https://github.com/zodern) [PR](https://github.com/meteor/meteor/pull/12596)
* Updating MongoDB driver to 4.16 by [Grubba27](https://github.com/Grubba27) [PR](https://github.com/meteor/meteor/pull/12599)
* Update sockjs-client by [harryadel](https://github.com/harryadel) [PR](https://github.com/meteor/meteor/pull/12590)
* [Accounts] set custom collection by [dmromanov](https://github.com/dmromanov) [PR](https://github.com/meteor/meteor/pull/12591)
* Wrappers to help in the async migration by [matheusccastroo](https://github.com/matheusccastroo) [PR](https://github.com/meteor/meteor/pull/12593)
* Mongo query hangs all clients subscribed to a query/collection by [KoenLav](https://github.com/KoenLav) [PR](https://github.com/meteor/meteor/pull/12587)
* Blaze to 2.6.2 by [StorytellerCZ](https://github.com/StorytellerCZ) [PR](https://github.com/meteor/blaze/pull/411)
#### Breaking Changes
N/A
#### Internal API changes
N/A
#### Migration Steps
Now if you want to check where do you call old-style api methods
you can use ```WARN_WHEN_USING_OLD_API``` before starting your meteor process.
#### Meteor Version Release
* `accounts-base@2.2.8`:
- Added `loginServiceConfiguration` type.
- Added the `collection` option property, in order to be able to set the collection for Accounts,
more can be seen in the [discussion](https://github.com/meteor/meteor/discussions/12544#discussioncomment-5240763)
and in the [related issue](https://github.com/meteor/meteor-feature-requests/issues/20).
- `onCreateUserHook` now accept promises and wait if necessary.
* `babel-compiler@7.10.4`:
- Added `es5` compatible syntax.
* `browser-policy-content@1.1.2`:
- Added `es5` compatible syntax.
* `browser-policy-framing@1.1.2`:
- Added `es5` compatible syntax.
* `browser-policy@1.1.2`:
- Updated test name.
* `callback-hook@1.5.1`:
- Added async hooks possibility to make async migrations easier.
* `context@0.5.1`:
- Added `es5` compatible syntax.
* `ddp-rate-limiter@1.2.0`:
- Allow setting a custom ddp-rate-limit message per rule.
* `ddp-server@2.6.1`:
- Updated sockjs version.
* `dev-error-overlay@0.1.2`:
- Added `es5` compatible syntax by adding the `ecmascript` package.
* `dynamic-import@0.7.3`:
- Added `es5` compatible syntax.
* `ecmascript@0.16.7`:
- Updated tests location.
* `ecmascript-runtime@0.8.1`:
- Updated npm dependencies.
* `email@2.2.5`:
- Updated type `CustomEmailOptions` to be a type instead of an interface.
* `hot-module-replacement@0.5.3`:
- Added `es5` compatible syntax.
* `meteor@1.11.2`:
- Added documentation for `isTest`, `isAppTest` and `isPackageTest` methods.
- Added possibility to add async hooks to make async migrations easier. [PR](https://github.com/meteor/meteor/pull/12593)
* `minifier-css@1.6.4`:
- Bump NPM versions for css minifiers.
* `minimongo@1.9.3`:
- Updated to be able to track old api usage.
* `modules-runtime-hot@0.14.2`:
- Added `es5` compatible syntax.
* `mongo@1.16.6`:
- Added `countDocuments` and `estimatedDocumentCount` types.
- Added warning for when old style apis are being used, to use this feature,
use the variable`WARN_WHEN_USING_OLD_API=true` before starting the Meteor process.
- Oplog driver updated to not throw error when MongoDB server and Meteor client mismatch. [issue](https://github.com/meteor/meteor/issues/12516)
* `non-core`:
- Blaze to version 2.6.2.
* `npm-mongo@4.16.0`:
- Updated MongoDB driver to 4.15.
- Updated MongoDB driver to 4.16.
* `rate-limit@1.1.1`:
- Added `ruleId` property that will be used for setting messages.
* `react-fast-refresh@0.2.7`:
- Added `es5` compatible syntax.
* `socket-stream-client@0.5.0`:
- Updated sockjs version.
* `standard-minifier-css@1.9.2`:
- Bump NPM versions for css minifiers.
* `tracker@1.3.2`:
- Updated types and updated JSDocs for `Tracker.withComputation`.
* `underscore@1.0.13`:
- Updated npm dependencies.
* `webapp@1.13.5`:
- Added `addHtmlAttributeHook` type.
#### Special thanks to
- [@harryadel](https://github.com/harryadel).
- [@wreiske](https://github.com/wreiske).
- [@ebroder](https://github.com/ebroder).
- [@jamauro](https://github.com/jamauro).
- [@DblK](https://github.com/DblK).
- [@ArthurHoaro](https://github.com/ArthurHoaro).
- [@Grubba27](https://github.com/Grubba27).
- [@zodern](https://github.com/zodern).
- [@dmromanov](https://github.com/dmromanov).
- [@matheusccastroo](https://github.com/matheusccastroo).

View File

@@ -0,0 +1,75 @@
## v2.13.0, 2023-07-26
### Highlights
* Handled implicit collection creation oplog message by [radekmie](https://github.com/radekmie) [PR](https://github.com/meteor/meteor/pull/12643).
* Fix upsert logs when using WARN_WHEN_USING_OLD_API flag by [Grubba27](https://github.com/Grubba27) [PR](https://github.com/meteor/meteor/pull/12640).
* Updating mongo types by [Grubba27](https://github.com/Grubba27) [PR](https://github.com/meteor/meteor/pull/12639).
* Fix solid skeleton by [fredmaiaarantes](https://github.com/fredmaiaarantes) [PR](https://github.com/meteor/meteor/pull/12637).
* Setting The Viewport meta tag on skeletons [fredmaiaarantes](https://github.com/fredmaiaarantes) [PR](https://github.com/meteor/meteor/pull/12636).
* Update mongo.d.ts with projection [StorytellerCZ](https://github.com/StorytellerCZ) [PR](https://github.com/meteor/meteor/pull/12635).
* Update guide code for GraphQL [StorytellerCZ](https://github.com/StorytellerCZ) [PR](https://github.com/meteor/meteor/pull/12619).
* Twitter Whitelist issue resolved [Atharshoyeb](https://github.com/Atharshoyeb) [PR](https://github.com/meteor/meteor/pull/12369).
* Node security patch (14.21.4) [PR](https://github.com/meteor/node-v14-esm/pull/1). Thanks a lot [denihs](https://github.com/denihs) for your contribuiton.
* Updated deprecated reference in mongo package by [StorytellerCZ](https://github.com/StorytellerCZ) [PR](https://github.com/meteor/meteor/pull/12653/files).
* Updated BlazeJS git ref in core meteor to 2.7.1 by [Grubba27](https://github.com/Grubba27) [PR](https://github.com/meteor/meteor/pull/12651).
* Added `Meteor.applyAsync` types by [Julusian](https://github.com/Julusian) [PR](https://github.com/meteor/meteor/pull/12645).
#### Breaking Changes
If you are running Meteor with docker you will
need to update your docker file to use our [new docker image](https://hub.docker.com/r/meteor/node)
that contains Nodejs v14.21.4.
#### Known issues
Please, [check our known issues page](https://docs.meteor.com/known-issues)
for more information about the problems and issues you might find while migrating.
#### Internal changes
* `ddp-server@get-version`:
- Updated livedata server test to be more easily debbuged.
* `mongo@get-version`:
- Updated deprecated reference in Mongo package.
#### Migration Steps
Please, follow our [migration guide](https://guide.meteor.com/2.13-migration) to understand what's needed to upgrade to Meteor 2.13.
#### Meteor Version Release
* `Command line`:
- Updated metatags for skeletons.
- Updated solidjs skeleton to be more idiomatic.
* `meteor@1.11.3`:
- Added types for applyAsync and added more documentation for applyAsync options.
* `mongo@1.16.7`:
- Updated types with projection.
- Fixed wrong upsert logs when using WARN_WHEN_USING_OLD_API flag.
- Handled implicit collection creation oplog message.
* `test-in-console@1.2.5`:
- Adjusted log indentation.
- All errors will be logged to console.
- Will always use puppeteer@20.4.0
* `twitter-oauth@1.3.3`:
- Fixed twitter whitelist issue.
#### Special thanks to
- [@radekmie](https://github.com/radekmie).
- [@Grubba27](https://github.com/Grubba27).
- [@fredmaiaarantes](https://github.com/fredmaiaarantes).
- [@StorytellerCZ](https://github.com/StorytellerCZ).
- [@Atharshoyeb](https://github.com/Atharshoyeb).
- [@Julusian](https://github.com/Julusian).
- [@denihs](https://github.com/denihs).

View File

@@ -0,0 +1,40 @@
## v2.13.1, 2023-09-04
### Highlights
* Solved zlib issue with Meteor.js and ESM Node.js 14.21.4 [PR #12765] by (GH Grubba27).
#### Breaking Changes
N/A
#### Internal API changes
N/A
#### Migration Steps
Please run the following command to update your project:
```bash
meteor update --release 2.13.1
```
#### Meteor Version Release
* `Command line`:
- The bundle version was changed to 14.21.4.1 to use another compiled version of the [ESM Node.js](https://guide.meteor.com/using-node-v14.21.4).
#### Special thanks to
- [@Grubba27](https://github.com/Grubba27).
For making this great framework even better!

View File

@@ -0,0 +1,41 @@
## v2.13.3, 2023-09-08
### Highlights
* Solves the issue [#12771: Version 2.13.1 suddenly requires a newer glibc version](https://github.com/meteor/meteor/issues/12771).
#### Breaking Changes
N/A
#### Internal API changes
N/A
#### Migration Steps
Please run the following command to update your project:
```bash
meteor update --release 2.13.3
```
#### Meteor Version Release
* `Command line`:
- The bundle version was changed to 14.21.4.3 to use another compiled version of the [ESM Node.js](https://guide.meteor.com/using-node-v14.21.4). The previous version was generated using a different unix distribution (Ubuntu) while we should use CentOS.
#### Special thanks to
- [@aquinoit](https://github.com/aquinoit).
- [@fredmaiaarantes](https://github.com/fredmaiaarantes).
- [@Grubba27](https://github.com/Grubba27).
For making this great framework even better!

View File

@@ -0,0 +1,711 @@
## v3.0, TBD
### Highlights
#### Breaking Changes
- `accounts-2fa@3.0.0`:
- Some methods are now async. See below:
- `Accounts._is2faEnabledForUser`
- `(Meteor Method) - generate2faActivationQrCode`
- `(Meteor Method) - enableUser2fa`
- `(Meteor Method) - disableUser2fa`
- `(Meteor Method) - has2faEnabled`
- `accounts-base@3.0.0`:
- `methods.removeOtherTokens` is now async
- `Accounts.destroyToken` is now async
- `Accounts.insertUserDoc` is now async
- `Accounts.updateOrCreateUserFromExternalService` is now async
- `Accounts.expirePasswordToken` is now async
- `Accounts.setupUsersCollection` is now async
- `Meteor.user` is now async in server
- `accounts-facebook@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-github@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-google@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-meetup@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-meteor-developer@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-password@3.0.0`:
- Some server methods are now async:
- `Accounts.sendResetPasswordEmail`
- `Accounts.sendEnrollmentEmail`
- `Accounts.sendVerificationEmail`
- `Accounts.addEmail`
- `Accounts.removeEmail`
- `Accounts.verifyEmail`
- `Accounts.createUserVerifyingEmail`
- `Accounts.createUser`
- `Accounts.generateVerificationToken`
- `Accounts.generateResetToken`
- `Accounts.forgotPassword`
- `Accounts.setPassword`
- `Accounts.changePassword`
- `Accounts.setUsername`
- `Accounts.findUserByEmail`
- `Accounts.findUserByUsername`
- `accounts-passwordless@3.0.0`:
- `Accounts.sendLoginTokenEmail` is now async.
- `accounts-twitter@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-ui-unstyled@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `accounts-weibo@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `allow-deny@2.0.0`:
- Updated to accept async functions.
- `appcache@2.0.0`:
- Updated internal api to use `handlers`
- `audit-argument-checks@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `autopublish@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `autoupdate@2.0.0`:
- Updated api to be async, with asyncronous queueing.
- `babel-compiler@8.0.0`:
- Removed `Promise.await` default transform.
- Added top-level-await to packages.
- `babel-runtime@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `base64@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `binary-heap@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `blaze@3.0.0`:
- Todo
- `boilerplate-generator-tests@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `boilerplate-generator@2.0.0`:
- `toHTML` is no longer available (it was already deprecated). Use `toHTMLStream` instead.
- Updated to use `handlers`
- `browser-policy-common@2.0.0`:
- Updated to use `handlers`
- `browser-policy-content@2.0.0`:
- Some methods are now async. See below:
- `BrowserPolicy.content.setPolicy`
- `BrowserPolicy.content.allowInlineScripts`
- `BrowserPolicy.content.disallowInlineScripts`
- `BrowserPolicy.content.disallowAll`
- `BrowserPolicy.setDefaultPolicy`
- `browser-policy-framing@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `browser-policy@2.0.0`:
Updated to use async methods from `browser-policy-common` and `browser-policy-content`.
- `caching-compiler@2.0.0`:
- `afterLink` is now async.
- Updated to use now async API.
- `callback-hook@2.0.0`:
- Added `forEachAsync` method.
- `check@2.0.0`:
- Removed `fibers` related tests.
- `constraint-solver@2.0.0`:
- Some methods are now async. See below:
- `ConstraintSolver.getVersionCostSteps`
- `ConstraintSolver.analyze`
- `ConstraintSolver.resolve`
- Updated tests to be async.
- Removed a few underscore usage.
- Added updated to use async methods
- `context@1.0.0`:
- Removed `fibers` from package.
- `core-runtime@2.0.0`:
- Created package to load packages and the app.
- This is the pakcages that sets up the Runtime.
- `crosswalk@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `ddp-client@3.0.0`:
- Added `isAsyncCall` method to know if call is being made by a async method.
- Removed `fibers` from package.
- Updated tests to use async methods.
- `ddp-common@2.0.0`:
- Added `.fence` option.
- `ddp-rate-limiter@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `ddp-server@3.0.0`:
- Updated to use async methods.
- Removed `fibers` from package.
- Updated tests to use async methods.
- Turned server implementation to async.
- `ddp@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `diff-sequence@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `disable-oplog@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `ecmascript-runtime-client@1.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `ecmascript-runtime-server@1.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `ecmascript-runtime@1.0.0`:
- Added dependency to `@babel/runtime`.
- `ecmascript@1.0.0`:
- Added dependency to `@babel/runtime`.
- Moved runtime tests.
- `ejson@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `email@3.0.0`:
- `Email.send` is no longer available. Use `Email.sendAsync` instead.
- Updated types to reflext async methods and `Email.send` depracation.
- `es5-shim@5.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `facebook-config-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `facebook-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `facts-base@2.0.0`:
- turned unorderd deps on `ddp` to false.
- `facts-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `fetch@1.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `force-ssl-common@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `force-ssl@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `geojson-utils@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `github-config-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `github-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `google-config-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `google-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `hot-code-push@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `http@`:
- Updated handlers to use `handlers`
- `id-map@2.0.0`:
- Added `forEachAsync` method.
- `insecure@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `inter-process-messaging@1.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `launch-screen@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `localstorage@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `logging@2.0.0`:
- Added dependency to `@babel/runtime`.
- `logic-solver@3.0.0`:
`Logic.disablingAssertions` is now async.
`minMaxWS` is now async.
- `meetup-config-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `meetup-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `meteor-base@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `meteor-developer-config-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `meteor-developer-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `meteor-tool@3.0.0`:
- Changes to how meteor apps are being created [PR](https://github.com/meteor/meteor/pull/12697)
- `meteor@2.0.0`:
- Async local storage was added to help deal with async methods.
- Added `promiseEmmiter` to help with async methods.
- Removed `fibers` from package.
- `Meteor.absoluteUrl` in localhost uses `127.0.1` by default.
- `minifier-css@2.0.0`:
- `minifyCss` is now async.
- Removed `fibers` from package.
- `minifier-js@3.0.0`:
- `minifyJs` is now async.
- `terserMinify` no longer takes callbacks
- Removed `fibers` from package.
* `minimongo@2.0.0`:
- `cursor.observe` now returns `isReady` and `isReadyPromise` wich indicates
if the cursor is ready and if the callbacks are have been called.
If you only use it in the `Client` or as a `LocalCollection` things have not
changed.
- `mobile-experience@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `mobile-status-bar@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `modern-browsers@1.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `modules-runtime@1.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `modules@1.0.0`:
- Updated `reify` version.
- `mongo-decimal@`:
- Updated to use `async` methods.
- `mongo-dev-server@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `mongo-id@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `mongo-livedata@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `mongo@2.0.0`:
- Updated to unify methods, `update`,`insert`,`remove`, `fetch` are now async, they are
the same as their `*Async` counterpart.
- `ensureIndex` and `createIndex` are now async.
- `npm-mongo@5.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `oauth-encryption@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `oauth@`:
- `_endOfPopupResponseTemplate` and `_endOfRedirectResponseTemplate` are no longer a property but now a function that returns a promise of the same value as before
- the following server methods are now async:
- `OAuth._renderOauthResults`
- `OAuth._endOfLoginResponse`
- `OAuth.renderEndOfLoginResponse`
- `OAuth._storePendingCredential`
- `OAuth._retrievePendingCredential`
- `ensureConfigured`
- `_cleanStaleResults`
- `oauth@3.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `oauth1@`:
- the following server methods are now async:
- `OAuth._storeRequestToken`
- `OAuth._retrieveRequestToken`
- `oauth1@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `oauth2@`:
- `OAuth._requestHandlers['2']` is now async.
- `oauth2@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `ordered-dict@2.0.0`:
- Added `forEachAsync` method.
- `package-stats-opt-out@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `package-version-parser@4.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `promise@1.0.0`:
- Removed `fibers` usage
- `random@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `rate-limit@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `reactive-dict@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `reactive-var@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `reload-safetybelt@2.0.0`:
- Added `ecmascript` package to `package.js`
- `reload@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `retry@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `routepolicy@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `server-render@1.0.0`:
- Updated usage with `getBoilerplate` that are now `async`.
- `service-configuration@2.0.0`:
- Updated to use `createIndexAsync`.
- `session@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `sha@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `shell-server@1.0.0`:
- Updated to handle promises results.
- `socket-stream-client@1.0.0`:
- Updated tests to handle `async` code.
- `spiderable@`:
- Updated handlers to use `handlers` that are now using express
- removed `fibers` usage if flag is set to `true`
- `standard-minifier-css@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `standard-minifier-js@3.0.0`:
- `processFilesForBundle` is now `async`.
- `standard-minifiers@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `static-html@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `test-helpers@2.0.0`:
- Updated to use `async` methods.
- Removed `fibers` usage.
- Added possibliy to use `async` tests.
- `test-in-browser@2.0.0`:
- Updated css to be in dark mode.
- `test-in-console@2.0.0`:
- Updated log identation.
- `test-server-tests-in-console-once@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `tinytest-harness@1.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `tinytest@2.0.0`:
- Added `test name` to logs.
- Removed `fibers` usage.
- `twitter-config-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `twitter-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `typescript@5.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `underscore-tests@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `underscore@2.0.0`:
- Removed dependency in meteor package.
- `url@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `webapp-hashing@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `webapp@2.0.0`:
- These methods are now async:
- `WebAppInternals.reloadClientPrograms()`
- `WebAppInternals.pauseClient()`
- `WebAppInternals.generateClientProgram()`
- `WebAppInternals.generateBoilerplate()`
- `WebAppInternals.setInlineScriptsAllowed()`
- `WebAppInternals.enableSubresourceIntegrity()`
- `WebAppInternals.setBundledJsCssUrlRewriteHook()`
- `WebAppInternals.setBundledJsCssPrefix()`
- `WebAppInternals.getBoilerplate`
- Changed engine from connect to express and changed api naming to match express. See below:
- `WebApp.connectHandlers.use(middleware)` is now `WebApp.handlers.use(middleware)`
- `WebApp.rawConnectHandlers.use(middleware)` is now `WebApp.rawHandlers.use(middleware)`
- `WebApp.connectApp` is now `WebApp.expressApp`
- `weibo-config-ui@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
- `weibo-oauth@2.0.0`:
- Package was bumped due to a dependency update. No code changes were made.
#### New Public API
- `accounts-base`: (2.9+)
- `Meteor.userAsync()`
- `callback-hook`:forEachAsync
- `forEachAsync`
- `ddp-server`: (2.8+)
- `Meteor.callAsync()`
- `minifier-css`: (2.9+)
- `CssTools.minifyCssAsync()`
- `mongo`:
- `Mongo.Collection`: (2.8+)
- `createCappedCollectionAsync`
- `createIndexAsync`
- `dropCollectionAsync`
- `dropIndexAsync`
- `findOneAsync`
- `insertAsync`
- `removeAsync`
- `updateAsync`
- `upsertAsync`
- `Collection.Cursor`: (2.8+)
- `countAsync`
- `fetchAsync`
- `forEachAsync`
- `mapAsync`
- `[Symbol.asyncIterator]` so this code should work:
```js
for await (const document of collection.find(query, options)) /* ... */
```
#### Internal API changes
`accounts-base`:
- `_attemptLogin`
- `_loginMethod`
- `_runLoginHandlers`
* Upgraded `node-gyp` to v9.4.0
* Upgraded `node-pre-gyp` to `@mapbox/node-pre-gyp` v1.0.11
#### New Internal API
`accounts-password`:
- `Accounts._checkPasswordAsync`
#### Special thanks to
- [@StorytellerCZ](https://github.com/sponsors/StorytellerCZ/)
For making this great framework even better!

View File

@@ -0,0 +1,14 @@
# Changelog
This is a complete history of changes for Meteor releases.
[//]: # (Do not edit this file by hand.)
[//]: # (This is a generated file.)
[//]: # (If you want to change something in this file)
[//]: # (go to meteor/docs/generators/changelog/docs)

View File

@@ -0,0 +1,11 @@
const { generateChangelog } = require("./changelog/script.js");
const { listPackages } = require("./packages-listing/script.js");
async function main() {
console.log("🚂 Started codegen 🚂");
await generateChangelog();
await listPackages();
console.log("🚀 Done codegen 🚀");
}
main();

View File

@@ -0,0 +1,18 @@
# Listing of all meteor core packages
This is a script that will generate a list of all meteor core packages, being ran every build.
This ensures that we always have a list of core packages up to date with their correct links to GitHub.
We can always add packages to the list by adding them to the `script.js` constant `OUTSIDE_OF_CORE_PACKAGES`.
Should follow the following format:
```js
{
name: 'package-name',
link: 'https://link-to-github.com/meteor/meteor/tree/devel/packages/package-name'
}
```
At the end, this script will update the file located in `docs/source/packages/packages-listing.md` with the new list of packages.

View File

@@ -0,0 +1,55 @@
const fs = require("fs");
const HEADER_TEMPLATE = `
[//]: # (Do not edit this file by hand.)
[//]: # (This is a generated file.)
[//]: # (If you want to change something in this file)
[//]: # (go to meteor/docs/generators/packages-listing)
# Core Packages
`;
const OUTSIDE_OF_CORE_PACKAGES = [
{
name: "blaze",
link: "https://github.com/meteor/blaze",
},
{
name: "react-packages",
link: "https://github.com/meteor/react-packages",
},
];
const IGNORED = ["depracated", "non-core"];
const getPackages = () => {
const packages = fs
.readdirSync("../../packages", { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
.filter((name) => !IGNORED.includes(name))
.map((name) => {
return {
name,
link: `https://github.com/meteor/meteor/tree/devel/packages/${name}`,
};
});
return [...OUTSIDE_OF_CORE_PACKAGES, ...packages];
};
const generateMarkdown = (packages) =>
packages.map(({ name, link }) => `### [${name}](${link}) {#${name}}`).join("\n");
exports.listPackages = function() {
console.log("🚂 Started listing 🚂");
const packages = getPackages();
const markdown = generateMarkdown(packages);
const content = HEADER_TEMPLATE + markdown;
console.log("📝 Writing to file 📝");
fs.writeFileSync("./api/packages-listing.md", content);
console.log("🚀 Done package listing 🚀");
}

2951
v3-docs/docs/history.md Normal file

File diff suppressed because it is too large Load Diff

52
v3-docs/docs/index.md Normal file
View File

@@ -0,0 +1,52 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
text: "Meteor Docs"
tagline: "All the documentation you need to build and deploy your app with Meteor."
image:
src: /logo.png
alt: Meteor
actions:
- theme: brand
text: View API Docs
link: /about/what-is
- theme: alt
text: View on GitHub
link: https://github.com/meteor/meteor
# TODO: Update with Docs features and links as they are added
features:
- title: Zero Config
icon: 🛠️
details: Use popular frameworks and tools right out-of-the-box. Focus on building features instead of configuring tools.
- title: Front-end Agnostic
icon: 🎨
details: Choose your preferred front-end framework like React, VueJS, Blaze, Svelte, or Solid.
- title: RPCs API
icon: 📡
details: Easily connect back-end and front-end with Methods. Our Remote Procedure Call (RPC) system
- title: Easy to Deploy
icon: 🚀
details: Deploy using one command from the CLI or directly from your Git repository by using Meteor Cloud.
- title: Real-time Features
icon: ⚡
details: Develop apps with real-time features like chats and collaborative apps with ease by using publications and subscriptions.
- title: TypeScript Support
icon: 📝
details: Experience the power of TypeScript inference to boost productivity for your full-stack application.
- title: Built-in Accounts
icon: 👤
details: Login and Accounts package ready to use with your app. Never rebuilt an authentication system again.
- title: Pioneering and Reliable
icon: 🏆
details: Developed for over a decade and trusted by industry giants. Alows you to build and scale efficiently.
---

View File

@@ -0,0 +1,165 @@
/*global require: true */
(function () {
'use strict';
// This file receives data from JSDoc via the `publish` exported function,
// and converts it into JSON that is written to a file.
var fs = require('jsdoc/fs');
var helper = require('jsdoc/util/templateHelper');
var stringify = require("canonical-json");
// This is the big map of name -> data that we'll write to a file.
var dataContents = {};
// List of just the names, which we'll also write to a file.
var names = [];
/**
* Get a tag dictionary from the tags field on the object, for custom fields
* like package
* @param {JSDocData} data The thing you get in the TaffyDB from JSDoc
* @return {Object} Keys are the parameter names, values are the values.
*/
var getTagDict = function (data) {
var tagDict = {};
if (data.tags) {
data.tags.forEach(function (tag) {
tagDict[tag.title] = tag.value;
});
}
return tagDict;
};
// Fix up a JSDoc entry and add it to `dataContents`.
var addToData = function (entry) {
Object.assign(entry, getTagDict(entry));
// strip properties we don't want
entry.comment = undefined;
entry.___id = undefined;
entry.___s = undefined;
entry.tags = undefined;
// generate `.filepath` and `.lineno` from `.meta`
if (entry.meta && entry.meta.path) {
var packagesFolder = 'packages/';
var index = entry.meta.path.indexOf(packagesFolder);
if (index != -1 && !entry.isprototype) {
var fullFilePath = entry.meta.path.substr(index + packagesFolder.length) + '/' + entry.meta.filename;
entry.filepath = fullFilePath;
entry.lineno = entry.meta.lineno;
}
}
entry.meta = undefined;
if (!entry.importfrompackage && entry.filepath) {
entry.module = entry.filepath.split('/')[0];
} else {
entry.module = entry.importfrompackage;
}
names.push(entry.longname);
dataContents[entry.longname] = entry;
};
/**
Entry point where JSDoc calls us. It passes us data in the form of
a TaffyDB object (which is an in-JS database of sorts that you can
query for records.
@param {TAFFY} taffyData See <http://taffydb.com/>.
@param {object} opts
@param {Tutorial} tutorials
*/
exports.publish = function(taffyData) {
var data = helper.prune(taffyData);
var namespaces = helper.find(data, {kind: "namespace"});
// prepare all of the namespaces
namespaces.forEach(function (namespace) {
if (namespace.summary) {
addToData(namespace);
}
});
var properties = helper.find(data, {kind: "member"});
properties.forEach(function (property) {
if (property.summary) {
addToData(property);
}
});
// Callback descriptions are going to be embedded into Function descriptions
// when they are used as arguments, so we always attach them to reference
// them later.
var callbacks = helper.find(data, {kind: "typedef"});
callbacks.forEach(function (cb) {
delete cb.comment;
addToData(cb);
});
var functions = helper.find(data, {kind: "function"});
var constructors = helper.find(data, {kind: "class"});
// we want to do all of the same transformations to classes and functions
functions = functions.concat(constructors);
// insert all of the function data into the namespaces
functions.forEach(function (func) {
if (! func.summary) {
// we use the @summary tag to indicate that an item is documented
return;
}
func.options = [];
var filteredParams = [];
// Starting a param with `options.` makes it an option, not a
// param. Dot (`.`) in this case binds tighter than comma, so
// `options.foo,bar` will create an option named `foo, bar`
// (representing two options in the docs). We process pipes so
// that `options.foo|bar` also results in `foo, bar`.
(func.params || []).forEach(function (param) {
param.name = param.name.replace(/,|\|/g, ", ");
var splitName = param.name.split(".");
if (splitName.length < 2 || splitName[0] !== "options") {
// not an option
filteredParams.push(param);
return;
}
param.name = splitName[1];
func.options.push(param);
});
func.params = filteredParams;
// the entire unparsed doc comment. takes up too much room in the
// data file.
delete func.comment;
addToData(func);
});
// write full docs JSON
var jsonString = stringify(dataContents, null, 2);
var jsString = "export default" + jsonString + ";";
jsString = "// This file is automatically generated by JSDoc; regenerate it with scripts/admin/jsdoc/jsdoc.sh\n" + jsString;
var docsDataFilename = "./v3-docs/docs/data/data.js";
fs.writeFileSync(docsDataFilename, jsString);
// write name tree JSON
jsonString = stringify(names.sort(), null, 2);
var nameTreeFilename= "./v3-docs/docs/data/names.json";
fs.writeFileSync(nameTreeFilename, jsonString);
};
})();

View File

@@ -0,0 +1,22 @@
{
"plugins": ["plugins/markdown"],
"markdown": {
"parser": "gfm"
},
"source": {
"exclude": [
"packages/ddp/sockjs-0.3.4.js",
"packages/test-in-browser/diff_match_patch_uncompressed.js",
"packages/jquery/jquery.js",
"packages/underscore/underscore.js",
"packages/json/json2.js",
"packages/minimongo/minimongo_tests.js",
"tools/node_modules",
"tools/skel-pack/package.js",
"docs",
"guide",
"**/node_modules",
"npm-packages"
]
}
}

19
v3-docs/docs/jsdoc/jsdoc.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
INFINITY=10000
TOPDIR=$(pwd)
METEOR_DIR="../../"
cd "$METEOR_DIR"
# Ensure that jsdoc failure actually makes this script fail.
set -o pipefail
# Call git grep to find all js files with the appropriate comment tags,
# and only then pass it to JSDoc which will parse the JS files.
# This is a whole lot faster than calling JSDoc recursively.
git grep -ialE "@(summary|borrows|namespace|memberof|alias)" | xargs -L ${INFINITY} -t \
"$TOPDIR/node_modules/.bin/jsdoc" \
-t "$TOPDIR/jsdoc/docdata-jsdoc-template" \
-c "$TOPDIR/jsdoc/jsdoc-conf.json" \
2>&1 | grep -v 'WARNING: JSDoc does not currently handle'

2282
v3-docs/docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
v3-docs/docs/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"scripts": {
"generate-jsdoc": "jsdoc/jsdoc.sh",
"codegen": "jsdoc/jsdoc.sh && node ./generators/codegen.js",
"docs:dev": "npm run codegen && vitepress dev",
"docs:build": "npm run codegen && vitepress build",
"docs:preview": "vitepress preview",
"deploy:preview": "npm run docs:build && npm run docs:preview"
},
"devDependencies": {
"canonical-json": "^0.0.4",
"jsdoc": "^4.0.2",
"vitepress": "1.0.0-rc.31"
},
"dependencies": {
"vue-collapsed": "^1.3.0"
}
}

View File

@@ -0,0 +1,222 @@
# accounts-2fa
This package allows you to provide a way for your users to enable 2FA on their accounts, using an authenticator app such as Google Authenticator, or 1Password. When the user is logged in on your app, they will be able to generate a new QR code and read this code on the app they prefer. After that, they'll start receiving their codes. Then, they can finish enabling 2FA on your app, and every time they try to log in to your app, you can redirect them to a place where they can provide a code they received from the authenticator.
To provide codes that are exactly compatible with all other Authenticator apps and services that implements TOTP, this package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), **that** implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)).
> This package is meant to be used with [`accounts-password`](../api/accounts.md#passwords) or [`accounts-passwordless`](./accounts-passwordless.md), so if you don't have either of those in your project, you'll need to add one of them. In the future, we want to enable the use of this package with other login methods, our oauth methods (Google, GitHub, etc...).
## 2FA Activation Flow {#activating-2fa}
The first step, in order to enable 2FA, is to generate a QR code so that the user can scan it in an authenticator app and start receiving codes.
<ApiBox name="Accounts.generate2faActivationQrCode" from="accounts-base"/>
Receives an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called, on success, with a QR code in SVG format, a QR secret, and the URI that can be used to activate the 2FA in an authenticator app,
or a single `Error` argument on failure.
On success, this function will also add an object to the logged user's services object containing the QR secret:
```js
services: {
...
twoFactorAuthentication: {
secret: "***"
}
}
```
Here it's an example on how to call this function:
```js
import { Buffer } from "buffer";
import { Accounts } from 'meteor/accounts-base';
// component
const [qrCode, setQrCode] = useState(null);
<button
onClick={() => {
Accounts.generate2faActivationQrCode("My app name", (err, result) => {
if (err) {console.error("...", err);return;}
const { svg, secret, uri } = result;
/*
the svg can be converted to base64, then be used like:
<img
width="200"
src={`data:image/svg+xml;base64,${qrCode}`}
/>
*/
setQrCode(Buffer.from(svg).toString('base64'));
})
}}
>
Generate a new code
</button>
```
This method can fail throwing the following error:
- "The 2FA is activated. You need to disable the 2FA first before trying to generate a new activation code [2fa-activated]" if trying to generate an activation when the user already have 2FA enabled.
At this point, the 2FA won't be activated just yet. Now that the user has access to the codes generated by their authenticator app, you can call the function `Accounts.enableUser2fa`:
<ApiBox name="Accounts.enableUser2fa" from="accounts-base"/>
It should be called with a code that the users will receive from the authenticator app once they read the QR code. The callback is called with a single `Error` argument on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object and now 2FA is considered enabled:
```js
services: {
...
twoFactorAuthentication: {
type: "otp",
secret: "***",
}
}
```
To verify whether or not a user has 2FA enabled, you can call the function `Accounts.has2faEnabled`:
<ApiBox name="Accounts.has2faEnabled" from="accounts-base"/>
This function must be called when the user is logged in.
## Disabling 2FA {#disabling-2fa}
To disable 2FA for a user use this method:
<ApiBox name="Accounts.disableUser2fa" from="accounts-base"/>
To call this function the user must be already logged in.
## Log in with 2FA {#log-in-with-2fa}
Now that you have a way to allow your users to enable 2FA on their accounts, you can create a login flow based on that.
As said at the beginning of this guide, this package is currently working with two other packages: `accounts-password` and `accounts-passwordless`. Below there is an explanation on how to use this package with them.
## Working with accounts-password {#working-with-accounts-password}
When calling the function `Meteor.loginWithPassword`, if the 2FA is enabled for the user, an error will be returned to the callback, so you can redirect the user to a place where they can provide a code.
As an example:
```js
<button
onClick={() => {
Meteor.loginWithPassword(username, password, (error) => {
if (error) {
if (error.error === "no-2fa-code") {
// send user to a page or show a component
// where they can provide a 2FA code
setShouldAskCode(true);
return;
}
console.error("Error trying to log in (user without 2fa)", error);
}
});
}}
>
Login
</button>
```
If the 2FA is not enabled, the user will be logged in normally.
The function you will need to call now to allow the user to login is `Meteor.loginWithPasswordAnd2faCode`:
<ApiBox name="Meteor.loginWithPasswordAnd2faCode"/>
Now you will be able to receive a code from the user and this function will verify if the code is valid. If it is, the user will be logged in.
So the call of this function should look something like this:
```js
<button
onClick={() => {
Meteor.loginWithPasswordAnd2faCode(username, password, code, (error) => {
if (error) {
console.error("Error trying to log in (user with 2fa)", error);
}
});
}}
>
Validate and log in
</button>
```
This method can fail throwing one of the following errors:
- "2FA code must be informed [no-2fa-code]" if a 2FA code was not provided.
- "Invalid 2FA code [invalid-2fa-code]" if the provided 2FA code is invalid.
## Working with accounts-passwordless {#working-with-accounts-passwordless}
Following the same logic from the previous package, if the 2FA is enabled, an error will be returned to the callback of the function
[`Meteor.passwordlessLoginWithToken`](./accounts-passwordless.md#Meteor-passwordlessLoginWithToken),
then you can redirect the user to a place where they can provide a code.
Here is an example:
```js
<button
onClick={() => {
// logging in just with token
Meteor.passwordlessLoginWithToken(email, token, (error) => {
if (error) {
if (error.error === "no-2fa-code") {
// send user to a page or show a component
// where they can provide a 2FA code
setShouldAskCode(true);
return;
}
console.error("Error verifying token", error);
}
});
}}
>
Validate token
</button>
```
Now you can call the function `Meteor.passwordlessLoginWithTokenAnd2faCode` that will allow you to provide a selector, token, and 2FA code:
<ApiBox name="Meteor.passwordlessLoginWithTokenAnd2faCode"/>
This method can fail throwing one of the following errors:
- "2FA code must be informed [no-2fa-code]" if a 2FA code was not provided.
- "Invalid 2FA code [invalid-2fa-code]" if the provided 2FA code is invalid.
## Integrating an Authentication Package with accounts-2fa {#integrating-auth-package}
To integrate this package with any other existing Login method, it's necessary following two steps:
1 - For the client, create a new method from your current login method. So for example, from the method `Meteor.loginWithPassword` we created a new one called `Meteor.loginWithPasswordAnd2faCode`, and the only difference between them is that the latest one receives one additional parameter, the 2FA code, but we call the same function on the server side.
2 - For the server, inside the function that will log the user in, you verify if the function `Accounts._check2faEnabled` exists, and if yes, you call it providing the user object you want to check if the 2FA is enabled, and if either of these statements are false, you proceed with the login flow. This function exists only when the package `accounts-2fa` is added to the project.
If both statements are true, and the login validation succeeds, you verify if a code was provided: if not, throw an error; if it was provided, verify if the code is valid by calling the function `Accounts._isTokenValid`. if `Accounts._isTokenValid` returns false, throw an error.
Here it's an example:
```js
const result = validateLogin();
if (!result.error && Accounts._check2faEnabled?.(user)) {
if (!code) {
Accounts._handleError("2FA code must be informed.");
}
if (
!Accounts._isTokenValid(user.services.twoFactorAuthentication.secret, code)
) {
Accounts._handleError("Invalid 2FA code.");
}
}
return result;
```

View File

@@ -0,0 +1,39 @@
# Passwordless
Passwordless package allows you to create a login for users without the need for user to provide password. Upon registering or login an email is sent to the user's email with a code to enter to confirm login and a link to login directly. Since the user is responding to the email it will also verify the email.
The first step to in the passwordless process is for the user to sign-up or request a token to their email address. You can do that with the following:
<ApiBox name="Accounts.requestLoginTokenForUser" from="accounts-base"/>
If the user is signing up you can pass in the `userData` object like in [Accounts.createUser](/api/accounts#Accounts-createUser).
<ApiBox name="Meteor.passwordlessLoginWithToken" />
The second step in the passwordless flow. Like all the other `loginWith` functions call this method to login the user with the token they have inputted.
<ApiBox name="Accounts.sendLoginTokenEmail" from="accounts-base" />
Use this function if you want to manually send the email to users to login with token from the server. Do note that you will need to create the token/sequence and save it in the DB yourself. This is good if you want to change how the tokens look or are generated, but unless you are sure of what you are doing we don't recommend it.
<h3 id="config-options">Settings Options</h3>
You can use the function `Accounts.config` in the server to change some settings on this package:
- **tokenSequenceLength**: use `Accounts.config({tokenSequenceLength: _Number_})` to the size of the token sequence generated. The default is 6.
- **loginTokenExpirationHours**: use `Accounts.config({loginTokenExpirationHours: _Number_})` to set the amount of time a token sent is valid. As it's just a number, you can use, for example, 0.5 to make the token valid for just half hour. The default is 1 hour.
<h3 id="passwordless-email-templates">E-mail templates</h3>
`accounts-passwordless` brings new templates that you can edit to change the look of emails which send code to users. The email template is named `sendLoginToken` and beside `user` and `url`, the templates also receive a data object with `sequence` which is the user's code.
```javascript
sendLoginToken: {
text: (user, url, { sequence }) => {
/* text template */
};
}
```
<h3 id="enabling-2fa">Enable 2FA for this package</h3>
You can add 2FA to your login flow by using the package [accounts-2fa](./accounts-2fa.md).
You can find an example showing how this would look like [here](./accounts-2fa.md#working-with-accounts-passwordless).

View File

@@ -0,0 +1,29 @@
# accounts-ui
A turn-key user interface for Meteor Accounts.
To add Accounts and a set of login controls to an application, add the
`accounts-ui` package and at least one login provider package:
`accounts-password`, `accounts-facebook`, `accounts-github`,
`accounts-google`, `accounts-twitter`, or `accounts-weibo`.
Then simply add the <span v-pre>`{{> loginButtons}}`</span> helper to an HTML file. This
will place a login widget on the page. If there is only one provider configured
and it is an external service, this will add a login/logout button. If you use
`accounts-password` or use multiple external login services, this will add
a "Sign in" link which opens a dropdown menu with login options. If you plan to
position the login dropdown in the right edge of the screen, use
<span v-pre>`{{> loginButtons align="right"}}`</span> in order to get the dropdown to lay
itself out without expanding off the edge of the screen.
To configure the behavior of <span v-pre>`{{> loginButtons}}`</span>, use
[`Accounts.ui.config`](../api/accounts.md#loggingIn).
`accounts-ui` also includes modal popup dialogs to handle links from
[`sendResetPasswordEmail`](../api/accounts.md#Accounts-sendResetPasswordEmail),
[`sendVerificationEmail`](../api/accounts.md#Accounts-sendVerificationEmail),
and [`sendEnrollmentEmail`](../api/accounts.md#Accounts-sendEnrollmentEmail). These
do not have to be manually placed in HTML: they are automatically activated
when the URLs are loaded.
If you want to control the look and feel of your accounts system a little more, we recommend reading the [useraccounts](http://guide.meteor.com/accounts.html#useraccounts) section of the Meteor Guide.

View File

@@ -0,0 +1,111 @@
# AppCache
> This package has been deprecated since [applicationCache](https://developer.mozilla.org/en-US/docs/Web/API/Window/applicationCache), which this package relies on, has been deprecated and is not available on the latest browsers. Plaese consider using [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) as an replacement.
The `appcache` package stores the static parts of a Meteor application
(the client side Javascript, HTML, CSS, and images) in the browser's
[application cache](https://en.wikipedia.org/wiki/AppCache). To enable
caching simply add the `appcache` package to your project.
* Once a user has visited a Meteor application for the first time and
the application has been cached, on subsequent visits the web page
loads faster because the browser can load the application out of the
cache without contacting the server first.
* Hot code pushes are loaded by the browser in the background while the
app continues to run. Once the new code has been fully loaded the
browser is able to switch over to the new code quickly.
* The application cache allows the application to be loaded even when
the browser doesn't have an Internet connection, and so enables using
the app offline.
(Note however that the `appcache` package by itself doesn't make
*data* available offline: in an application loaded offline, a Meteor
Collection will appear to be empty in the client until the Internet
becomes available and the browser is able to establish a DDP
connection).
To turn AppCache off for specific browsers use:
```js
Meteor.AppCache.config({
chrome: false,
firefox: false
});
```
The supported browsers that can be enabled or disabled include, but are
not limited to, `android`, `chrome`, `chromium`, `chromeMobileIOS`,
`firefox`, `ie`, `mobileSafari` and `safari`.
Browsers limit the amount of data they will put in the application
cache, which can vary due to factors such as how much disk space is
free. Unfortunately if your application goes over the limit rather
than disabling the application cache altogether and running the
application online, the browser will instead fail that particular
*update* of the cache, leaving your users running old code.
Thus it's best to keep the size of the cache below 5MB. The
`appcache` package will print a warning on the Meteor server console
if the total size of the resources being cached is over 5MB.
Starting from `appcache@1.2.5`, if you need more advanced logic
to enable/disable the cache, you can use the `enableCallback` option
that is evaluated on a per-request basis. For example:
```js
// Enable offline mode using a value from database and certificate validation
Meteor.AppCache.config({
// This option is available starting from appcache@1.2.4
enableCallback: () => {
if (!getSettingsFromDb("public.appcache_enabled")) {
return false;
}
const validation = validateClientCert({
clientCertPayload: req.headers["x-client-cert"],
url: req.url.href,
});
return validation.passed;
},
});
```
If you have files too large to fit in the cache you can disable
caching by URL prefix. For example,
```js
Meteor.AppCache.config({ onlineOnly: ['/online/'] });
```
causes files in your `public/online` directory to not be cached, and
so they will only be available online. You can then move your large
files into that directory and refer to them at the new URL:
```html
<img src="/online/bigimage.jpg">
```
If you'd prefer not to move your files, you can use the file names
themselves as the URL prefix:
```js
Meteor.AppCache.config({
onlineOnly: [
'/bigimage.jpg',
'/largedata.json'
]
});
```
though keep in mind that since the exclusion is by prefix (this is a
limitation of the application cache manifest), excluding
`/largedata.json` will also exclude such URLs as
`/largedata.json.orig` and `/largedata.json/file1`.
For more information about how Meteor interacts with the application
cache, see the
[AppCache page](https://github.com/meteor/meteor/wiki/AppCache)
in the Meteor wiki.

View File

@@ -0,0 +1,13 @@
# Audit Argument Checks
This package causes Meteor to require that all arguments passed to methods and
publish functions are [checked](../api/check.md). Any method that does not pass each
one of its arguments to `check` will throw an error, which will be logged on the
server and which will appear to the client as a
`500 Internal server error`. This is a simple way to help ensure that your
app has complete check coverage.
Methods and publish functions that do not need to validate their arguments can
simply run `check(arguments, [Match.Any])` to satisfy the
`audit-argument-checks` coverage checker.

View File

@@ -0,0 +1,62 @@
# Autoupdate
This is the Meteor package that provides hot code push (HCP) functionality.
Every Meteor application that wasn't created with the `--minimal` option
has this package already through `meteor-base` and HCP should work out of the
box. For those running `--minimal` applications and want to benefit from this
package, just add it with `meteor add autoupdate`.
> `autoupdate` adds up to 30KB on your client's production bundle.
With this package Meteor will use DDP to publish a collection called
_'meteor_autoupdate_clientVersions'_. This collection will be subscribed by the
user's client and every time the client identifies a change in the published
version it will refresh itself.
## Browser Client
The refresh will happen in the browser in two different ways: a _soft update_,
and a _hard update_. If Meteor identifies that only stylesheets were changed, then it
will verify if the user's browser is capable of reloading CSS on the fly, and a
soft update will take place. The soft update will replace the old stylesheet
with the new stylesheet without triggering a full page reload.
In cases where a change in a server's or client's compiled file happens, the hard
update will take place: Meteor will force a complete browser reload using the
`reload` package.
> Among other things, the `reload` package tries do reload the application
> preserving some unchanged cached files.
## Cordova Client
There is no soft update with Cordova apps, the client is always fully refreshed
once a change is detected.
### `usesCleartextTraffic`
Starting with Android 9 (API level 28), [cleartext support is disabled](https://developer.android.com/training/articles/security-config) by default.
During development `autoupdate` uses cleartext to publish new client versions.
If your app targets Android 9 or greater, it will be necessary to create a
`mobile-config.js` file enabling the use of cleartext in order to have HCP working:
```js
App.appendToConfig(`<edit-config file="app/src/main/AndroidManifest.xml"
mode="merge"
target="/manifest/application"
xmlns:android="http://schemas.android.com/apk/res/android">
<application android:usesCleartextTraffic="true"></application>
</edit-config>
`);
```
### `--mobile-server`
Additionally, for the HCP functionality to work it is also mandatory to provide
the address for the application server with `--mobile-server` option. If you're
testing your app on an emulator you should run it with `meteor run android --mobile-server 10.0.2.2:3000`.
If you're running it on a real device, the application server and the device
should be on the same network, and you should run your app with `meteor run android --mobile-server XXX.XXX.XXX.XXX`
where *XXX.XXX.XXX.XXX* is your local development address, _e.g. 192.168.1.4_.
> To have a better understanding of how HCP works for mobile apps already
> published to production refer to [Hot code push on mobile](https://guide.meteor.com/cordova.html#hot-code-push)

View File

@@ -0,0 +1,175 @@
# Browser Policy
The `browser-policy` family of packages, part of
[Webapp](https://github.com/meteor/meteor/tree/master/packages/webapp), lets you
set security-related policies that will be enforced by newer browsers. These
policies help you prevent and mitigate common attacks like cross-site scripting
and clickjacking.
## Details
When you add `browser-policy` to your app, you get default configurations for
the HTTP headers X-Frame-Options and Content-Security-Policy. X-Frame-Options
tells the browser which websites are allowed to frame your app. You should only
let trusted websites frame your app, because malicious sites could harm your
users with [clickjacking attacks](https://www.owasp.org/index.php/Clickjacking).
[Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Security/CSP/Introducing_Content_Security_Policy)
tells the browser where your app can load content from, which encourages safe
practices and mitigates the damage of a cross-site-scripting attack.
`browser-policy` also provides functions for you to configure these policies if
the defaults are not suitable.
If you only want to use Content-Security-Policy or X-Frame-Options but not both,
you can add the individual packages `browser-policy-content` or
`browser-policy-framing` instead of `browser-policy`.
For most apps, we recommend that you take the following steps:
- Add `browser-policy` to your app to enable a starter policy. With this starter
policy, your app's client code will be able to load content (images, scripts,
fonts, etc.) only from its own origin, except that XMLHttpRequests and WebSocket
connections can go to any origin. Further, your app's client code will not be
able to use functions such as `eval()` that convert strings to code. Users'
browsers will only let your app be framed by web pages on the same origin as
your app.
- You can use the functions described below to customize the policies. If your
app does not need any inline Javascript such as inline `<script>` tags, we
recommend that you modify the policy by calling
`BrowserPolicy.content.disallowInlineScripts()` in server code. This will result
in one extra round trip when your app is loaded, but will help prevent
cross-site scripting attacks by disabling all scripts except those loaded from a
`script src` attribute.
Meteor determines the browser policy when the server starts up, so you should
call `BrowserPolicy` functions on the server in top-level application code or in
`Meteor.startup`. `BrowserPolicy` functions cannot be used in client code.
## Usage
### Frame options
By default, if you add `browser-policy` or `browser-policy-framing`, only web
pages on the same origin as your app are allowed to frame your app. You can use
the following functions to modify this policy.
## BrowserPolicy.framing.disallow()
Your app will never render inside a frame or iframe.
## BrowserPolicy.framing.restrictToOrigin(origin)
Your app will only render inside frames loaded by `origin`. You can only call
this function once with a single origin, and cannot use wildcards or specify
multiple origins that are allowed to frame your app. (This is a limitation of
the X-Frame-Options header.) Example values of `origin` include
`"http://example.com"` and `"https://foo.example.com"`. **This value of the
X-Frame-Options header is not yet supported in Chrome or Safari and will be
ignored in those browsers. If you need Chrome and/or Safari support, or need to
allow multiple domains to frame your application, you can use the frame-ancestors
CSP option via the BrowserPolicy.content.allowFrameAncestorsOrigin() function.**
## BrowserPolicy.framing.allowAll()
This unsets the X-Frame-Options header, so that your app can be framed by any
webpage.
### Content options
You can use the functions in this section to control how different types of
content can be loaded on your site.
You can use the following functions to adjust policies on where Javascript and
CSS can be run:
## BrowserPolicy.content.allowInlineScripts()
Allows inline `<script>` tags, `javascript:` URLs, and inline event handlers.
The default policy already allows inline scripts.
## BrowserPolicy.content.disallowInlineScripts()
Disallows inline Javascript. Calling this function results in an extra round-trip
on page load to retrieve Meteor runtime configuration that is usually part of an
inline script tag.
## BrowserPolicy.content.allowEval()
Allows the creation of Javascript code from strings using function such as `eval()`.
## BrowserPolicy.content.disallowEval()
Disallows eval and related functions. Note: The default policy disallows eval,
though for almost all Meteor apps it is enabled by the `dynamic-imports` package
## BrowserPolicy.content.allowInlineStyles()
Allows inline style tags and style attributes. The default policy already allows
inline styles.
## BrowserPolicy.content.disallowInlineStyles()
Disallows inline CSS.
Finally, you can configure a whitelist of allowed requests that various types of
content can make. The following functions are defined for the content types
script, object, image, media, font, frame, frame-ancestors, style, and connect.
## BrowserPolicy.content.allowContentTypeOrigin(origin)
Allows this type of content to be loaded from the given origin. `origin` is a
string and can include an optional scheme (such as `http` or `https`), an
optional wildcard at the beginning, and an optional port which can be a
wildcard. Examples include `example.com`, `https://*.example.com`, and
`example.com:*`. You can call these functions multiple times with different
origins to specify a whitelist of allowed origins. Origins that don't specify a
protocol will allow content over both HTTP and HTTPS: passing `example.com` will
allow content from both `http://example.com` and `https://example.com`.
## BrowserPolicy.content.allowContentTypeDataUrl()
Allows this type of content to be loaded from a `data:` URL.
## BrowserPolicy.content.allowContentTypeSameOrigin()
Allows this type of content to be loaded from the same origin as your app.
## BrowserPolicy.content.disallowContentType()
Disallows this type of content on your app.
You can also set policies for all these types of content at once, using these
functions:
- `BrowserPolicy.content.allowSameOriginForAll()`,
- `BrowserPolicy.content.allowDataUrlForAll()`,
- `BrowserPolicy.content.allowOriginForAll(origin)`
- `BrowserPolicy.content.disallowAll()`
For example, if you want to allow the
origin `https://foo.com` for all types of content but you want to disable
`<object>` tags, you can call
`BrowserPolicy.content.allowOriginForAll("https://foo.com")` followed by
`BrowserPolicy.content.disallowObject()`.
Other examples of using the `BrowserPolicy.content` API:
- `BrowserPolicy.content.disallowFont()` causes the browser to disallow all
`<font>` tags.
- `BrowserPolicy.content.allowImageOrigin("https://example.com")`
allows images to have their `src` attributes point to images served from
`https://example.com`.
- `BrowserPolicy.content.allowConnectOrigin("https://example.com")` allows XMLHttpRequest
and WebSocket connections to `https://example.com`.
- `BrowserPolicy.content.allowFrameOrigin("https://example.com")` allows
your site to load the origin `https://example.com` in a frame or
iframe. The `BrowserPolicy.framing` API allows you to control which
sites can frame your site, while
`BrowserPolicy.content.allowFrameOrigin` allows you to control which
sites can be loaded inside frames on your site.
Adding `browser-policy-content` to your app also tells certain
browsers to avoid sniffing content types away from the declared type
(for example, interpreting a text file as JavaScript), using the
[X-Content-Type-Options](http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx)
header. To re-enable content sniffing, you can call
`BrowserPolicy.content.allowContentTypeSniffing()`.

View File

@@ -0,0 +1,60 @@
# Bundle Visualizer
The `bundle-visualizer` package is an analysis tool which provides a visual
representation within the web browser showing what is included in the initial
client bundle. The initial client bundle is the primary package of code
downloaded and executed by the browser to run a Meteor application and includes
packages which have been added via `meteor add <package>` or Node modules
included in the `node_modules` directory and used in an application.
This visualization can uncover details about which files or packages are
occupying space within the initial client bundle. This can be useful in
determining which imports might be candidates for being converted to dynamic
`import()` statements (which are excluded from the initial client bundle), or
for identifying packages which have been inadvertently included in a project.
## How it works
This package utilizes the `<hash>.stats.json` files which are written alongside
file bundles when the application is ran with the `--production` flag. The
specific details for the minified file sizes is added by the minifier package
and therefore it's important to review the minifier requirements below.
## Requirements
This package requires data provided by the project's minifier. For this reason,
it is necessary to use the official `standard-minifier-js` package or a minifier
which includes file-size details obtained during minification.
## Usage
Since bundle analysis is only truly accurate on a minified bundle and
minification does not take place during development (as it is a complex and
CPU-intensive process which would substantially slow down normal development)
this package must be used in conjunction with the `--production` flag to the
`meteor` tool to simulate production bundling and enable minification.
> **IMPORTANT:** Since this package is active in production mode, it is critical
> to only add this package temporarily. This can be easily accomplished using
> the `--extra-packages` option to `meteor`.
### Enabling
```sh
$ cd app/
$ meteor --extra-packages bundle-visualizer --production
```
### Viewing
Once enabled, view the application in a web-browser as usual
(e.g. `http://localhost:3000/`) and the chart will be displayed on top of the
application.
### Disabling
If you used `--extra-packages`, simply remove `bundle-visualizer` from the list
of included packages and run `meteor` as normal.
> If you've added `bundle-visualizer` permanently with `meteor add`, it is
> important to remove this package prior to bundling or deploying to
> production with `meteor remove bundle-visualizer`.

View File

@@ -0,0 +1,50 @@
# CoffeeScript
[CoffeeScript](http://coffeescript.org/) is a little language that
compiles into JavaScript. It provides a simple syntax without lots of
braces and parentheses. The code compiles one-to-one into the
equivalent JS, and there is no interpretation at runtime.
CoffeeScript is supported on both the client and the server. Files
ending with `.coffee`, `.litcoffee`, or `.coffee.md` are automatically
compiled to JavaScript.
### Namespacing and CoffeeScript
Here's how CoffeeScript works with Meteor's namespacing.
* Per the usual CoffeeScript convention, CoffeeScript variables are
file-scoped by default (visible only in the `.coffee` file where
they are defined.)
* When writing a package, CoffeeScript-defined variables can be
exported like any other variable (see [Package.js](/api/package)). Exporting a variable pulls it up to
package scope, meaning that it will be visible to all of the code in
your app or package (both `.js` and `.coffee` files).
* Package-scope variables declared in `.js` files are visible in any
`.coffee` files in the same app or project.
* There is no way to make a package-scope variable from a `.coffee`
file other than exporting it. We couldn't figure out a way to make
this fit naturally inside the CoffeeScript language. If you want to
use package-scope variables with CoffeeScript, one way is to make a
short `.js` file that declares all of your package-scope
variables. They can then be used and assigned to from `.coffee`
files.
* If you want to share variables between `.coffee` files in the same
package, and don't want to separately declare them in a `.js` file,
we have an experimental feature that you may like. An object called
`share` is visible in CoffeeScript code and is shared across all
`.coffee` files in the same package. So, you can write `share.foo`
for a value that is shared between all CoffeeScript code in a
package, but doesn't escape that package.
Heavy CoffeeScript users, please let us know how this arrangement
works for you, whether `share` is helpful for you, and anything else
you'd like to see changed.
### Modules and CoffeeScript
See [Modules » Syntax » CoffeeScript](/packages/modules.html#coffeescript).

View File

@@ -0,0 +1,121 @@
# Dynamic Imports
> **Note:** Dynamic imports require Meteor 1.5 or higher.
The `dynamic-import` package provides an implementation of
`Module.prototype.dynamicImport`, an extension of the module runtime which
powers the [dynamic `import(...)`](https://github.com/tc39/proposal-dynamic-import)
statement, an up-and-coming (ECMA2020) addition to the
ECMAScript standard.
The dynamic `import(...)` statement is a complementary method to the static
`import` technique of requiring a module. While a statically `import`-ed
module would be bundled into the initial JavaScript bundle, a
dynamically `import()`-ed module is fetched from the server at
runtime.
Once a module is fetched dynamically from the server, it is cached permanently
on the client and additional requests for the same version of the module will
not incur the round-trip request to the server. If the module is changed then a
fresh copy will always be retrieved from the server.
## Usage
The `import(...)` statement returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)
which is resolved with the `exports` of the module when it has been successfully
fetched from the server and is ready to be used.
Because it's a `Promise`, there are a couple methods developers can use to
dictate what will happen upon the availability of the dynamically loaded module:
### The `.then()` method of the `Promise`
```js
import("tool").then((tool) => tool.task());
```
### By `await`-ing in an asynchronous function
Meteor supports [`async` and `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function),
which provide a straightforward approach to asynchronously wait for the
module to be ready without the need to provide a callback:
```js
async function performTask() {
const tool = await import("tool");
tool.task();
}
```
::: warning
**Default exports**
The `import(...)` `Promise` is resolved with the `exports` of the module.
If it's necessary to use the "default" export from a module, it will be
available on the `default` property of the resulting object. In the above
examples, this means it will be available as `tool.default`. It can be
helpful to use parameter de-structuring to provide additional clarity:
```js
import("another-tool").then(({ default: thatTool }) => thatTool.go());
```
:::
### Using `import()` with dynamic expressions
If you try to import using any computed expression, such as:
```js
let path = "example";
const module = await import(`/libs/${path}.js`);
```
You'll get an error like so:
```js
Error: Cannot find module '/libs/example.js'
```
Meteors build process builds a graph of all files that are imported or required
using static analysis. It then creates exact bundles of the referenced files
and makes them available to the client for `import()`.
Without a complete import statement (static, dynamic or `require`), Meteor won't
make that module available for `import()`.
The solution to make dynamic expressions work is to create a module "whitelist"
that can be read by the build process, but does not actually run. For example:
```js
if (false) {
import("/libs/example.js");
import("/libs/another-example.js");
import("/libs/yet-another-example.js");
}
```
Make sure the whitelist is imported from both the client and server entry points.
## Difference to other bundling systems
In Meteor's implementation, the client has perfect information about which
modules were in the initial bundle, which modules are in the local cache, and
which modules still need to be fetched. There is never any overlap between
requests made by a single client, nor will there be any unneeded modules in the
response from the server. You might call this strategy **exact code splitting**,
to differentiate it from bundling.
Moreover, the initial bundle includes the hashes of all available dynamic
modules, so the client doesn't have to ask the server if it can use a cached
version of a module, and the same version of the module never needs to be
downloaded again by the same client. This caching system has all the benefits of
immutable caching.
Meteor also allows dynamic expressions as long as the dependency is expressed
statically somewhere else in your code. This is possible because Meteor's
client-side module system understands how to resolve dynamic strings at runtime
(which is not true in webpack or browserify, because they replace module
identifier strings with numbers). However, the set of available modules is
constrained by the string literals that you, the programmer, explicitly decided
to allow to be imported (either directly or in a whitelist).

View File

@@ -0,0 +1,309 @@
# ECMAScript
This package lets you use new JavaScript language features that are part
of the [ECMAScript 2015 specification](http://www.ecma-international.org/ecma-262/6.0/) but are
not yet supported by all engines or browsers. Unsupported syntax is
automatically translated into standard JavaScript that behaves the same
way.
[This video](https://www.youtube.com/watch?v=05Z6YGiZKmE) from the July
2015 Meteor Devshop gives an overview of how the package works, and what
it provides.
## Usage
The `ecmascript` package registers a compiler plugin that transpiles
ECMAScript 2015+ to ECMAScript 5 (standard JS) in all `.js` files. By
default, this package is pre-installed for all new apps and packages.
To add this package to an existing app, run the following command from
your app directory:
```bash
meteor add ecmascript
```
To add the `ecmascript` package to an existing package, include the
statement `api.use('ecmascript');` in the `Package.onUse` callback in your
`package.js` file:
```js
Package.onUse((api) => {
api.use('ecmascript');
});
```
## Supported ES2015 Features
### Syntax
The `ecmascript` package uses [Babel](http://babeljs.io/) to compile
ES2015 syntax to ES5 syntax. Many but not all ES2015 features can be
simulated by Babel, and `ecmascript` enables most of the features
supported by Babel.
Here is a list of the Babel transformers that are currently enabled:
* [`es3.propertyLiterals`](https://babeljs.io/docs/advanced/transformers/es3/property-literals/)<br>
Makes it safe to use reserved keywords like `catch` as unquoted keys in
object literals. For example, `{ catch: 123 }` is translated to `{ "catch": 123 }`.
* [`es3.memberExpressionLiterals`](https://babeljs.io/docs/advanced/transformers/es3/member-expression-literals/)<br>
Makes it safe to use reserved keywords as property names. For
example, `object.catch` is translated to `object["catch"]`.
* [`es6.arrowFunctions`](http://babeljs.io/docs/learn-es2015/#arrows)<br>
Provides a shorthand for function expressions. For example,
`[1, 2, 3].map(x => x + 1)` evaluates to `[2, 3, 4]`. If `this` is used
in the body of the arrow function, it will be automatically bound to the
value of `this` in the enclosing scope.
* [`es6.literals`](http://babeljs.io/docs/learn-es2015/#binary-and-octal-literals)<br>
Adds support for binary and octal numeric literals. For example,
`0b111110111 === 503` and `0o767 === 503`.
* [`es6.templateLiterals`](http://babeljs.io/docs/learn-es2015/#template-strings)<br>
Enables multi-line strings delimited by backticks instead of quotation
marks, with variable interpolation:
```js
var name = 'Ben';
var message = `My name is:
${name}`;
```
* [`es6.classes`](http://babeljs.io/docs/learn-es2015/#classes)<br>
Enables `class` syntax:
```js
class Base {
constructor(a, b) {
this.value = a * b;
}
}
class Derived extends Base {
constructor(a, b) {
super(a + 1, b + 1);
}
}
var d = new Derived(2, 3);
d.value; // 12
```
* [`es6.constants`](https://babeljs.io/docs/learn-es2015/#let-const)<br>
Allows defining block-scoped variables that are not allowed to be
redefined:
```js
const GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
// This reassignment will be forbidden by the compiler:
GOLDEN_RATIO = 'new value';
```
* [`es6.blockScoping`](http://babeljs.io/docs/learn-es2015/#let-const)<br>
Enables the `let` and `const` keywords as alternatives to `var`. The key
difference is that variables defined using `let` or `const` are
visible only within the block where they are declared, rather than being
visible anywhere in the enclosing function. For example:
```js
function example(condition) {
let x = 0;
if (condition) {
let x = 1;
console.log(x);
} else {
console.log(x);
x = 2;
}
return x;
}
example(true); // logs 1, returns 0
example(false); // logs 0, returns 2
```
* [`es6.properties.shorthand`](https://babeljs.io/docs/learn-es2015/#enhanced-object-literals)<br>
Allows omitting the value of an object literal property when the desired
value is held by a variable that has the same name as the property
key. For example, instead of writing `{ x: x, y: y, z: "asdf" }` you can
just write `{ x, y, z: "asdf" }`. Methods can also be written without
the `: function` property syntax:
```js
var obj = {
oldWay: function (a, b) { ... },
newWay(a, b) { ... }
};
```
* [`es6.properties.computed`](http://babeljs.io/docs/learn-es2015/#enhanced-object-literals)<br>
Allows object literal properties with dynamically computed keys:
```js
var counter = 0;
function getKeyName() {
return 'key' + counter++;
}
var obj = {
[getKeyName()]: 'zero',
[getKeyName()]: 'one',
};
obj.key0; // 'zero'
obj.key1; // 'one'
```
* [`es6.parameters`](http://babeljs.io/docs/learn-es2015/#default-rest-spread)<br>
Default expressions for function parameters, evaluated whenever the parameter
is `undefined`, `...rest` parameters for capturing remaining
arguments without using the `arguments` object:
```js
function add(a = 0, ...rest) {
rest.forEach(n => a += n);
return a;
}
add(); // 0
add(1, 2, 3); // 6
```
* [`es6.spread`](http://babeljs.io/docs/learn-es2015/#default-rest-spread)<br>
Allows an array of arguments to be interpolated into a list of arguments
to a function call, `new` expression, or array literal, without using
`Function.prototype.apply`:
```js
add(1, ...[2, 3, 4], 5); // 15
new Node('name', ...children);
[1, ...[2, 3, 4], 5]; // [1, 2, 3, 4, 5]
```
* [`es6.forOf`](http://babeljs.io/docs/learn-es2015/#iterators-for-of)<br>
Provides an easy way to iterate over the elements of a collection:
```js
let sum = 0;
for (var x of [1, 2, 3]) {
sum += x;
}
sum; // 6
```
* [`es6.destructuring`](http://babeljs.io/docs/learn-es2015/#destructuring)<br>
Destructuring is the technique of using an array or object pattern on
the left-hand side of an assignment or declaration, in place of the
usual variable or parameter, so that certain sub-properties of the value
on the right-hand side will be bound to identifiers that appear within the
pattern. Perhaps the simplest example is swapping two variables without
using a temporary variable:
```js
[a, b] = [b, a];
```
Extracting a specific property from an object:
```js
let { username: name } = user;
// is equivalent to
let name = user.username;
```
Instead of taking a single opaque `options` parameter, a function can
use an object destructuring pattern to name the expected options:
```js
function run({ command, args, callback }) { ... }
run({
command: 'git',
args: ['status', '.'],
callback(error, status) { ... },
unused: 'whatever'
});
```
* [`es7.objectRestSpread`](https://github.com/sebmarkbage/ecmascript-rest-spread)<br>
Supports catch-all `...rest` properties in object literal declarations
and assignments:
```js
let { x, y, ...rest } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
rest; // { a: 3, b: 4 }
```
Also enables `...spread` properties in object literal expressions:
```js
let n = { x, y, ...rest };
n; // { x: 1, y: 2, a: 3, b: 4 }
```
* [`es7.trailingFunctionCommas`](https://github.com/jeffmo/es-trailing-function-commas)<br>
Allows the final parameter of a function to be followed by a comma,
provided that parameter is not a `...rest` parameter.
* [`flow`](https://babeljs.io/docs/advanced/transformers/other/flow/)<br>
Permits the use of [Flow](http://flowtype.org/) type annotations. These
annotations are simply stripped from the code, so they have no effect on
the code's behavior, but you can run the `flow` tool over your code to
check the types if desired.
### Polyfills
The ECMAScript 2015 standard library has grown to include new APIs and
data structures, some of which can be implemented ("polyfilled") using
JavaScript that runs in all engines and browsers today. Here are three new
constructors that are guaranteed to be available when the `ecmascript`
package is installed:
* [`Promise`](https://github.com/meteor/promise)<br>
A `Promise` allows its owner to wait for a value that might not be
available yet. See [this tutorial](https://www.promisejs.org/) for more
details about the API and motivation. The Meteor `Promise`
implementation is especially useful because it runs all callback
functions in recycled `Fiber`s, so you can use any Meteor API, including
those that yield (e.g. `HTTP.get`, `Meteor.call`, or `MongoCollection`),
and you never have to call `Meteor.bindEnvironment`.
* [`Map`](https://github.com/zloirock/core-js#map)<br>
An associative key-value data structure where the keys can be any
JavaScript value (not just strings). Lookup and insertion take constant
time.
* [`Set`](https://github.com/zloirock/core-js#set)<br>
A collection of unique JavaScript values of any type. Lookup and
insertion take constant time.
* [`Symbol`](https://github.com/zloirock/core-js#ecmascript-6-symbol)<br>
An implementation of the global
[`Symbol`](http://www.2ality.com/2014/12/es6-symbols.html)s namespace
that enables a number of other ES2015 features, such as `for`-`of` loops
and `Symbol.iterator` methods: `[1,2,3][Symbol.iterator]()`.
* Polyfills for the following [`Object`](https://github.com/zloirock/core-js#ecmascript-6-object)-related methods:
* `Object.assign`
* `Object.is`
* `Object.setPrototypeOf`
* `Object.prototype.toString` (fixes `@@toStringTag` support)<br>
Complete reference [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object).
* Polyfills for the following [`String`](https://github.com/zloirock/core-js#ecmascript-6-string)-related methods:
* `String.fromCodePoint`
* `String.raw`
* `String.prototype.includes`
* `String.prototype.startsWith`
* `String.prototype.endsWith`
* `String.prototype.repeat`
* `String.prototype.codePointAt`
* `String.prototype.trim`<br>
Complete reference [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String).
* Polyfills for the following [`Array`](https://github.com/zloirock/core-js#ecmascript-6-array)-related methods:
* `Array.from`
* `Array.of`
* `Array.prototype.copyWithin`
* `Array.prototype.fill`
* `Array.prototype.find`
* `Array.prototype.findIndex`
Complete reference [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
* Polyfills for the following [`Function`](https://github.com/zloirock/core-js#ecmascript-6-function)-related properties:
* `Function.prototype.name` (fixes IE9+)
* `Function.prototype[Symbol.hasInstance]` (fixes IE9+)
Complete reference [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).

View File

@@ -0,0 +1,69 @@
# Fetch
Isomorphic modern/legacy/Node polyfill for WHATWG fetch().
This package replaces the `http` package for HTTP calls. `fetch` package provides polyfill for the [WHATWG fetch specification](https://fetch.spec.whatwg.org/) for legacy browsers or defaults to the global class which is available in modern browsers and Node. It is recommended that you use this package for compatibility with non-modern browsers.
For more information we recommend [reading the MDN articles](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) about it as this article covers only basic usage in Meteor.
## Usage
### Installation
To add this package to an existing app, run the following command from
your app directory:
```bash
meteor add fetch
```
To add the `fetch` package to an existing package, include the
statement `api.use('fetch');` in the `Package.onUse` callback in your
`package.js` file:
```js
Package.onUse((api) => {
api.use("fetch");
});
```
## API
You can import `fetch`, `Headers`, `Request` and `Response` classes from `meteor/fetch`.
```js
import { fetch, Headers, Request, Response } from "meteor/fetch";
```
For the most part though, you will only need to import `fetch` and `Headers`.
```js
import { Meteor } from 'meteor/meteor';
import { fetch, Headers } from 'meteor/fetch';
async function postData (url, data) {
try {
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: new Headers({
Authorization: 'Bearer my-secret-key',
'Content-Type': 'application/json'
}),
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
const data = await response.json();
return response(null, data);
} catch (err) {
return response(err, null);
}
}
const postDataCall = Meteor.wrapAsync(postData);
const results = postDataCall('https://www.example.org/statsSubmission', { totalUsers: 55 }));
```

View File

@@ -0,0 +1,133 @@
# Hot Module Replacement
Hot Module Replacement (HMR) is a method of updating javascript modules within a running application. This reduces the feedback cycle while developing, so you can view and test changes quicker. In Meteor's implementation, the app can be updated before the build has even finished.
::: tip
`hot-module-replacement` package was introduced in Meteor 2.0
:::
To enable HMR for an app, it should use the `hot-module-replacement` package. HMR is currently not supported for packages, but packages can depend on the `hot-module-replacement` package to ensure access to the hot API. When a change can not be accepted with HMR, Meteor uses hot code push to update the app, as is normally done when HMR is not used.
HMR currently supports the modern web architecture. It is always disabled in other architectures and in production.
### How the app is updated
While rebuilding a supported architecture, Meteor checks which files were modified, and sends the modified files to the client. The client then uses this process:
1. It checks if the modified module accepts or declines updates. If the module does neither, Meteor looks at the modules that imported it to see if they accept or decline the update, and then the modules that import those, and so on. If all paths it followed leads to modules that accept the update, it uses HMR. Otherwise, it uses hot code push.
2. Many js modules do things that have a long term effect on the app. They might create Tracker autoruns, register event listeners, or have UI components that have been rendered. Modules can register dispose handlers to clean up the old version of a module so it no longer affects the app. At this point in the update process, those dispose handlers are called.
3. Meteor runs the new version of the module, the modules that accepted the update, and all of the modules between them. This ensures the module's new exports are used. Because of this, usually the only modules that accept updates are the ones that have no exports or that have another way of updating the exports used by parent modules. At this point, there are two versions of these modules running, but the old version is no longer in use if it was disposed properly.
For HMR to work properly, the correct modules have to accept or decline updates, and dispose handlers have to be written. Fortunately, most apps do not need to manually do either. Instead, you can use integrations that automatically detect modules that can accept updates, and how to clean up specific types of modules. For example, the React integration is enabled by default in Meteor apps, and is able to automatically update React components.
### API
The hot-module-replacement package provides an API to control how HMR is used. The API is available at `module.hot`. Since the API isn't always available (for example, in production or in architectures not supported by HMR), the code should make sure `module.hot` is defined before using it:
```js
if (module.hot) {
module.hot.accept();
}
```
In a future Meteor version, using the if statement will allow minifiers to remove this block when minifying for production.
Packages that use the `module.hot` api should use the `hot-module-reload` package to ensure access to the API.
<ApiBox name="module.hot#accept" hasCustomExample instanceName="module.hot"/>
HMR reruns files that import the modified modules so the files use the new exports. In addition to configuring which modules can be updated with HMR, `module.hot.accept()` also controls how many files are re-ran. Meteor re-runs the files that import the modified modules, the files that import those files, and so on, until it reaches the modules that accepted the update. Because of this, usually the only modules that accept updates are ones that have no exports, are have another way of updating the exports used by its parent modules.
<ApiBox name="module.hot#decline" hasCustomExample instanceName="module.hot"/>
<ApiBox name="module.hot#dispose" hasCustomExample instanceName="module.hot"/>
The call back is run when this instance of the module will no longer be used. The main use is making sure the instance of the module no longer affects the app. Here is an example where we stop a Tracker computation:
```js
import { setLocale } from '/imports/utils/locale';
const computation = Tracker.autorun(() => {
const user = Meteor.user();
if (user && user.locale) {
setLocale(user.locale);
}
});
if (module.hot) {
module.hot.dispose(() => {
computation.stop();
});
}
```
If it did not stop the computation, each time the module is reran for HMR, there would be an additional computation. This can lead to unexpected behavior, especially if we've modified the computation function.
The callback receives a data object that can be mutated to store information for the new instance of the module. This can be used to preserve class instances, state, or other data. For example, this module will preserve the value of the color variable:
```js
let color = 'blue';
export function getColor() {
return color;
}
export function changeColor(newColor) {
color = newColor;
}
if (module.hot) {
if (module.hot.data) {
color = module.hot.data.color;
}
module.hot.dispose(data => {
data.color = color;
});
}
```
When the module first runs, `module.hot.data` is null, so it leaves `color` set to blue. Eventually the app calls `changeColor` and sets the color to `purple`. If the module is re-ran, the old instance of the module stores the color in `data.color`. The new instance retrieves it from `module.hot.data.color`, and registers a new dispose handler for the next time it is re-run.
<ApiBox name="module.hot#data" hasCustomExample instanceName="module.hot"/>
<ApiBox name="module.hot#onRequire" hasCustomExample instanceName="module.hot"/>
This is used by some HMR integrations to detect files that can be automatically updated with HMR, and handle cleaning up the old module instances and migrating state.
For example, React Fast Refresh uses this to find the modules that only export React components, and have those modules accept updates.
```js
if (module.hot) {
module.hot.onRequire({
// requiredModule is the same object available in the
// required module as `module`, including access to `module.hot`
// and `module.exports`
//
// parentId is a string with the path of the module that
// imported requiredModule.
before(requiredModule, parentId) {
// Anything returned here is available to the
// after callback as the data parameter.
return {
importedBy: parentId,
previouslyEvaluated: !requiredModule.loaded
}
},
after(requiredModule, data) {
if (!data.previouslyEvaluated) {
console.log(`Finished evaluating ${requiredModule.id}`);
console.log(`It was imported by ${data.importedBy}`);
console.log(`Its exports are ${requiredModule.exports}`);
}
// canAcceptUpdates would look at the exports, and maybe the imports
// to check if this module can safely be updated with HMR.
if (requiredModule.hot && canAcceptUpdates(requiredModule)) {
requiredModule.hot.accept();
}
}
});
}
```

View File

@@ -0,0 +1,12 @@
# less
[LESS](http://lesscss.org/) extends CSS with dynamic behavior such as variables, mixins,
operations and functions. It allows for more compact stylesheets and
helps reduce code duplication in CSS files.
With the `less` package installed, `.less` files in your application are
automatically compiled to CSS and the results are included in the client CSS
bundle.
> If you want to `@import` a file, give it the extension `.import.less`
to prevent Meteor from processing it independently.

View File

@@ -0,0 +1,44 @@
# Logging
The `logging` package provides a standardised way for you to log and display in console various message from your application.
The added benefit is that among other data it will show you the location where the log was fired,
this is useful during debugging to quickly locate where the message is coming from.
Start by installing the package:
```bash
meteor add logging
```
You can then import the utility anywhere in you code like this:
```javascript
import { Log } from 'meteor/logging'
```
You can then call the logging functions in one of the following ways:
```javascript
Log('starting up') // or Log.info('starting up')
Log.error('error message')
Log.warn('warning')
Log.debug('this will show only in development')
```
Besides passing in strings, you can also pass in objects. This has few exceptions and special functions associated.
First in the root of the object the following keys are not allowed:
```javascript
'time', 'timeInexact', 'level', 'file', 'line', 'program', 'originApp', 'satellite', 'stderr'
```
On the other hand there is `message` and `app`, which are also reserved, but they will display in more prominent manner:
```javascript
Log.info({message: 'warning', app: 'DESKTOP', error: { property1: 'foo', property2: 'bar', property3: { foo: 'bar' }} })
```
will turn into:
```shell
E20200519-17:57:41.655(9) [DESKTOP] (main.js:36) warning {"error":{"property1":"foo","property2":"bar","property3":{"foo":"bar"}}}
```
The display of each log is color coded. Info is `blue`, warn is `magenta`, debug is `green` and error is in `red`.
### Log.debug
The `Log.debug()` logging is different from the other calls as these messages will not be displayed in production.

View File

@@ -0,0 +1,34 @@
# Markdown
> Note: This package has been deprecated.
This package lets you use Markdown in your templates.
### Installation
```sh
meteor add markdown
```
### Usage
This package is lazy loaded. Is is not added into the initial Bundle.
So you need to import it in your template.
```js
// myTemplate.js
import 'meteor/markdown';
```
Then you can use the `markdown` helper in your templates:
```html
<!-- myTemplate.html -->
{{#markdown}}I am using __markdown__.{{/markdown}}
```
outputs
```html
<p>I am using <strong>markdown</strong>.</p>
```

View File

@@ -0,0 +1,376 @@
# Modules
This document explains the usage and key features of the module system used by Meteor.
> Meteor 1.2 introduced support for [many new ECMAScript 2015 features](https://github.com/meteor/meteor/blob/devel/packages/ecmascript/README.md#supported-es2015-features), one of the most notable omissions was [ES2015 `import` and `export` syntax](http://exploringjs.com/es6/ch_modules.html).
> Meteor 1.3 filled the gap with a fully standards-compliant module system that works on both the client and the server.
> Meteor 1.7 introduced `meteor.mainModule` and `meteor.testModule` to `package.json` so Meteor doesn't need special folders anymore for js resources. Also doesn't need to eager load js resources.
By design, `meteor.mainModule` only affect js resources. For non-js resources, there are still some things that can only be done within imports:
- only stylesheets within imports can be dynamically imported
- you can only control the load order of stylesheets by importing them in js if the stylesheets are within imports
Any non-js resource outside of imports (and some other special folders) are still eagerly loaded.
> You can read more about these differences in this [comment](https://github.com/meteor/meteor/pull/11381#issuecomment-818816052).
## Enabling modules
It is installed by default for all new apps and packages. Nevertheless, the `modules` package is totally optional.
If you want to add it to existent apps or packages:
For apps, this is as easy as `meteor add modules`, or (even better) `meteor add ecmascript`, since the `ecmascript` package *implies* the `modules` package.
For packages, you can enable `modules` by adding `api.use('modules')` to the `Package.onUse` or `Package.onTest` sections of your `package.js` file.
Now, you might be wondering what good the `modules` package is without the `ecmascript` package, since `ecmascript` enables `import` and `export` syntax. By itself, the `modules` package provides the CommonJS `require` and `exports` primitives that may be familiar if youve ever written Node code, and the `ecmascript` package simply compiles `import` and `export` statements to CommonJS. The `require` and `export` primitives also allow Node modules to run within Meteor application code without modification. Furthermore, keeping `modules` separate allows us to use `require` and `exports` in places where using `ecmascript` is tricky, such as the implementation of the `ecmascript` package itself.
While the `modules` package is useful by itself, we very much encourage using the `ecmascript` package (and thus `import` and `export`) instead of using `require` and `exports` directly. If you need convincing, here's a [presentation](http://benjamn.github.io/empirenode-2015) that explains the differences.
## Basic syntax
### ES2015
Although there are a number of different variations of `import` and `export` syntax, this section describes the essential forms that everyone should know.
First, you can `export` any named declaration on the same line where it was declared:
```js
// exporter.js
export var a = ...;
export let b = ...;
export const c = ...;
export function d() { ... }
export function* e() { ... }
export class F { ... }
```
These declarations make the variables `a`, `b`, `c` (and so on) available not only within the scope of the `exporter.js` module, but also to other modules that `import` from `exporter.js`.
If you prefer, you can `export` variables by name, rather than prefixing their declarations with the `export` keyword:
```js
// exporter.js
function g() { ... }
let h = g();
// At the end of the file
export { g, h };
```
All of these exports are *named*, which means other modules can import them using those names:
```js
// importer.js
import { a, c, F, h } from './exporter';
new F(a, c).method(h);
```
If youd rather use different names, youll be glad to know `export` and `import` statements can rename their arguments:
```js
// exporter.js
export { g as x };
g(); // Same as calling `y()` in importer.js
```
```js
// importer.js
import { x as y } from './exporter';
y(); // Same as calling `g()` in exporter.js
```
As with CommonJS `module.exports`, it is possible to define a single *default* export:
```js
// exporter.js
export default any.arbitrary(expression);
```
This default export may then be imported without curly braces, using any name the importing module chooses:
```js
// importer.js
import Value from './exporter';
// Value is identical to the exported expression
```
Unlike CommonJS `module.exports`, the use of default exports does not prevent the simultaneous use of named exports. Here is how you can combine them:
```js
// importer.js
import Value, { a, F } from './exporter';
```
In fact, the default export is conceptually just another named export whose name happens to be "default":
```js
// importer.js
import { default as Value, a, F } from './exporter';
```
These examples should get you started with `import` and `export` syntax. For further reading, here is a very detailed [explanation](http://www.2ality.com/2014/09/es6-modules-final.html) by [Axel Rauschmayer](https://twitter.com/rauschma) of every variation of `import` and `export` syntax.
### CommonJS
You dont need to use the `ecmascript` package or ES2015 syntax in order to use modules. Just like Node.js in the pre-ES2015 days, you can use `require` and `module.exports`—thats what the `import` and `export` statements are compiling into, anyway.
ES2015 `import` lines like these:
```js
import { AccountsTemplates } from 'meteor/useraccounts:core';
import '../imports/startup/client/routes.js';
```
can be written with CommonJS like this:
```js
var UserAccountsCore = require('meteor/useraccounts:core');
require('../imports/startup/client/routes.js');
```
and you can access `AccountsTemplates` via `UserAccountsCore.AccountsTemplates`.
Note that files dont need a `module.exports` if theyre required like `routes.js` is in this example, without assignment to any variable. The code in `routes.js` will simply be included and executed in place of the above `require` statement.
ES2015 `export` statements like these:
```js
export const insert = new ValidatedMethod({ ... });
export default incompleteCountDenormalizer;
```
can be rewritten to use CommonJS `module.exports`:
```js
module.exports.insert = new ValidatedMethod({ ... });
module.exports.default = incompleteCountDenormalizer;
```
You can also simply write `exports` instead of `module.exports` if you prefer. If you need to `require` from an ES2015 module with a `default` export, you can access the export with `require('package').default`.
There is a case where you might *need* to use CommonJS, even if your project has the `ecmascript` package: if you want to conditionally include a module. `import` statements must be at top-level scope, so they cannot be within an `if` block. If youre writing a common file, loaded on both client and server, you might want to import a module in only one or the other environment:
```js
if (Meteor.isClient) {
require('./client-only-file.js');
}
```
Note that dynamic calls to `require()` (where the name being required can change at runtime) cannot be analyzed correctly and may result in broken client bundles. This is also discussed in [the guide](http://guide.meteor.com/structure.html#using-require).
### CoffeeScript
CoffeeScript has been a first-class supported language since Meteors early days. Even though today we recommend ES2015, we still intend to support CoffeeScript fully.
As of CoffeeScript 1.11.0, [CoffeeScript supports `import` and `export` statements natively](http://coffeescript.org/#modules). Make sure you are using the latest version of the [CoffeeScript package](https://atmospherejs.com/meteor/coffeescript) in your project to get this support. New projects created today will get this version with `meteor add coffeescript`. Make sure you dont forget to include the `ecmascript` and `modules` packages: `meteor add ecmascript`. (The `modules` package is implied by `ecmascript`.)
CoffeeScript `import` syntax is nearly identical to the ES2015 syntax you see above:
```coffee
import { Meteor } from 'meteor/meteor'
import SimpleSchema from 'simpl-schema'
import { Lists } from './lists.coffee'
```
You can also use traditional CommonJS syntax with CoffeeScript.
## Modular application structure
Use in your application `package.json` file the section `meteor`.
> This is available since Meteor 1.7
```json
{
"meteor": {
"mainModule": {
"client": "client/main.js",
"server": "server/main.js"
}
}
}
```
When specified, these entry points will define in which files Meteor is going to start the evaluation process for each architecture (client and server).
This way Meteor is not going to eager load any other js files.
There is also an architecture for the `legacy` client, which is useful if you want to load polyfills or other code for old browsers before importing the main module for the modern client.
In addition to `meteor.mainModule`, the `meteor` section of `package.json` may also specify `meteor.testModule` to control which test modules are loaded by `meteor test` or `meteor test --full-app`:
```json
{
"meteor": {
"mainModule": {
"client": "client/main.js",
"server": "server/main.js"
},
"testModule": "tests.js"
}
}
```
If your client and server test files are different, you can expand the testModule configuration using the same syntax as mainModule:
```json
{
"meteor": {
"mainModule": {
"client": "client/main.js",
"server": "server/main.js"
},
"testModule": {
"client": "client/tests.js",
"server": "server/tests.js"
}
}
}
```
The same test module will be loaded whether or not you use the `--full-app` option.
Any tests that need to detect `--full-app` should check `Meteor.isAppTest`.
The module(s) specified by `meteor.testModule` can import other test modules at runtime, so you can still distribute test files across your codebase; just make sure you import the ones you want to run.
To disable eager loading of modules on a given architecture, simply provide a mainModule value of false:
```json
{
"meteor": {
"mainModule": {
"client": false,
"server": "server/main.js"
}
}
}
```
### Historic behind Modular application structure
If you want to understand how Meteor works without `meteor.mainModule` on `package.json` keep reading this section, but we don't recommend this approach anymore.
Before the release of Meteor 1.3, the only way to share values between files in an application was to assign them to global variables or communicate through shared variables like `Session` (variables which, while not technically global, sure do feel syntactically identical to global variables). With the introduction of modules, one module can refer precisely to the exports of any other specific module, so global variables are no longer necessary.
If you are familiar with modules in Node, you might expect modules not to be evaluated until the first time you import them. However, because earlier versions of Meteor evaluated all of your code when the application started, and we care about backwards compatibility, eager evaluation is still the default behavior.
If you would like a module to be evaluated *lazily* (in other words: on demand, the first time you import it, just like Node does it), then you should put that module in an `imports/` directory (anywhere in your app, not just the root directory), and include that directory when you import the module: `import {stuff} from './imports/lazy'`. Note: files contained by `node_modules/` directories will also be evaluated lazily (more on that below).
## Modular package structure
If you are a package author, in addition to putting `api.use('modules')` or `api.use('ecmascript')` in the `Package.onUse` section of your `package.js` file, you can also use a new API called `api.mainModule` to specify the main entry point for your package:
```js
Package.describe({
name: 'my-modular-package'
});
Npm.depends({
moment: '2.10.6'
});
Package.onUse((api) => {
api.use('modules');
api.mainModule('server.js', 'server');
api.mainModule('client.js', 'client');
api.export('Foo');
});
```
Now `server.js` and `client.js` can import other files from the package source directory, even if those files have not been added using the `api.addFiles` function.
When you use `api.mainModule`, the exports of the main module are exposed globally as `Package['my-modular-package']`, along with any symbols exported by `api.export`, and thus become available to any code that imports the package. In other words, the main module gets to decide what value of `Foo` will be exported by `api.export`, as well as providing other properties that can be explicitly imported from the package:
```js
// In an application that uses 'my-modular-package':
import { Foo as ExplicitFoo, bar } from 'meteor/my-modular-package';
console.log(Foo); // Auto-imported because of `api.export`.
console.log(ExplicitFoo); // Explicitly imported, but identical to `Foo`.
console.log(bar); // Exported by server.js or client.js, but not auto-imported.
```
Note that the `import` is `from 'meteor/my-modular-package'`, not `from 'my-modular-package'`. Meteor package identifier strings must include the prefix `meteor/...` to disambiguate them from npm packages.
Finally, since this package is using the new `modules` package, and the package `Npm.depends` on the "moment" npm package, modules within the package can `import moment from 'moment'` on both the client and the server. This is great news, because previous versions of Meteor allowed npm imports only on the server, via `Npm.require`.
### Lazy loading modules from a package
Packages can also specify a *lazy* main module:
```js
Package.onUse(function (api) {
api.mainModule("client.js", "client", { lazy: true });
});
```
This means the `client.js` module will not be evaluated during app
startup unless/until another module imports it, and will not even be
included in the client bundle if no importing code is found.
To import a method named `exportedPackageMethod`, simply:
```js
import { exportedPackageMethod } from "meteor/<package name>";
```
> Note: Packages with `lazy` main modules cannot use `api.export` to export global
symbols to other packages/apps. Also, prior to Meteor 1.4.4.2 it is necessary to explicitly name the file containing the module: `import "meteor/<package name>/client.js"`.
## Local `node_modules`
Before Meteor 1.3, the contents of `node_modules` directories in Meteor application code were completely ignored. When you enable `modules`, those useless `node_modules` directories suddenly become infinitely more useful:
```sh
meteor create modular-app
cd modular-app
mkdir node_modules
npm install moment
echo "import moment from 'moment';" >> modular-app.js
echo 'console.log(moment().calendar());' >> modular-app.js
meteor
```
When you run this app, the `moment` library will be imported on both the client and the server, and both consoles will log output similar to: `Today at 7:51 PM`. Our hope is that the possibility of installing Node modules directly within an app will reduce the need for npm wrapper packages such as https://atmospherejs.com/momentjs/moment.
A version of the `npm` command comes bundled with every Meteor installation, and (as of Meteor 1.3) it's quite easy to use: `meteor npm ...` is synonymous with `npm ...`, so `meteor npm install moment` will work in the example above. (Likewise, if you don't have a version of `node` installed, or you want to be sure you're using the exact same version of `node` that Meteor uses, `meteor node ...` is a convenient shortcut.) That said, you can use any version of `npm` that you happen to have available. Meteor's module system only cares about the files installed by `npm`, not the details of how `npm` installs those files.
## File load order
Before Meteor 1.3, the order in which application files were evaluated was dictated by a set of rules described in the [Application Structure - Default file load order](http://guide.meteor.com/structure.html#load-order) section of the Meteor Guide. These rules could become frustrating when one file depended on a variable defined by another file, particularly when the first file was evaluated after the second file.
Thanks to modules, any load-order dependency you might imagine can be resolved by adding an `import` statement. So if `a.js` loads before `b.js` because of their file names, but `a.js` needs something defined by `b.js`, then `a.js` can simply `import` that value from `b.js`:
```js
// a.js
import { bThing } from './b';
console.log(bThing, 'in a.js');
```
```js
// b.js
export var bThing = 'a thing defined in b.js';
console.log(bThing, 'in b.js');
```
Sometimes a module doesnt actually need to import anything from another module, but you still want to be sure the other module gets evaluated first. In such situations, you can use an even simpler `import` syntax:
```js
// c.js
import './a';
console.log('in c.js');
```
No matter which of these modules is imported first, the order of the `console.log` calls will always be:
```js
console.log(bThing, 'in b.js');
console.log(bThing, 'in a.js');
console.log('in c.js');
```

View File

@@ -0,0 +1,97 @@
# OAuth Encryption
Encrypts sensitive login secrets stored in the database such as a
login service's application secret key and users' access tokens.
## Generating a Key
The encryption key is 16 bytes, encoded in Base64.
To generate a key:
```bash
$ meteor node -e 'console.log(require("crypto").randomBytes(16).toString("base64"))'
```
## Using oauth-encryption with accounts
On the server only, use the `oauthSecretKey` option to `Accounts.config`:
```js
Accounts.config({ oauthSecretKey: 'onsqJ+1e4iGFlV0nhZYobg==' });
```
This call to `Accounts.config` should be made at load time (place at
the top level of your source file), not called from inside of a
`Meteor.startup` block.
To avoid storing the secret key in your application's source code, you
can use [`Meteor.settings`](../api/meteor.md#Meteor-settings):
```js
Accounts.config({ oauthSecretKey: Meteor.settings.oauthSecretKey });
```
## Migrating unencrypted user tokens
This example for Twitter shows how existing unencrypted user tokens
can be encrypted. The query finds user documents which have a Twitter
access token but not the `algorithm` field which is created when the
token is encrypted. The relevant fields in the service data are then
encrypted.
```js
const cursor = Meteor.users.find({
$and: [
{ 'services.twitter.accessToken': { $exists: true } },
{ 'services.twitter.accessToken.algorithm': { $exists: false } }
]
});
cursor.forEach((userDoc) => {
const set = {};
['accessToken', 'accessTokenSecret', 'refreshToken'].forEach((field) => {
const plaintext = userDoc.services.twitter[field];
if (!_.isString(plaintext)) {
return;
}
set[`services.twitter.${field}`] = OAuthEncryption.seal(
plaintext,
userDoc._id
);
});
Meteor.users.update(userDoc._id, { $set: set });
});
```
## Using oauth-encryption without accounts
If you're using the oauth packages directly instead of through the
Meteor accounts packages, you can load the OAuth encryption key
directly using `OAuthEncryption.loadKey`:
```js
OAuthEncryption.loadKey('onsqJ+1e4iGFlV0nhZYobg==');
```
If you call `retrieveCredential` (such as
`Twitter.retrieveCredential`) as part of your process, you'll find
when using oauth-encryption that the sensitive service data fields
will be encrypted.
You can decrypt them using `OAuth.openSecrets`:
```js
const credentials = Twitter.retrieveCredential(token);
const serviceData = OAuth.openSecrets(credentials.serviceData);
```
## Using oauth-encryption on Windows
This package depends on [npm-node-aes-gcm](https://github.com/meteor/meteor/tree/devel/packages/non-core/npm-node-aes-gcm), which requires you to have OpenSSL installed on your system to run. To install OpenSSL on Windows, use one of the binaries on [this page](http://slproweb.com/products/Win32OpenSSL.html). Don't forget to install the Visual Studio 2008 redistributables if you don't have them yet.

View File

@@ -0,0 +1,155 @@
[//]: # (Do not edit this file by hand.)
[//]: # (This is a generated file.)
[//]: # (If you want to change something in this file)
[//]: # (go to meteor/docs/generators/packages-listing)
# Core Packages
### [blaze](https://github.com/meteor/blaze) {#blaze}
### [react-packages](https://github.com/meteor/react-packages) {#react-packages}
### [accounts-2fa](https://github.com/meteor/meteor/tree/devel/packages/accounts-2fa) {#accounts-2fa}
### [accounts-base](https://github.com/meteor/meteor/tree/devel/packages/accounts-base) {#accounts-base}
### [accounts-facebook](https://github.com/meteor/meteor/tree/devel/packages/accounts-facebook) {#accounts-facebook}
### [accounts-github](https://github.com/meteor/meteor/tree/devel/packages/accounts-github) {#accounts-github}
### [accounts-google](https://github.com/meteor/meteor/tree/devel/packages/accounts-google) {#accounts-google}
### [accounts-meetup](https://github.com/meteor/meteor/tree/devel/packages/accounts-meetup) {#accounts-meetup}
### [accounts-meteor-developer](https://github.com/meteor/meteor/tree/devel/packages/accounts-meteor-developer) {#accounts-meteor-developer}
### [accounts-oauth](https://github.com/meteor/meteor/tree/devel/packages/accounts-oauth) {#accounts-oauth}
### [accounts-password](https://github.com/meteor/meteor/tree/devel/packages/accounts-password) {#accounts-password}
### [accounts-passwordless](https://github.com/meteor/meteor/tree/devel/packages/accounts-passwordless) {#accounts-passwordless}
### [accounts-twitter](https://github.com/meteor/meteor/tree/devel/packages/accounts-twitter) {#accounts-twitter}
### [accounts-ui](https://github.com/meteor/meteor/tree/devel/packages/accounts-ui) {#accounts-ui}
### [accounts-ui-unstyled](https://github.com/meteor/meteor/tree/devel/packages/accounts-ui-unstyled) {#accounts-ui-unstyled}
### [accounts-weibo](https://github.com/meteor/meteor/tree/devel/packages/accounts-weibo) {#accounts-weibo}
### [allow-deny](https://github.com/meteor/meteor/tree/devel/packages/allow-deny) {#allow-deny}
### [audit-argument-checks](https://github.com/meteor/meteor/tree/devel/packages/audit-argument-checks) {#audit-argument-checks}
### [autopublish](https://github.com/meteor/meteor/tree/devel/packages/autopublish) {#autopublish}
### [autoupdate](https://github.com/meteor/meteor/tree/devel/packages/autoupdate) {#autoupdate}
### [babel-compiler](https://github.com/meteor/meteor/tree/devel/packages/babel-compiler) {#babel-compiler}
### [babel-runtime](https://github.com/meteor/meteor/tree/devel/packages/babel-runtime) {#babel-runtime}
### [base64](https://github.com/meteor/meteor/tree/devel/packages/base64) {#base64}
### [binary-heap](https://github.com/meteor/meteor/tree/devel/packages/binary-heap) {#binary-heap}
### [boilerplate-generator](https://github.com/meteor/meteor/tree/devel/packages/boilerplate-generator) {#boilerplate-generator}
### [boilerplate-generator-tests](https://github.com/meteor/meteor/tree/devel/packages/boilerplate-generator-tests) {#boilerplate-generator-tests}
### [browser-policy](https://github.com/meteor/meteor/tree/devel/packages/browser-policy) {#browser-policy}
### [browser-policy-common](https://github.com/meteor/meteor/tree/devel/packages/browser-policy-common) {#browser-policy-common}
### [browser-policy-content](https://github.com/meteor/meteor/tree/devel/packages/browser-policy-content) {#browser-policy-content}
### [browser-policy-framing](https://github.com/meteor/meteor/tree/devel/packages/browser-policy-framing) {#browser-policy-framing}
### [caching-compiler](https://github.com/meteor/meteor/tree/devel/packages/caching-compiler) {#caching-compiler}
### [callback-hook](https://github.com/meteor/meteor/tree/devel/packages/callback-hook) {#callback-hook}
### [check](https://github.com/meteor/meteor/tree/devel/packages/check) {#check}
### [constraint-solver](https://github.com/meteor/meteor/tree/devel/packages/constraint-solver) {#constraint-solver}
### [context](https://github.com/meteor/meteor/tree/devel/packages/context) {#context}
### [core-runtime](https://github.com/meteor/meteor/tree/devel/packages/core-runtime) {#core-runtime}
### [crosswalk](https://github.com/meteor/meteor/tree/devel/packages/crosswalk) {#crosswalk}
### [ddp](https://github.com/meteor/meteor/tree/devel/packages/ddp) {#ddp}
### [ddp-client](https://github.com/meteor/meteor/tree/devel/packages/ddp-client) {#ddp-client}
### [ddp-common](https://github.com/meteor/meteor/tree/devel/packages/ddp-common) {#ddp-common}
### [ddp-rate-limiter](https://github.com/meteor/meteor/tree/devel/packages/ddp-rate-limiter) {#ddp-rate-limiter}
### [ddp-server](https://github.com/meteor/meteor/tree/devel/packages/ddp-server) {#ddp-server}
### [deprecated](https://github.com/meteor/meteor/tree/devel/packages/deprecated) {#deprecated}
### [dev-error-overlay](https://github.com/meteor/meteor/tree/devel/packages/dev-error-overlay) {#dev-error-overlay}
### [diff-sequence](https://github.com/meteor/meteor/tree/devel/packages/diff-sequence) {#diff-sequence}
### [disable-oplog](https://github.com/meteor/meteor/tree/devel/packages/disable-oplog) {#disable-oplog}
### [dynamic-import](https://github.com/meteor/meteor/tree/devel/packages/dynamic-import) {#dynamic-import}
### [ecmascript](https://github.com/meteor/meteor/tree/devel/packages/ecmascript) {#ecmascript}
### [ecmascript-runtime](https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime) {#ecmascript-runtime}
### [ecmascript-runtime-client](https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime-client) {#ecmascript-runtime-client}
### [ecmascript-runtime-server](https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime-server) {#ecmascript-runtime-server}
### [ejson](https://github.com/meteor/meteor/tree/devel/packages/ejson) {#ejson}
### [email](https://github.com/meteor/meteor/tree/devel/packages/email) {#email}
### [es5-shim](https://github.com/meteor/meteor/tree/devel/packages/es5-shim) {#es5-shim}
### [facebook-config-ui](https://github.com/meteor/meteor/tree/devel/packages/facebook-config-ui) {#facebook-config-ui}
### [facebook-oauth](https://github.com/meteor/meteor/tree/devel/packages/facebook-oauth) {#facebook-oauth}
### [facts-base](https://github.com/meteor/meteor/tree/devel/packages/facts-base) {#facts-base}
### [facts-ui](https://github.com/meteor/meteor/tree/devel/packages/facts-ui) {#facts-ui}
### [fetch](https://github.com/meteor/meteor/tree/devel/packages/fetch) {#fetch}
### [force-ssl](https://github.com/meteor/meteor/tree/devel/packages/force-ssl) {#force-ssl}
### [force-ssl-common](https://github.com/meteor/meteor/tree/devel/packages/force-ssl-common) {#force-ssl-common}
### [geojson-utils](https://github.com/meteor/meteor/tree/devel/packages/geojson-utils) {#geojson-utils}
### [github-config-ui](https://github.com/meteor/meteor/tree/devel/packages/github-config-ui) {#github-config-ui}
### [github-oauth](https://github.com/meteor/meteor/tree/devel/packages/github-oauth) {#github-oauth}
### [google-config-ui](https://github.com/meteor/meteor/tree/devel/packages/google-config-ui) {#google-config-ui}
### [google-oauth](https://github.com/meteor/meteor/tree/devel/packages/google-oauth) {#google-oauth}
### [hot-code-push](https://github.com/meteor/meteor/tree/devel/packages/hot-code-push) {#hot-code-push}
### [hot-module-replacement](https://github.com/meteor/meteor/tree/devel/packages/hot-module-replacement) {#hot-module-replacement}
### [id-map](https://github.com/meteor/meteor/tree/devel/packages/id-map) {#id-map}
### [insecure](https://github.com/meteor/meteor/tree/devel/packages/insecure) {#insecure}
### [inter-process-messaging](https://github.com/meteor/meteor/tree/devel/packages/inter-process-messaging) {#inter-process-messaging}
### [launch-screen](https://github.com/meteor/meteor/tree/devel/packages/launch-screen) {#launch-screen}
### [localstorage](https://github.com/meteor/meteor/tree/devel/packages/localstorage) {#localstorage}
### [logging](https://github.com/meteor/meteor/tree/devel/packages/logging) {#logging}
### [logic-solver](https://github.com/meteor/meteor/tree/devel/packages/logic-solver) {#logic-solver}
### [meetup-config-ui](https://github.com/meteor/meteor/tree/devel/packages/meetup-config-ui) {#meetup-config-ui}
### [meetup-oauth](https://github.com/meteor/meteor/tree/devel/packages/meetup-oauth) {#meetup-oauth}
### [meteor](https://github.com/meteor/meteor/tree/devel/packages/meteor) {#meteor}
### [meteor-base](https://github.com/meteor/meteor/tree/devel/packages/meteor-base) {#meteor-base}
### [meteor-developer-config-ui](https://github.com/meteor/meteor/tree/devel/packages/meteor-developer-config-ui) {#meteor-developer-config-ui}
### [meteor-developer-oauth](https://github.com/meteor/meteor/tree/devel/packages/meteor-developer-oauth) {#meteor-developer-oauth}
### [meteor-platform](https://github.com/meteor/meteor/tree/devel/packages/meteor-platform) {#meteor-platform}
### [meteor-tool](https://github.com/meteor/meteor/tree/devel/packages/meteor-tool) {#meteor-tool}
### [minifier-css](https://github.com/meteor/meteor/tree/devel/packages/minifier-css) {#minifier-css}
### [minifier-js](https://github.com/meteor/meteor/tree/devel/packages/minifier-js) {#minifier-js}
### [minimongo](https://github.com/meteor/meteor/tree/devel/packages/minimongo) {#minimongo}
### [mobile-experience](https://github.com/meteor/meteor/tree/devel/packages/mobile-experience) {#mobile-experience}
### [mobile-status-bar](https://github.com/meteor/meteor/tree/devel/packages/mobile-status-bar) {#mobile-status-bar}
### [modern-browsers](https://github.com/meteor/meteor/tree/devel/packages/modern-browsers) {#modern-browsers}
### [modules](https://github.com/meteor/meteor/tree/devel/packages/modules) {#modules}
### [modules-runtime](https://github.com/meteor/meteor/tree/devel/packages/modules-runtime) {#modules-runtime}
### [modules-runtime-hot](https://github.com/meteor/meteor/tree/devel/packages/modules-runtime-hot) {#modules-runtime-hot}
### [mongo](https://github.com/meteor/meteor/tree/devel/packages/mongo) {#mongo}
### [mongo-dev-server](https://github.com/meteor/meteor/tree/devel/packages/mongo-dev-server) {#mongo-dev-server}
### [mongo-id](https://github.com/meteor/meteor/tree/devel/packages/mongo-id) {#mongo-id}
### [mongo-livedata](https://github.com/meteor/meteor/tree/devel/packages/mongo-livedata) {#mongo-livedata}
### [npm-mongo](https://github.com/meteor/meteor/tree/devel/packages/npm-mongo) {#npm-mongo}
### [oauth](https://github.com/meteor/meteor/tree/devel/packages/oauth) {#oauth}
### [oauth-encryption](https://github.com/meteor/meteor/tree/devel/packages/oauth-encryption) {#oauth-encryption}
### [oauth1](https://github.com/meteor/meteor/tree/devel/packages/oauth1) {#oauth1}
### [oauth2](https://github.com/meteor/meteor/tree/devel/packages/oauth2) {#oauth2}
### [ordered-dict](https://github.com/meteor/meteor/tree/devel/packages/ordered-dict) {#ordered-dict}
### [package-stats-opt-out](https://github.com/meteor/meteor/tree/devel/packages/package-stats-opt-out) {#package-stats-opt-out}
### [package-version-parser](https://github.com/meteor/meteor/tree/devel/packages/package-version-parser) {#package-version-parser}
### [promise](https://github.com/meteor/meteor/tree/devel/packages/promise) {#promise}
### [random](https://github.com/meteor/meteor/tree/devel/packages/random) {#random}
### [rate-limit](https://github.com/meteor/meteor/tree/devel/packages/rate-limit) {#rate-limit}
### [react-fast-refresh](https://github.com/meteor/meteor/tree/devel/packages/react-fast-refresh) {#react-fast-refresh}
### [reactive-dict](https://github.com/meteor/meteor/tree/devel/packages/reactive-dict) {#reactive-dict}
### [reactive-var](https://github.com/meteor/meteor/tree/devel/packages/reactive-var) {#reactive-var}
### [reload](https://github.com/meteor/meteor/tree/devel/packages/reload) {#reload}
### [reload-safetybelt](https://github.com/meteor/meteor/tree/devel/packages/reload-safetybelt) {#reload-safetybelt}
### [retry](https://github.com/meteor/meteor/tree/devel/packages/retry) {#retry}
### [routepolicy](https://github.com/meteor/meteor/tree/devel/packages/routepolicy) {#routepolicy}
### [server-render](https://github.com/meteor/meteor/tree/devel/packages/server-render) {#server-render}
### [service-configuration](https://github.com/meteor/meteor/tree/devel/packages/service-configuration) {#service-configuration}
### [session](https://github.com/meteor/meteor/tree/devel/packages/session) {#session}
### [sha](https://github.com/meteor/meteor/tree/devel/packages/sha) {#sha}
### [shell-server](https://github.com/meteor/meteor/tree/devel/packages/shell-server) {#shell-server}
### [socket-stream-client](https://github.com/meteor/meteor/tree/devel/packages/socket-stream-client) {#socket-stream-client}
### [standard-app-packages](https://github.com/meteor/meteor/tree/devel/packages/standard-app-packages) {#standard-app-packages}
### [standard-minifier-css](https://github.com/meteor/meteor/tree/devel/packages/standard-minifier-css) {#standard-minifier-css}
### [standard-minifier-js](https://github.com/meteor/meteor/tree/devel/packages/standard-minifier-js) {#standard-minifier-js}
### [standard-minifiers](https://github.com/meteor/meteor/tree/devel/packages/standard-minifiers) {#standard-minifiers}
### [static-html](https://github.com/meteor/meteor/tree/devel/packages/static-html) {#static-html}
### [test-helpers](https://github.com/meteor/meteor/tree/devel/packages/test-helpers) {#test-helpers}
### [test-in-browser](https://github.com/meteor/meteor/tree/devel/packages/test-in-browser) {#test-in-browser}
### [test-in-console](https://github.com/meteor/meteor/tree/devel/packages/test-in-console) {#test-in-console}
### [test-server-tests-in-console-once](https://github.com/meteor/meteor/tree/devel/packages/test-server-tests-in-console-once) {#test-server-tests-in-console-once}
### [tinytest](https://github.com/meteor/meteor/tree/devel/packages/tinytest) {#tinytest}
### [tinytest-harness](https://github.com/meteor/meteor/tree/devel/packages/tinytest-harness) {#tinytest-harness}
### [tracker](https://github.com/meteor/meteor/tree/devel/packages/tracker) {#tracker}
### [twitter-config-ui](https://github.com/meteor/meteor/tree/devel/packages/twitter-config-ui) {#twitter-config-ui}
### [twitter-oauth](https://github.com/meteor/meteor/tree/devel/packages/twitter-oauth) {#twitter-oauth}
### [typescript](https://github.com/meteor/meteor/tree/devel/packages/typescript) {#typescript}
### [underscore](https://github.com/meteor/meteor/tree/devel/packages/underscore) {#underscore}
### [underscore-tests](https://github.com/meteor/meteor/tree/devel/packages/underscore-tests) {#underscore-tests}
### [url](https://github.com/meteor/meteor/tree/devel/packages/url) {#url}
### [webapp](https://github.com/meteor/meteor/tree/devel/packages/webapp) {#webapp}
### [webapp-hashing](https://github.com/meteor/meteor/tree/devel/packages/webapp-hashing) {#webapp-hashing}
### [weibo-config-ui](https://github.com/meteor/meteor/tree/devel/packages/weibo-config-ui) {#weibo-config-ui}
### [weibo-oauth](https://github.com/meteor/meteor/tree/devel/packages/weibo-oauth) {#weibo-oauth}

View File

@@ -0,0 +1,15 @@
# Random
The `random` package provides several functions for generating random
numbers. It uses a cryptographically strong pseudorandom number generator when
possible, but falls back to a weaker random number generator when
cryptographically strong randomness is not available (on older browsers or on
servers that don't have enough entropy to seed the cryptographically strong
generator).
<ApiBox name="Random.id" />
<ApiBox name="Random.secret" />
<ApiBox name="Random.fraction" />
<ApiBox name="Random.choice" />
<ApiBox name="Random.hexString" />

View File

@@ -0,0 +1,209 @@
# Server Rendering
This package implements generic support for server-side rendering in
Meteor apps, by providing a mechanism for injecting fragments of HTML into
the `<head>` and/or `<body>` of the application's initial HTML response.
## Usage
This package exports a function named `onPageLoad` which takes a callback
function that will be called at page load (on the client) or whenever a
new request happens (on the server).
The callback receives a `sink` object, which is an instance of either
`ClientSink` or `ServerSink` depending on the environment. Both types of
`sink` have the same methods, though the server version accepts only HTML
strings as content, whereas the client version also accepts DOM nodes.
The current interface of `{Client,Server}Sink` objects is as follows:
```js
class Sink {
// Appends content to the <head>.
appendToHead(content)
// Appends content to the <body>.
appendToBody(content)
// Appends content to the identified element.
appendToElementById(id, content)
// Replaces the content of the identified element.
renderIntoElementById(id, content)
// Redirects request to new location.
redirect(location, code)
// server only methods
// sets the status code of the response.
setStatusCode(code)
// sets a header of the response.
setHeader(key, value)
// gets request headers
getHeaders()
// gets request cookies
getCookies()
}
```
The `sink` object may also expose additional properties depending on the
environment. For example, on the server, `sink.request` provides access to
the current `request` object, and `sink.arch` identifies the target
architecture of the pending HTTP response (e.g. "web.browser").
Here is a basic example of `onPageLoad` usage on the server:
::: code-group
```js [server.js]
import from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import App from "/imports/Server.js";
onPageLoad(sink => {
sink.renderIntoElementById("app", renderToString(
<App location={sink.request.url} />
));
});
```
Likewise on the client:
```js [client.js]
import React from "react";
import ReactDOM from "react-dom";
import { onPageLoad } from "meteor/server-render";
onPageLoad(async (sink) => {
const App = (await import("/imports/Client.js")).default;
ReactDOM.hydrate(<App />, document.getElementById("app"));
});
```
:::
Note that the `onPageLoad` callback function is allowed to return a
`Promise` if it needs to do any asynchronous work, and thus may be
implemented by an `async` function (as in the client case above).
Note also that the client example does not end up calling any methods of
the `sink` object, because `ReactDOM.hydrate` has its own similar API. In
fact, you are not even required to use the `onPageLoad` API on the client,
if you have your own ideas about how the client should do its rendering.
Here is a more complicated example of `onPageLoad` usage on the server,
involving the [`styled-components`](https://www.styled-components.com/docs/advanced#server-side-rendering) npm package:
```js
import React from "react";
import { onPageLoad } from "meteor/server-render";
import { renderToString } from "react-dom/server";
import { ServerStyleSheet } from "styled-components";
import App from "/imports/Server";
onPageLoad((sink) => {
const sheet = new ServerStyleSheet();
const html = renderToString(
sheet.collectStyles(<App location={sink.request.url} />)
);
sink.renderIntoElementById("app", html);
sink.appendToHead(sheet.getStyleTags());
});
```
In this example, the callback not only renders the `<App />` element into
the element with `id="app"`, but also appends any `<style>` tag(s)
generated during rendering to the `<head>` of the response document.
Although these examples have all involved React, the `onPageLoad` API is
designed to be generically useful for any kind of server-side rendering.
## Streaming HTML
React 16 introduced [`renderToNodeStream`](https://reactjs.org/docs/react-dom-server.html#rendertonodestream), which enables the reading of rendered HTML in chunks. This reduces the [TTFB](https://en.wikipedia.org/wiki/Time_to_first_byte) (time to first byte).
Here is a `renderToNodeStream` example using [styled-components](https://www.styled-components.com). Note the use of `sheet.interleaveWithNodeStream` instead of `sink.appendToHead(sheet.getStyleTags());`:
```js
import React from "react";
import { onPageLoad } from "meteor/server-render";
import { renderToNodeStream } from "react-dom/server";
import { ServerStyleSheet } from "styled-components";
import App from "/imports/Server";
onPageLoad((sink) => {
const sheet = new ServerStyleSheet();
const appJSX = sheet.collectStyles(<App location={sink.request.url} />);
const htmlStream = sheet.interleaveWithNodeStream(renderToNodeStream(appJSX));
sink.renderIntoElementById("app", htmlStream);
});
```
## Getting data from the request
In some cases you want to customize meta tags or something else in your response based in the requested URL, for example, if your are loading a page with a specific product in your app maybe you want to include an image and a description for [social previews](https://www.contentkingapp.com/academy/open-graph/).
You can extract information from the request using the `sink` object.
```js
import { onPageLoad } from "meteor/server-render";
const getBaseUrlFromHeaders = (headers) => {
const protocol = headers["x-forwarded-proto"];
const { host } = headers;
// we need to have '//' to findOneByHost work as expected
return `${protocol ? `${protocol}:` : ""}//${host}`;
};
const getContext = (sink) => {
// more details about this implementation here
// https://github.com/meteor/meteor/issues/9765
const { headers, url, browser } = sink.request;
// no useful data will be found for galaxybot requests
if (browser && browser.name === "galaxybot") {
return null;
}
// when we are running inside cordova we don't want to resolve meta tags
if (url && url.pathname && url.pathname.includes("cordova/")) {
return null;
}
const baseUrl = getBaseUrlFromHeaders(headers);
const fullUrl = `${baseUrl}${url.pathname || ""}`;
return { baseUrl, fullUrl };
};
onPageLoad((sink) => {
const { baseUrl, fullUrl } = getContext(sink);
// product URL contains /product on it
const urlParseArray = fullUrl.split("/");
const productPosition = urlParseArray.indexOf("product");
const productId =
productPosition !== -1 &&
urlParseArray[productPosition + 1].replace("?", "");
const product = productId && ProductsCollection.findOne(productId);
const productTitle = product && `Buy now ${product.name}, ${product.price}`;
if (productTitle) {
sink.appendToHead(`<title>${productTitle}</title>\n`);
sink.appendToHead(`<meta property="og:title" content="${productTitle}">\n`);
if (product.imageUrl) {
sink.appendToHead(
`<meta property="og:image" content="${product.imageUrl}">\n`
);
}
}
});
```

View File

@@ -0,0 +1,65 @@
# Standard Minifier CSS
This package is the default css minifier in Meteor apps. In addition to minifying the css code in production builds, it also runs any PostCSS plugins configured for the app.
## Post CSS
This package can optionally run [PostCSS](https://postcss.org/) plugins on the css files in your app. To enable:
1. Install npm peer dependencies:
```sh
meteor npm install -D postcss postcss-load-config
```
2. Add PostCSS Config. Create a `postcss.config.js` file and add a config:
```js
module.exports = {
plugins: {
autoprefixer: {},
}
};
```
> The example config enables the `autoprefixer` postcss plugin. You can install the plugin by running `meteor npm install -D autoprefixer`.
Learn more about [configuring postcss](https://github.com/postcss/postcss-load-config#packagejson) or find a list of [available plugins](https://www.postcss.parts/).
After making changes to the PostCSS Config, `meteor` must be restarted for it to use the new config.
### Exclude Meteor Packages
In addition to the css files in your app, PostCSS will also process the css files added from Meteor packages. In case you do not want these files to be processed, or they are not compatible with your PostCSS config, you can have PostCSS ignore them by using the `excludedMeteorPackages` option:
```js
module.exports = {
plugins: {
autoprefixer: {},
},
excludedMeteorPackages: [
'github-config-ui',
'constellation:console'
]
};
```
### Tailwind CSS
Tailwind CSS is fully supported. Since HMR applies updates to js files earlier than the css is updated, there can be a delay when using a Tailwind CSS class the first time before the styles are applied.
### Debbuging
_Since Meteor.js 2.11.0 in this [PR](https://github.com/meteor/meteor/pull/12478) we have a debbug mode for the minifier_
#### How standard-minifier-css becomes verbose
- Either of the common debugging commandline arguments
- `--verbose`
- `--debug`
- Environment variable
- `DEBUG_CSS`
Side notes:
`DEBUG_CSS=false` or `DEBUG_CSS=0` will prevent it from being verbose regardless of `--verbose` or `--debug` commandline arguments, because `DEBUG_CSS` is specific.

View File

@@ -0,0 +1,19 @@
# Underscore
[Underscore](http://underscorejs.org/) is a utility-belt library for
JavaScript that provides support for functional programming. It is
invaluable for writing clear, concise JavaScript in a functional style.
The `underscore` package defines the `_` namespace on both the client
and the server.
::: warning
We have slightly modified the way Underscore differentiates between
objects and arrays in [collection functions](http://underscorejs.org/#each).
The original Underscore logic is to treat any object with a numeric `length`
property as an array (which helps it work properly on
[`NodeList`s](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)).
In Meteor's version of Underscore, objects with a numeric `length` property
are treated as objects if they have no prototype (specifically, if
`x.constructor === Object`.
:::

View File

@@ -0,0 +1,62 @@
# URL
`url` package provides polyfill for the [WHATWG url specification](https://url.spec.whatwg.org/) for legacy browsers or defaults to the global class which is available in modern browsers and Node. It is recommended that you use this package for compatibility with non-modern browsers.
For more information we recommend [reading the MDN articles](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) about it and looking over the [Node API documentation](https://nodejs.org/api/url.html#url_the_whatwg_url_api) for more details as this article covers only basic usage in Meteor.
## Usage
### Installation
To add this package to an existing app, run the following command from
your app directory:
```bash
meteor add url
```
To add the `url` package to an existing package, include the
statement `api.use('url');` in the `Package.onUse` callback in your
`package.js` file:
```js
Package.onUse((api) => {
api.use("url");
});
```
After installing the package you can then import the `URL` and `URLSearchParams` from the package and use it as described at MDN and Node documentations.
### URL
```js
import { URL } from "meteor/url";
const url = new URL("https://www.meteor.com");
```
You can then use `URL` for example in a [fetch](/packages/fetch) call:
```js
import { URL } from 'meteor/url';
import { fetch } from 'meteor/fetch';
const url = new URL('https://www.example.com/api/reportVisit');
fetch(url, {
method: 'POST',
body: JSON.stringify({ siteId: 11 })
...
})
```
### URLSearchParams
```js
import { URLSearchParams } from "meteor/url";
const searchParams = new URLSearchParams({ query: "WHATWG", location: "MDN" });
```
You can then include `URLSearchParams` in the options for `URL` if you build them separately from when creating the `URL` class.

View File

@@ -0,0 +1,171 @@
# WebApp
The `webapp` package is what lets your Meteor app serve content to a web
browser. It is included in the `meteor-base` set of packages that is
automatically added when you run `meteor create`. You can easily build a
Meteor app without it - for example if you wanted to make a command-line
tool that still used the Meteor package system and DDP.
This package also allows you to add handlers for HTTP requests.
This lets other services access your app's data through an HTTP API, allowing
it to easily interoperate with tools and frameworks that don't yet support DDP.
`webapp` exposes the [express](https://github.com/expressjs/express) API for
handling requests through `WebApp.handlers`.
Here's an example that will let you handle a specific URL:
```js
// Listen to incoming HTTP requests (can only be used on the server).
WebApp.handlers.use("/hello", (req, res, next) => {
res.writeHead(200);
res.end(`Hello world from: ${Meteor.release}`);
});
```
<ApiBox name="WebApp.handlers"/>
<ApiBox name="expressHandlersCallback(req, res, next)" hasCustomExample/>
### Serving a Static Landing Page
One of the really cool things you can do with WebApp is serve static HTML for a landing page where TTFB (time to first byte) is of utmost importance.
The [Bundle Visualizer](https://docs.meteor.com/packages/bundle-visualizer.html) and [Dynamic Imports](https://docs.meteor.com/packages/dynamic-import.html) are great tools to help you minimize initial page load times. But sometimes you just need to skinny down your initial page load to bare metal.
The good news is that WebApp makes this is really easy to do.
Step one is to create a your static HTML file and place it in the _private_ folder at the root of your application.
Here's a sample _index.html_ you might use to get started:
::: code-group
```html [index.html]
<head>
<title>Fast Landing Page</title>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no" />
<link rel="stylesheet" href="path to your style sheet etc">
</head>
<body>
<!-- your content -->
</body>
<script>
// any functions you need to support your landing page
</script>
</html>
```
```javascript [index.js]
/* global WebApp Assets */
import crypto from "crypto";
import express from "express";
const router = express.Router();
router.get("/", function (req, res, next) {
const buf = Assets.getText("index.html");
if (buf.length > 0) {
const eTag = crypto.createHash("md5").update(buf).digest("hex");
if (req.headers["if-none-match"] === eTag) {
res.writeHead(304, "Not Modified");
return res.end();
}
res.writeHead(200, {
ETag: eTag,
"Content-Type": "text/html",
});
return res.end(buf);
}
return res.end("<html><body>Index page not found!</body></html>");
});
WebApp.handlers.use(router);
```
:::
There are a couple things to think about with this approach.
We're reading the contents of index.html using the [Assets](../api/assets.md) module that makes it really easy to read files out of the _private_ root folder.
We're using the [connect-route](https://www.npmjs.com/package/connect-route) NPM package to simplify WebApp route processing. But you can use any package you want to understand what is being requested.
And finally, if you decide to use this technique you'll want to make sure you understand how conflicting client side routing will affect user experience.
### Dynamic Runtime Configuration
In some cases it is valuable to be able to control the **meteor_runtime_config** variable that initializes Meteor at runtime.
#### Example
There are occasions when a single Meteor server would like to serve multiple cordova applications that each have a unique `ROOT_URL`. But there are 2 problems:
1. The Meteor server can only be configured to serve a single `ROOT_URL`.
2. The `cordova` applications are build time configured with a specific `ROOT_URL`.
These 2 conditions break `autoupdate` for the cordova applications. `cordova-plugin-meteor-webapp` will fail the update if the `ROOT_URL` from the server does not match the build time configured `ROOT_URL` of the cordova application.
To remedy this problem `webapp` has a hook for dynamically configuring `__meteor_runtime_config__` on the server.
#### Dynamic Runtime Configuration Hook
```js
WebApp.addRuntimeConfigHook(
({ arch, request, encodedCurrentConfig, updated }) => {
// check the request to see if this is a request that requires
// modifying the runtime configuration
if (request.headers.domain === "calling.domain") {
// make changes to the config for this domain
// decode the current runtime config string into an object
const config = WebApp.decodeRuntimeConfig(current);
// make your changes
config.newVar = "some value";
config.oldVar = "new value";
// encode the modified object to the runtime config string
// and return it
return WebApp.encodeRuntimeConfig(config);
}
// Not modifying other domains so return undefined
return undefined;
}
);
```
<ApiBox name="WebApp.addRuntimeConfigHook"/>
<ApiBox name="addRuntimeConfigHookCallback(options)" hasCustomExample/>
Additionally, 2 helper functions are available to decode the runtime config string and encode the runtime config object.
<ApiBox name="WebApp.decodeRuntimeConfig"/>
<ApiBox name="WebApp.encodeRuntimeConfig"/>
### Updated Runtime Configuration Hook
```js
const autoupdateCache;
// Get a notification when the runtime configuration is updated
// for each arch
WebApp.addUpdatedNotifyHook(({arch, manifest, runtimeConfig}) => {
// Example, see if runtimeConfig.autoupdate has changed and if so
// do something
if(!_.isEqual(autoupdateCache, runtimeConfig.autoupdate)) {
autoupdateCache = runtimeConfig.autoupdate;
// do something...
}
})
```
<ApiBox name="WebApp.addUpdatedNotifyHook"/>
<ApiBox name="addUpdatedNotifyHookCallback(options)" hasCustomExample/>
<ApiBox name="main"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

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