Repository: Swydo/ddp-apollo
Branch: master
Commit: e282d40d07f5
Files: 63
Total size: 74.4 KB
Directory structure:
gitextract_3ipm2_dq/
├── .eslintrc.json
├── .gitignore
├── .meteorignore
├── .npmrc
├── .travis.yml
├── .versions
├── CHANGELOG.md
├── LICENSE
├── README.md
├── client.js
├── docs/
│ └── MIGRATION_GUIDE_1_0.md
├── package.js
├── package.json
├── packages/
│ └── apollo-link-ddp/
│ ├── .babelrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── .npmrc
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ └── src/
│ ├── client/
│ │ ├── apollo-link-ddp.js
│ │ ├── apollo-link-meteor-auth.js
│ │ ├── apollo-link-meteor.js
│ │ ├── getLoginToken.js
│ │ ├── graphQLFetcher.js
│ │ └── listenToGraphQLMessages.js
│ ├── common/
│ │ ├── defaults.js
│ │ └── isSubscription.js
│ └── index.js
├── server.js
├── specs/
│ ├── client/
│ │ ├── apollo-client.js
│ │ ├── apollo-link-ddp.js
│ │ ├── apollo-link-meteor-auth.js
│ │ ├── apollo-link-meteor.js
│ │ ├── asteroid.js
│ │ ├── exports.js
│ │ ├── graphQLFetcher.js
│ │ ├── helpers/
│ │ │ ├── callPromise.js
│ │ │ └── login.js
│ │ └── simpleddp.js
│ ├── client.js
│ ├── data/
│ │ ├── pubsub.js
│ │ ├── resolvers.js
│ │ └── typeDefs.js
│ ├── server/
│ │ ├── exports.js
│ │ ├── getUserIdByLoginToken.js
│ │ ├── helpers/
│ │ │ └── setupLoginByUserId.js
│ │ ├── helpers.js
│ │ ├── server.js
│ │ └── setup.js
│ └── server.js
└── src/
├── contextToFunction.js
├── createExecutor.js
├── createGraphQLMethod.js
├── createGraphQLMiddleware.js
├── createGraphQLPublication.js
├── forAwaitEach.js
├── getUserIdByLoginToken.js
├── initSchema.js
├── invokeDDP.js
├── meteorAuthMiddleware.js
├── setup.js
└── setupHttpEndpoint.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "airbnb-base",
"rules": {
"no-plusplus": 0,
"no-underscore-dangle": 0,
"no-unused-expressions": 0,
"import/no-extraneous-dependencies": 0,
"import/no-unresolved": 0,
"import/extensions": 0,
"import/prefer-default-export": 0,
"import/no-named-as-default": 0
},
"globals": {
"Meteor": true,
"Package": true
},
"settings": {
"import/resolver": "meteor"
}
}
================================================
FILE: .gitignore
================================================
node_modules
test/**
================================================
FILE: .meteorignore
================================================
test/**
docs
packages
================================================
FILE: .npmrc
================================================
save-exact=true
package-lock=false
================================================
FILE: .travis.yml
================================================
dist: trusty
sudo: required
language: node_js
node_js:
- "8"
addons:
chrome: stable
cache:
directories:
- ~/.npm
- "node_modules"
before_install:
- "curl https://install.meteor.com | /bin/sh"
- export PATH="$HOME/.meteor:$PATH"
- meteor --version
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3 # give xvfb some time to start
script:
- npm run eslint -s
- npm test -s
================================================
FILE: .versions
================================================
accounts-base@2.2.2
allow-deny@1.1.1
babel-compiler@7.9.0
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
boilerplate-generator@1.7.1
callback-hook@1.4.0
check@1.3.1
ddp@1.4.0
ddp-client@2.5.0
ddp-common@1.4.0
ddp-rate-limiter@1.1.0
ddp-server@2.5.0
diff-sequence@1.1.1
dynamic-import@0.7.2
ecmascript@0.16.2
ecmascript-runtime@0.8.0
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.2
fetch@0.1.1
geojson-utils@1.0.10
id-map@1.1.1
inter-process-messaging@0.1.1
local-test:swydo:ddp-apollo@4.0.2
localstorage@1.2.0
logging@1.3.1
meteor@1.10.0
meteortesting:browser-tests@0.1.2
meteortesting:mocha@0.4.4
minimongo@1.8.0
modern-browsers@0.1.7
modules@0.18.0
modules-runtime@0.13.0
mongo@1.14.6
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.3.1
ordered-dict@1.1.0
practicalmeteor:mocha-core@1.0.1
promise@0.12.0
random@1.2.0
rate-limit@1.0.9
react-fast-refresh@0.2.3
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
routepolicy@1.1.1
service-configuration@1.3.0
socket-stream-client@0.4.0
swydo:ddp-apollo@4.0.2
tracker@1.2.0
underscore@1.0.10
url@1.3.2
webapp@1.13.1
webapp-hashing@1.1.0
================================================
FILE: CHANGELOG.md
================================================
## vNEXT
-
## 4.0.2
- Bump package versions
- Replace old imports using apollo-link-ddp to @swydo/apollo-link-ddp
## 4.0.1
- Import from @swydo/scoped package
- Use named arguments for graphql methods
- Bump apollo and graphql versions
## 4.0.0
- Upgrade to Apollo Client 3 (#392)
- Avoid attribute errors when accessing `connection` (#368)
- Verify that return is a function before executing in onStop
## 3.0.0
- Add support for Apollo Gateway [#356](https://github.com/Swydo/ddp-apollo/pull/356)
## 2.2.0
- Support async iterators in latest Node (and Meteor) versions [#351](https://github.com/Swydo/ddp-apollo/pull/51)
## 2.1.0
- Compile apollo-link-ddp browser code with babel [#300](https://github.com/Swydo/ddp-apollo/pull/300)
## 2.0.1
- Expose `createGraphQLPublication` on the server [#276](https://github.com/Swydo/ddp-apollo/pull/276)
- The client module has been marked as lazy, so if using the Meteor package, `DDPLink` will only be included in the bundle when `import`ed [#277](https://github.com/Swydo/ddp-apollo/pull/277)
- Remove `graphql` dependency from apollo-link-ddp, saving ~250kb [ca42d2cb1](https://github.com/Swydo/ddp-apollo/commit/ca42d2cb1c4a2f73755ecb542b1ee88db3b6c9ac)
## 2.0.0
- Move client code to stand-alone npm package [#207](https://github.com/Swydo/ddp-apollo/pull/207)
## 1.4.0
- Add HTTP support [#168](https://github.com/Swydo/ddp-apollo/pull/168)
- Add a DDP retry switch [#186](https://github.com/Swydo/ddp-apollo/pull/186)
- Allow custom DDP message observer [#198](https://github.com/Swydo/ddp-apollo/pull/198)
- Add `ddpConnection` to the resolver context [#268](https://github.com/Swydo/ddp-apollo/pull/268)
## 1.3.0
- Add ability to pass a client context to the server context via `ddpContext` key.
```js
// client
apolloClient.query({
query,
context: { ddpContext: { foo: 'bar' } }
});
// server
const context = (previousContext, ddpContext) => ({
...previousContext,
foo: ddpContext.foo
});
setup({
schema,
context,
});
```
## 1.2.0
- Remove support for Optics, because as of 2018-01-31 it's no longer operational
## 1.1.0
- Add `context` option to server setup
## 1.0.0
- Support Apollo Client 2.0 with subscriptions
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2016-present Swydo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# DDP-Apollo
DDP-Apollo leverages the power of DDP for GraphQL queries and subscriptions. Meteor developers do not need an HTTP server or extra websocket connection, because DDP offers all we need and has been well tested over time.
<img width="830" alt="Github Header" src="https://user-images.githubusercontent.com/2283434/183906965-4d07a08e-81a7-4960-980d-768dcc188562.png">
- DDP-Apollo is one of the easiest ways to get GraphQL running for Meteor developers
- Works with the Meteor accounts packages out of the box, giving a userId in your resolvers
- Method calls and collection hooks will have `this.userId` when called within your resolvers
- Doesn’t require an HTTP server to be setup, like with express, koa or hapi
- Supports GraphQL Subscriptions out-of-the-box
- Doesn’t require an extra websocket for GraphQL Subscriptions, because DDP already has a websocket
- Already have a server setup? Use `DDPSubscriptionLink` stand-alone for just Subscriptions support. [Read more](#using-ddp-only-for-subscriptions)
# Just another Apollo Link
Because it's "just another Apollo Link":
- It works with [Apollo Dev Tools](https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm)
- It's easy to combine with other Apollo Links
- It's front-end agnostic
# Starter Kit
Checkout this [starter kit](https://github.com/jamiter/meteor-starter-kit) to see Meteor, Apollo, DDP and React all work together.
*Note: DDP-Apollo works with all front-ends, not just React*
[](https://travis-ci.org/Swydo/ddp-apollo)
## Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Client setup](#client-setup)
- [Options](#options)
- [Server setup](#server-setup)
- [Options](#options-1)
- [Custom context](#custom-context)
- [GraphQL subscriptions](#graphql-subscriptions)
- [Setting up PubSub](#setting-up-pubsub)
- [Using DDP only for subscriptions](#using-ddp-only-for-subscriptions)
- [Rate limiting GraphQL calls](#rate-limiting-graphql-calls)
- [HTTP support](#http-support)
- [Installation](#installation-1)
- [Client setup](#client-setup-1)
- [Server setup](#server-setup-1)
- [Options](#options-2)
- [Sponsor](#sponsor)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
```
meteor add swydo:ddp-apollo
```
```
meteor npm install --save @apollo/client @swydo/apollo-link-ddp graphql
```
## Client setup
All client code is in the `@swydo/apollo-link-ddp` npm package. It gives you a `DDPLink` for your Apollo Client. Creating an Apollo Client is the same as with any other Apollo Link.
```javascript
// Choose any cache implementation, but we'll use InMemoryCache as an example
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { DDPLink } from '@swydo/apollo-link-ddp';
export const client = new ApolloClient ({
link: new DDPLink(),
cache: new InMemoryCache()
});
```
### Options
- `connection`: The DDP connection to use. Default `Meteor.connection`.
- `method`: The name of the method. Default `__graphql`.
- `publication`: The name of the publication. Default `__graphql-subscriptions`.
- `ddpRetry`: Retry failed DDP method calls. Default `true`. Switch off and use [apollo-link-retry](https://www.npmjs.com/package/apollo-link-retry) for more control.
- `socket`: Optionally pass a socket to listen to for messages. This makes it easy to integrate with non-Meteor DDP clients.
```javascript
// Pass options to the DDPLink constructor
new DDPLink({
connection: Meteor.connection
});
```
## Server setup
The server will add a method and publication that will be used by the DDP Apollo Link.
```javascript
import { schema } from './path/to/your/executable/schema';
import { setup } from 'meteor/swydo:ddp-apollo';
setup({
schema,
...otherOptions
});
```
### Options
- `schema`: The GraphQL schema. Default `undefined`. Required when no `gateway` is provided.
- `gateway`: An [Apollo Gateway](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-gateway). Default `undefined`. Required when no `schema` is provided.
- `context`: A custom context. Either an object or a function returning an object. Optional.
- `method`: The name of the method. Default `__graphql`.
- `publication`: The name of the publication. Default `__graphql-subscriptions`.
### Custom context
To modify or overrule the default context, you can pass a `context` object or function to the setup:
```js
// As an object:
const context = {
foo: 'bar'
}
// As a function, returning an object:
const context = (currentContext) => ({ ...currentContext, foo: 'bar' });
// As an async function, returning a promise with an object
const context = async (currentContext) => ({ ...currentContext, foo: await doAsyncStuff() });
setup({
schema,
context,
});
```
## GraphQL subscriptions
Subscription support is baked into this package. Simply add the subscriptions to your schema and resolvers and everything works.
*Note: [Apollo Gateway](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-gateway) does not yet support Subscriptions.*
```graphql
# schema.graphql
type Query {
name: String
}
type Subscription {
message: String
}
```
### Setting up PubSub
```sh
meteor npm install --save graphql-subscriptions
```
```javascript
import { PubSub } from 'graphql-subscriptions';
// The pubsub mechanism of your choice, for instance:
// - PubSub from graphql-subscriptions (in-memory, so not recommended for production)
// - RedisPubSub from graphql-redis-subscriptions
// - MQTTPubSub from graphql-mqtt-subscriptions
const pubsub = new PubSub();
export const resolvers = {
Query: {
name: () => 'bar',
},
Subscription: {
message: {
subscribe: () => pubsub.asyncIterator('SOMETHING_CHANGED'),
},
},
};
// Later you can publish updates like this:
pubsub.publish('SOMETHING_CHANGED', { message: 'hello world' });
```
See [graphql-subscriptions](https://github.com/apollographql/graphql-subscriptions) package for more setup details and other pubsub mechanisms. It also explains why the default `PubSub` isn't meant for production.
### Using DDP only for subscriptions
If you already have an HTTP server setup and you are looking to support GraphQL Subscriptions in your Meteor application, you can use the `DDPSubscriptionLink` stand-alone.
```javascript
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
import { DDPSubscriptionLink, isSubscription } from '@swydo/apollo-link-ddp';
const httpLink = new HttpLink({ uri: "/graphql" });
const subscriptionLink = new DDPSubscriptionLink();
const link = split(
isSubscription,
subscriptionLink,
httpLink,
);
export const client = new ApolloClient ({
link,
cache: new InMemoryCache()
});
```
## Rate limiting GraphQL calls
Meteor supports rate limiting for DDP calls. This means you can rate limit DDP-Apollo as well!
```sh
meteor add ddp-rate-limiter
```
```js
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
// Define a rule that matches graphql method calls.
const graphQLMethodCalls = {
type: 'method',
name: '__graphql'
};
// Add the rule, allowing up to 5 messages every 1000 milliseconds.
DDPRateLimiter.addRule(graphQLMethodCalls, 5, 1000);
```
See [DDP Rate Limit documentation](https://docs.meteor.com/api/methods.html#ddpratelimiter).
## HTTP support
There can be reasons to use HTTP instead of a Meteor method. There is support for it built in, but it requires a little different setup than the DDP version.
### Installation
We'll need the HTTP link from Apollo and `body-parser` on top of the default dependencies:
```
meteor npm install @apollo/client body-parser
```
### Client setup
```js
import { ApolloClient, InMemoryCache } from '@apollo/client';
// Use the MeteorLink instead of the DDPLink
// It uses HTTP for queries and Meteor subscriptions (DDP) for GraphQL subscriptions
import { MeteorLink } from '@swydo/apollo-link-ddp';
export const client = new ApolloClient ({
link: new MeteorLink(),
cache: new InMemoryCache()
});
```
### Server setup
```js
import { schema } from './path/to/your/executable/schema';
import { setupHttpEndpoint, createGraphQLPublication } from 'meteor/swydo:ddp-apollo';
setupHttpEndpoint({
schema,
...otherOptions,
});
// For subscription support (not required)
createGraphQLPublication({ schema });
```
#### Options
- `schema`: The GraphQL schema. Default `undefined`. Required when no `gateway` is provided.
- `gateway`: An [Apollo Gateway](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-gateway). Default `undefined`. Required when no `schema` is provided.
- `context`: A custom context. Either an object or a function returning an object. Optional.
- `path`: The name of the HTTP path. Default `/graphql`.
- `engine`: An Engine instance, in case you want monitoring on your HTTP endpoint. Optional.
- `authMiddleware`: Middleware to get a userId and set it on the request. Default `meteorAuthMiddleware`, using a login token.
- `jsonParser`: Custom JSON parser. Loads `body-parser` from your `node_modules` by default and uses `.json()`.
## Sponsor
Want to work with Meteor and GraphQL? [Join the team!](https://swy.do/jobs)
================================================
FILE: client.js
================================================
import { DDPLink } from '@swydo/apollo-link-ddp';
export * from '@swydo/apollo-link-ddp';
// It is common for Apollo Links to export the link itself as the default
export default DDPLink;
================================================
FILE: docs/MIGRATION_GUIDE_1_0.md
================================================
# Migration guide to 1.0
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Installation changes](#installation-changes)
- [Client setup](#client-setup)
- [Previously](#previously)
- [Now](#now)
- [Server setup](#server-setup)
- [Previously](#previously-1)
- [Now](#now-1)
- [Subscriptions](#subscriptions)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation changes
You'll need the Apollo Link package and GrahpQL for Apollo Client 2 support:
```
meteor npm install --save apollo-link graphql
```
## Client setup
### Previously
```javascript
import ApolloClient from 'apollo-client';
import { DDPNetworkInterface } from 'meteor/swydo:ddp-apollo';
export const client = new ApolloClient ({
networkInterface: new DDPNetworkInterface()
});
```
### Now
```javascript
import ApolloClient from 'apollo-client';
import { DDPLink } from 'meteor/swydo:ddp-apollo';
// Apollo Clietn 2 requires a cache to be added as well
import { InMemoryCache } from 'apollo-cache-inmemory';
export const client = new ApolloClient ({
link: new DDPLink(),
cache: new InMemoryCache()
});
```
## Server setup
### Previously
```javascript
import { schema } from './path/to/your/executable/schema';
import { setup } from 'meteor/swydo:ddp-apollo';
setup(schema, {
...otherOptions
});
```
### Now
```javascript
import { schema } from './path/to/your/executable/schema';
import { setup } from 'meteor/swydo:ddp-apollo';
setup({
schema,
...otherOptions
});
```
## Subscriptions
`SubscriptionsManager` from `graphql-subscriptions` has been deprecated. Support for Subscriptions is now build into this package. All you need to do is setup the PubSub mechanism:
```javascript
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
export const resolvers = {
Query: {
name: () => 'bar',
},
Subscription: {
message: {
subscribe: () => pubsub.asyncIterator('SOMETHING_CHANGED'),
},
},
};
// Later you can publish updates like this:
pubsub.publish('SOMETHING_CHANGED', { message: 'hello world' });
```
================================================
FILE: package.js
================================================
/* eslint-disable no-var, prefer-arrow-callback */
var packages = [
'ecmascript',
'promise',
'webapp',
'random',
];
Package.describe({
name: 'swydo:ddp-apollo',
version: '4.0.2',
summary: 'DDP link and server for Apollo',
git: 'https://github.com/swydo/ddp-apollo',
documentation: 'README.md',
});
Package.onUse(function use(api) {
api.versionsFrom('1.3.2.4');
api.use(packages);
api.mainModule('client.js', 'client', { lazy: true });
api.mainModule('server.js', 'server');
});
Package.onTest(function test(api) {
api.use(packages);
api.use([
'meteortesting:mocha',
'accounts-base',
]);
api.mainModule('specs/client.js', 'client');
api.mainModule('specs/server.js', 'server');
});
================================================
FILE: package.json
================================================
{
"name": "ddp-apollo",
"private": true,
"version": "1.0.0",
"description": "DDP integration for the Apollo Client.",
"main": "lib/setup.js",
"devDependencies": {
"@apollo/client": "3.5.10",
"@apollo/federation": "0.36.1",
"@apollo/gateway": "2.0.1",
"@swydo/apollo-link-ddp": "file:packages/apollo-link-ddp",
"asteroid": "2.0.3",
"body-parser": "1.20.0",
"chai": "4.3.6",
"doctoc": "2.1.0",
"eslint": "8.13.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-import-resolver-meteor": "0.4.0",
"eslint-plugin-import": "2.26.0",
"graphql": "16.3.0",
"graphql-subscriptions": "2.0.0",
"graphql-tag": "2.12.6",
"graphql-tools": "8.2.8",
"react": "18.0.0",
"simpleddp": "2.2.4",
"sinon": "13.0.2"
},
"scripts": {
"doctoc": "doctoc ./README.md",
"eslint": "eslint ./src ./specs",
"publish": "meteor npm install && meteor npm run eslint -s && meteor npm run doctoc -s && rm -rf ./node_modules && rm -rf ./package-lock.json && rm -rf ./test && meteor publish && cd ./packages/apollo-link-ddp && meteor npm publish",
"setup-test": "cd packages/apollo-link-ddp && npm install && npm build && cd ../../ && rm -rf ./test && meteor create --bare test && cd test && meteor npm i --save selenium-webdriver@3 chromedriver @babel/runtime@7.2.0",
"test": "meteor npm run setup-test && cd test && METEOR_PACKAGE_DIRS=../ TEST_BROWSER_DRIVER=chrome meteor test-packages --once --driver-package meteortesting:mocha ../",
"test-watch": "meteor npm run setup-test && cd test && TEST_WATCH=1 METEOR_PACKAGE_DIRS=../ TEST_BROWSER_DRIVER=chrome meteor test-packages --driver-package meteortesting:mocha ../"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Swydo/ddp-apollo.git"
},
"author": "Swydo",
"license": "MIT",
"bugs": {
"url": "https://github.com/Swydo/ddp-apollo/issues"
},
"homepage": "https://github.com/Swydo/ddp-apollo#readme"
}
================================================
FILE: packages/apollo-link-ddp/.babelrc
================================================
{
"presets": [
[
"env",
{
"targets": {
"browsers": [">0.25%, not op_mini all"]
}
}
]
],
"plugins": ["transform-object-rest-spread"],
"comments": false
}
================================================
FILE: packages/apollo-link-ddp/.gitignore
================================================
node_modules
dist
================================================
FILE: packages/apollo-link-ddp/.npmignore
================================================
node_modules
================================================
FILE: packages/apollo-link-ddp/.npmrc
================================================
save-exact=true
package-lock=false
================================================
FILE: packages/apollo-link-ddp/LICENSE
================================================
MIT License
Copyright (c) 2016-present Swydo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: packages/apollo-link-ddp/README.md
================================================
# Apollo Link using DDP
This is the client part of the DDP setup for Apollo. It works out of the box in a Meteor environment but is also usable with packages like `asteroid`.
## Installation
```
meteor npm install --save apollo-link-ddp apollo-link graphql
```
## Setup
This packages gives you a `DDPLink` for your Apollo Client. Creating an Apollo Client is the same as with any other Apollo Link.
```javascript
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { DDPLink } from '@swydo/apollo-link-ddp';
export const client = new ApolloClient ({
link: new DDPLink(),
cache: new InMemoryCache()
});
```
### Options
- `connection`: The DDP connection to use. Default `Meteor.connection`.
- `method`: The name of the method. Default `__graphql`.
- `publication`: The name of the publication. Default `__graphql-subscriptions`.
- `ddpRetry`: Retry failed DDP method calls. Default `true`. Switch off and use [apollo-link-retry](https://www.npmjs.com/package/apollo-link-retry) for more control.
- `socket`: Optionally pass a socket to listen to for messages. This makes it easy to integrate with non-Meteor DDP clients.
```javascript
// Pass options to the DDPLink constructor
new DDPLink({
connection: Meteor.connection
});
```
## Setup with [Asteroid](https://github.com/mondora/asteroid)
```javascript
const Asteroid = createClass();
const asteroid = new Asteroid({
endpoint: 'ws://localhost:3000/websocket',
});
const link = new DDPLink({
connection: asteroid,
socket: asteroid.ddp.socket,
subscriptionIdKey: 'id',
});
```
## Setup with [SimpleDDP](https://github.com/Gregivy/simpleddp)
DDP-Apollo works with SimpleDDP version 2 and up.
```javascript
const SimpleDDP = require('simpleddp');
const connection = new SimpleDDP({
endpoint: 'ws://localhost:3000/websocket',
SocketConstructor: global.WebSocket,
});
this.link = new DDPLink({
connection: connection,
socket: connection.ddpConnection.socket,
});
```
================================================
FILE: packages/apollo-link-ddp/package.json
================================================
{
"name": "@swydo/apollo-link-ddp",
"version": "4.0.2",
"description": "Apollo Link using DDP",
"main": "src/index.js",
"browser": "dist/index.js",
"devDependencies": {
"babel-cli": "6.26.0",
"babel-plugin-transform-object-rest-spread": "6.26.0",
"babel-preset-env": "1.7.0"
},
"peerDependencies": {
"@apollo/client": "^3.0.0"
},
"scripts": {
"build": "babel src --out-dir dist",
"prepublish": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Swydo/ddp-apollo.git"
},
"keywords": [
"apollo",
"graphql",
"link",
"ddp"
],
"author": "Swydo",
"license": "MIT",
"bugs": {
"url": "https://github.com/Swydo/ddp-apollo/issues"
},
"homepage": "https://github.com/Swydo/ddp-apollo#readme"
}
================================================
FILE: packages/apollo-link-ddp/src/client/apollo-link-ddp.js
================================================
const { ApolloLink, Observable, split } = require('@apollo/client/core');
const isSubscription = require('../common/isSubscription');
const {
DEFAULT_METHOD,
DEFAULT_PUBLICATION,
DEFAULT_CLIENT_CONTEXT_KEY,
DEFAULT_SUBSCRIPTION_ID_KEY,
} = require('../common/defaults');
const {
createClientStreamObserver,
createSocketObserver,
filterGraphQLMessages,
} = require('./listenToGraphQLMessages');
function getDefaultMeteorConnection() {
try {
// eslint-disable-next-line global-require
const { Meteor } = require('meteor/meteor');
return Meteor.connection;
} catch (err) {
throw new Error('ddp-apollo: missing connection param');
}
}
function getClientContext(operation, key = DEFAULT_CLIENT_CONTEXT_KEY) {
return operation.getContext && operation.getContext()[key];
}
function callPromise(connection, name, args, options) {
return new Promise((resolve, reject) => {
const promise = connection.apply(name, args, options, (err, data) => {
err ? reject(err) : resolve(data);
});
if (promise && promise.then) { resolve(promise); }
});
}
class DDPMethodLink extends ApolloLink {
constructor({
connection = getDefaultMeteorConnection(),
method = DEFAULT_METHOD,
ddpRetry = true,
clientContextKey,
} = {}) {
super();
this.connection = connection;
this.method = method;
this.clientContextKey = clientContextKey;
this.ddpRetry = ddpRetry;
}
request(operation = {}) {
const clientContext = getClientContext(operation, this.clientContextKey);
const args = [operation, clientContext];
const options = { noRetry: !this.ddpRetry };
return new Observable((observer) => {
callPromise(this.connection, this.method, args, options)
.then(result => observer.next(result))
.catch(err => observer.error(err))
.finally(() => observer.complete());
return () => {};
});
}
}
class DDPSubscriptionLink extends ApolloLink {
constructor({
connection = getDefaultMeteorConnection(),
publication = DEFAULT_PUBLICATION,
subscriptionIdKey = DEFAULT_SUBSCRIPTION_ID_KEY,
clientContextKey,
socket,
} = {}) {
super();
this.connection = connection;
this.publication = publication;
this.clientContextKey = clientContextKey;
this.subscriptionIdKey = subscriptionIdKey;
this.subscriptionObservers = new Map();
this.ddpObserver = socket
? createSocketObserver(socket)
: createClientStreamObserver(this.connection._stream);
this.ddpSubscription = this.ddpObserver
.subscribe({
next: filterGraphQLMessages(({
subscriptionId,
result,
}) => {
const observer = this.subscriptionObservers.get(subscriptionId);
if (observer) {
observer.next(result);
}
}),
});
}
request(operation = {}) {
const clientContext = getClientContext(operation, this.clientContextKey);
const subHandler = this.connection.subscribe(this.publication, operation, clientContext);
const subId = subHandler[this.subscriptionIdKey];
return new Observable((observer) => {
this.subscriptionObservers.set(subId, observer);
return () => {
if (subHandler.stop) {
subHandler.stop();
} else if (this.connection.unsubscribe) {
this.connection.unsubscribe(subId);
} else {
console.warn(`ddp-apollo: could not unsubscribe from subscription with ID ${subId}`);
}
this.subscriptionObservers.delete(subId);
};
});
}
}
/*
* DDPLink combines the functionality from the method link and the subscription link
* providing support for queries, mutations and subscriptions.
*/
class DDPLink extends ApolloLink {
constructor(options) {
super();
this.methodLink = new DDPMethodLink(options);
this.subscriptionLink = new DDPSubscriptionLink(options);
}
request(operation = {}) {
return split(
isSubscription,
this.subscriptionLink,
this.methodLink,
).request(operation);
}
}
function getDDPLink(options) {
return new DDPLink(options);
}
module.exports = {
getDDPLink,
DDPLink,
DDPMethodLink,
DDPSubscriptionLink,
};
================================================
FILE: packages/apollo-link-ddp/src/client/apollo-link-meteor-auth.js
================================================
const { ApolloLink } = require('@apollo/client/core');
const getLoginToken = require('./getLoginToken');
const meteorAuthLink = new ApolloLink((operation, forward) => {
operation.setContext(() => ({
headers: {
Authorization: `Bearer ${getLoginToken() || ''}`,
},
}));
return forward(operation);
});
module.exports = meteorAuthLink;
================================================
FILE: packages/apollo-link-ddp/src/client/apollo-link-meteor.js
================================================
const { ApolloLink, HttpLink, split } = require('@apollo/client/core');
const isSubscription = require('../common/isSubscription');
const { DDPSubscriptionLink } = require('./apollo-link-ddp');
const { DEFAULT_PATH } = require('../common/defaults');
const meteorAuthLink = require('./apollo-link-meteor-auth');
class MeteorLink extends ApolloLink {
constructor(options = {}) {
super();
const {
uri = Meteor.absoluteUrl(DEFAULT_PATH),
httpLink,
authLink = meteorAuthLink,
} = options;
this.meteorHttpLink = authLink.concat(httpLink || new HttpLink({ uri }));
this.subscriptionLink = new DDPSubscriptionLink(options);
}
request(operation = {}) {
return split(
isSubscription,
this.subscriptionLink,
this.meteorHttpLink,
).request(operation);
}
}
module.exports = MeteorLink;
================================================
FILE: packages/apollo-link-ddp/src/client/getLoginToken.js
================================================
/* global localStorage */
const LOGIN_TOKEN_KEY = 'Meteor.loginToken';
function getLoginToken() {
if (typeof localStorage !== 'undefined') {
return localStorage.getItem(LOGIN_TOKEN_KEY);
}
return undefined;
}
module.exports = getLoginToken;
================================================
FILE: packages/apollo-link-ddp/src/client/graphQLFetcher.js
================================================
/* global window, fetch */
const { DEFAULT_PATH } = require('../common/defaults');
const getLoginToken = require('./getLoginToken');
/*
* Create a graphQL fetcher for graphiQL
*/
function createGraphQLFetcher({
path = DEFAULT_PATH,
} = {}) {
return function graphQLFetcher(graphQLParams) {
const headers = {
Authorization: `Bearer ${getLoginToken() || ''}`,
'Content-Type': 'application/json',
};
return fetch(`${window.location.origin}/${path}`, {
method: 'post',
headers,
body: JSON.stringify(graphQLParams),
}).then(response => response.json());
};
}
module.exports = createGraphQLFetcher;
================================================
FILE: packages/apollo-link-ddp/src/client/listenToGraphQLMessages.js
================================================
const { Observable } = require('@apollo/client/core');
const { GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE } = require('../common/defaults');
function filterGraphQLMessages(callback) {
return (message) => {
const data = typeof message === 'string'
? JSON.parse(message)
: message;
const {
type,
subId: subscriptionId,
graphqlData: result,
} = data;
if (
type === GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE
&& subscriptionId
&& result
) {
callback({
subscriptionId,
result,
});
}
};
}
function createClientStreamObserver(stream) {
return new Observable((observer) => {
const event = 'message';
const callback = message => observer.next(message);
if (stream) {
stream.on(event, callback);
}
return () => {
if (stream && stream.eventCallbacks && stream.eventCallbacks[event]) {
const index = stream.eventCallbacks[event].indexOf(callback);
if (index > -1) {
stream.eventCallbacks[event].splice(index, 1);
}
}
};
});
}
function createSocketObserver(socket) {
return new Observable((observer) => {
const event = 'message:in';
const listener = message => observer.next(message);
socket.on(event, listener);
return () => socket.off(event, listener);
});
}
module.exports = {
createClientStreamObserver,
createSocketObserver,
filterGraphQLMessages,
};
================================================
FILE: packages/apollo-link-ddp/src/common/defaults.js
================================================
const DEFAULT_METHOD = '__graphql';
const DEFAULT_PUBLICATION = '__graphql-subscriptions';
const DEFAULT_SUBSCRIPTION_ID_KEY = 'subscriptionId';
const DEFAULT_CLIENT_CONTEXT_KEY = 'ddpContext';
const GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE = 'graphql-sub-message';
const DEFAULT_PATH = '/graphql';
const DEFAULT_CREATE_CONTEXT = context => context;
const defaults = {
DEFAULT_CLIENT_CONTEXT_KEY,
DEFAULT_CREATE_CONTEXT,
DEFAULT_METHOD,
DEFAULT_PATH,
DEFAULT_PUBLICATION,
DEFAULT_SUBSCRIPTION_ID_KEY,
GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE,
};
module.exports = defaults;
================================================
FILE: packages/apollo-link-ddp/src/common/isSubscription.js
================================================
const isSubscriptionDefinition = ({ kind, operation }) => kind === 'OperationDefinition' && operation === 'subscription';
const isSubscription = ({ query }) => query
&& query.definitions
&& query.definitions.some(isSubscriptionDefinition);
module.exports = isSubscription;
================================================
FILE: packages/apollo-link-ddp/src/index.js
================================================
const defaults = require('./common/defaults');
const isSubscription = require('./common/isSubscription');
const {
getDDPLink,
DDPLink,
DDPMethodLink,
DDPSubscriptionLink,
} = require('./client/apollo-link-ddp');
const MeteorLink = require('./client/apollo-link-meteor');
const meteorAuthLink = require('./client/apollo-link-meteor-auth');
const createGraphQLFetcher = require('./client/graphQLFetcher');
module.exports = {
...defaults,
isSubscription,
getDDPLink,
DDPLink,
DDPMethodLink,
DDPSubscriptionLink,
MeteorLink,
meteorAuthLink,
createGraphQLFetcher,
};
================================================
FILE: server.js
================================================
export * from './src/setup';
================================================
FILE: specs/client/apollo-client.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import gql from 'graphql-tag';
import { Promise } from 'meteor/promise';
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { getDDPLink } from '@swydo/apollo-link-ddp';
import { FOO_CHANGED_TOPIC } from '../data/resolvers';
describe('ApolloClient with DDP link', function () {
beforeEach(function () {
// The ApolloClient won't recognize Promise in package tests unless exported like this
global.Promise = Promise;
this.link = getDDPLink();
this.client = new ApolloClient({
link: this.link,
cache: new InMemoryCache(),
});
});
afterEach(function () {
this.link.subscriptionLink.ddpSubscription.unsubscribe();
});
describe('#query', function () {
it('returns query data', async function () {
const { data } = await this.client.query({ query: gql`query { foo }` });
chai.expect(data.foo).to.be.a('string');
});
it('returns mutation data', async function () {
const { data } = await this.client.mutate({ mutation: gql`mutation { foo }` });
chai.expect(data.foo).to.be.a('string');
});
it('should pass and retrieve a ddp context', async function () {
const { data } = await this.client.query({
query: gql`query { ddpContextValue }`,
context: { ddpContext: 'ddpFoo' },
});
chai.expect(data.ddpContextValue).to.equal('ddpFoo');
});
it('handles errors', async function () {
try {
await this.client.query({
query: gql`query { somethingBad }`,
});
chai.expect(false, 'this should not happen').to.equal(true);
} catch (err) {
chai.expect(err.message).to.equal('SOMETHING_BAD');
chai.expect(err.graphQLErrors[0].message).to.equal('SOMETHING_BAD');
}
});
});
describe('#subscribe', function () {
it('returns subscription data', function (done) {
const message = { fooSub: 'bar' };
const observer = this.client.subscribe({ query: gql`subscription { fooSub }` });
const subscription = observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data).to.deep.equal(message);
subscription.unsubscribe();
done();
} catch (e) {
done(e);
}
},
});
this.link.subscriptionLink.connection.call('ddp-apollo/publish', FOO_CHANGED_TOPIC, message);
});
});
});
================================================
FILE: specs/client/apollo-link-ddp.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import gql from 'graphql-tag';
import { ApolloLink, Observable } from '@apollo/client';
import {
DEFAULT_METHOD,
DEFAULT_PUBLICATION,
getDDPLink,
DDPMethodLink,
DDPSubscriptionLink,
} from '@swydo/apollo-link-ddp';
import { loginWithUserId } from './helpers/login';
import { callPromise } from './helpers/callPromise';
import { FOO_CHANGED_TOPIC } from '../data/resolvers';
describe('DDPMethodLink', function () {
beforeEach(function (done) {
this.link = new DDPMethodLink();
Meteor.call('ddp-apollo/setup', done);
});
it('should add a default method', function () {
chai.expect(this.link.method).to.equal(DEFAULT_METHOD);
});
describe('#request', function () {
it('should return an observer', function () {
const operation = {
query: gql`query { foo }`,
};
chai.expect(this.link.request(operation)).to.be.instanceof(Observable);
});
it('returns data', function (done) {
const operation = {
query: gql`query { foo }`,
};
const observer = this.link.request(operation);
observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data.foo).to.be.a('string');
done();
} catch (e) {
done(e);
}
},
error: done,
});
});
});
describe('when authenticated', function () {
before(async function () {
const userId = await callPromise('createTestUser');
chai.expect(userId).to.be.a('string');
this.userId = userId;
await loginWithUserId(userId);
});
after(function (done) {
Meteor.logout((err) => { err ? done(err) : done(); });
});
it('returns the userId', function (done) {
const operation = {
query: gql`query { userId }`,
};
const observer = this.link.request(operation);
observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data.userId).to.be.a('string');
chai.expect(data.userId).to.equal(this.userId);
done();
} catch (e) {
done(e);
}
},
error: done,
});
});
it('returns the meteorUserId', function (done) {
const operation = {
query: gql`query { meteorUserId }`,
};
const observer = this.link.request(operation);
observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data.meteorUserId).to.be.a('string');
chai.expect(data.meteorUserId).to.equal(this.userId);
done();
} catch (e) {
done(e);
}
},
error: done,
});
});
it('returns the isDDP flag', function (done) {
const operation = {
query: gql`query { isDDP }`,
};
const observer = this.link.request(operation);
observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data.isDDP).to.be.a('boolean');
chai.expect(data.isDDP).to.be.true;
done();
} catch (e) {
done(e);
}
},
error: done,
});
});
});
describe('when disconnected', function () {
beforeEach(function () {
Meteor.disconnect();
});
afterEach(function () {
// Reconnect in case the test fail before being able to do so
Meteor.reconnect();
});
it('retries automatically', function (done) {
const operation = {
query: gql`query { foo }`,
};
this.link.request(operation).subscribe({
next: ({ data }) => {
try {
chai.expect(data.foo).to.be.a('string');
done();
} catch (e) {
done(e);
}
},
error: done,
});
Meteor.reconnect();
});
it('can be configured to prevent retrying automatically', function (done) {
this.link = new DDPMethodLink({ ddpRetry: false });
const operation = {
query: gql`query { foo }`,
};
this.link.request(operation).subscribe({
error: (err) => {
try {
chai.expect(err.message).to.contain('noRetry');
done();
} catch (e) {
done(e);
}
},
});
Meteor.reconnect();
});
});
});
describe('DDPSubscriptionLink', function () {
beforeEach(function (done) {
this.link = new DDPSubscriptionLink();
Meteor.call('ddp-apollo/setup', done);
});
afterEach(function () {
this.link.ddpSubscription.unsubscribe();
});
it('should add a default publication', function () {
chai.expect(this.link.publication).to.equal(DEFAULT_PUBLICATION);
});
it('subscribes to DDP messages', function () {
chai.expect(this.link.ddpObserver).to.be.an('object');
chai.expect(this.link.ddpSubscription).to.be.an('object');
});
describe('#request', function () {
it('should return an id and data', function (done) {
const operation = {
query: gql`subscription { fooSub }`,
};
const message = { fooSub: 'bar' };
const observer = this.link.request(operation);
const subscription = observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data).to.deep.equal(message);
subscription.unsubscribe();
done();
} catch (e) {
done(e);
}
},
});
Meteor.call('ddp-apollo/publish', FOO_CHANGED_TOPIC, message);
});
it('should receive multiple updates', function (done) {
const loops = 5;
let count = 0;
const operation = {
query: gql`subscription { fooSub }`,
};
const value = 'bar';
const observer = this.link.request(operation);
const subscription = observer.subscribe({
next: () => ++count,
});
const promises = [];
for (let i = 0; i < loops; i += 1) {
promises.push(callPromise('ddp-apollo/publish', FOO_CHANGED_TOPIC, { fooSub: value }));
}
Promise.all(promises).then(() => {
Meteor.setTimeout(() => {
try {
chai.expect(count, 'number of next calls').to.equal(loops);
subscription.unsubscribe();
done();
} catch (e) {
done(e);
}
}, 100);
});
});
it('continues after a graphql error', function (done) {
const loops = 5;
let successCount = 0;
let errorCount = 0;
const operation = {
query: gql`subscription { fooSub }`,
};
const value = 'bar';
const observer = this.link.request(operation);
const subscription = observer.subscribe({
next: (data) => (data.errors ? ++errorCount : ++successCount),
});
const promises = [];
for (let i = 0; i < loops; i += 1) {
promises.push(callPromise('ddp-apollo/publish', FOO_CHANGED_TOPIC, { fooSub: i % 2 === 0 ? null : value }));
}
Promise.all(promises).then(() => {
Meteor.setTimeout(() => {
try {
chai.expect(successCount + errorCount, 'number of next calls').to.equal(loops);
chai.expect(successCount, 'number of succesfull loops').to.equal(2);
chai.expect(errorCount, 'number of errors').to.equal(loops - successCount);
subscription.unsubscribe();
done();
} catch (e) {
done(e);
}
}, 100);
});
});
});
});
describe('#getDDPLink', function () {
beforeEach(function () {
this.link = getDDPLink();
});
afterEach(function () {
this.link.subscriptionLink.ddpSubscription.unsubscribe();
});
it('should return an instance of ApolloLink', function () {
chai.expect(this.link).to.be.an.instanceOf(ApolloLink);
});
});
================================================
FILE: specs/client/apollo-link-meteor-auth.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import gql from 'graphql-tag';
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { DEFAULT_PATH, meteorAuthLink } from '@swydo/apollo-link-ddp';
describe('MeteorAuthLink', function () {
beforeEach(function () {
const httpLink = new HttpLink({ uri: Meteor.absoluteUrl(DEFAULT_PATH) });
const cache = new InMemoryCache();
this.client = new ApolloClient({
link: meteorAuthLink.concat(httpLink),
cache,
});
});
describe('#query', function () {
it('should return data from the server', async function () {
const operation = {
query: gql`query { foo }`,
};
const { data, loading } = await this.client.query(operation);
chai.expect(data).to.deep.equal({ foo: 'bar' });
chai.expect(loading).to.equal(false);
});
});
});
================================================
FILE: specs/client/apollo-link-meteor.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import gql from 'graphql-tag';
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { MeteorLink } from '@swydo/apollo-link-ddp';
import { loginWithUserId } from './helpers/login';
import { callPromise } from './helpers/callPromise';
describe('MeteorLink', function () {
beforeEach(function () {
this.link = new MeteorLink();
this.client = new ApolloClient({
link: this.link,
cache: new InMemoryCache(),
});
this.client.cache.reset();
});
afterEach(function () {
this.link.subscriptionLink.ddpSubscription.unsubscribe();
});
describe('#mutate', function () {
it('returns data from the server', async function () {
const operation = {
mutation: gql`mutation { foo }`,
};
const { data } = await this.client.mutate(operation);
chai.expect(data).to.deep.equal({ foo: 'fooMutated' });
});
});
describe('#query', function () {
it('should return data from the server', async function () {
const operation = {
query: gql`query { foo }`,
};
const { data, loading } = await this.client.query(operation);
chai.expect(data).to.deep.equal({ foo: 'bar' });
chai.expect(loading).to.equal(false);
});
describe('when authenticated', function () {
before(async function () {
const userId = await callPromise('createTestUser');
chai.expect(userId).to.be.a('string');
this.userId = userId;
await loginWithUserId(userId);
});
after(function (done) {
Meteor.logout((err) => { err ? done(err) : done(); });
});
it('returns the userId', async function () {
const operation = {
query: gql`query { userId }`,
};
const { data } = await this.client.query(operation);
chai.expect(data.userId).to.be.a('string');
chai.expect(data.userId).to.equal(this.userId);
});
it('returns the meteorUserId', async function () {
const operation = {
query: gql`query { meteorUserId }`,
};
const { data } = await this.client.query(operation);
chai.expect(data.meteorUserId).to.be.a('string');
chai.expect(data.meteorUserId).to.equal(this.userId);
});
});
});
});
================================================
FILE: specs/client/asteroid.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import gql from 'graphql-tag';
import { Promise } from 'meteor/promise';
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { getDDPLink } from '@swydo/apollo-link-ddp';
import { FOO_CHANGED_TOPIC } from '../data/resolvers';
describe('Using Asteroid', function () {
beforeEach(function () {
// The ApolloClient won't recognize Promise in package tests unless exported like this
global.Promise = Promise;
// eslint-disable-next-line global-require
const { createClass } = require('asteroid');
const Asteroid = createClass();
const asteroid = new Asteroid({
endpoint: 'ws://localhost:3000/websocket',
});
this.link = getDDPLink({
connection: asteroid,
socket: asteroid.ddp.socket,
subscriptionIdKey: 'id',
});
this.client = new ApolloClient({
link: this.link,
cache: new InMemoryCache(),
});
});
afterEach(function () {
this.link.subscriptionLink.ddpSubscription.unsubscribe();
});
describe('#query', function () {
it('returns query data', async function () {
const { data } = await this.client.query({ query: gql`query { foo }` });
chai.expect(data.foo).to.be.a('string');
});
});
describe('#subscribe', function () {
it('returns subscription data', function (done) {
const message = { fooSub: 'bar' };
const observer = this.client.subscribe({ query: gql`subscription { fooSub }` });
const subscription = observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data).to.deep.equal(message);
subscription.unsubscribe();
done();
} catch (e) {
done(e);
}
},
});
this.link.subscriptionLink.connection.call('ddp-apollo/publish', FOO_CHANGED_TOPIC, message);
});
});
});
================================================
FILE: specs/client/exports.js
================================================
/* eslint-disable prefer-arrow-callback, func-names, import/named */
/* eslint-env mocha */
import { expect } from 'chai';
import DDPLink, {
DDPLink as namedDDPLink,
MeteorLink,
meteorAuthLink,
createGraphQLFetcher,
} from '../../client';
describe('Client exports', function () {
it('exports the DDPLink', function () {
expect(DDPLink).to.be.a('function');
});
it('exports a named DDPLink', function () {
expect(namedDDPLink).to.be.a('function');
});
it('exports a MeteorLink', function () {
expect(MeteorLink).to.be.a('function');
});
it('exports a meteorAuthLink', function () {
expect(meteorAuthLink).to.be.an('object');
});
it('exports a createGraphQLFetcher', function () {
expect(createGraphQLFetcher).to.be.a('function');
});
});
================================================
FILE: specs/client/graphQLFetcher.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import { createGraphQLFetcher } from '@swydo/apollo-link-ddp';
import { callPromise } from './helpers/callPromise';
import { loginWithUserId } from './helpers/login';
describe('graphQLFetcher', function () {
beforeEach(function () {
this.fetcher = createGraphQLFetcher();
});
it('should return data from the server', async function () {
const operation = {
query: 'query { foo }',
};
const { data } = await this.fetcher(operation);
chai.expect(data).to.deep.equal({ foo: 'bar' });
});
describe('when authenticated', function () {
before(async function () {
const userId = await callPromise('createTestUser');
chai.expect(userId).to.be.a('string');
this.userId = userId;
await loginWithUserId(userId);
});
after(function (done) {
Meteor.logout((err) => { err ? done(err) : done(); });
});
it('returns the userId', async function () {
const operation = {
query: 'query { userId, meteorUserId }',
};
const { data } = await this.fetcher(operation);
chai.expect(data.userId).to.equal(this.userId);
chai.expect(data.meteorUserId).to.equal(this.userId);
});
});
});
================================================
FILE: specs/client/helpers/callPromise.js
================================================
export function callPromise(name, ...args) {
return new Promise((resolve, reject) => {
Meteor.apply(name, args, (err, data) => {
err ? reject(err) : resolve(data);
});
});
}
================================================
FILE: specs/client/helpers/login.js
================================================
import { Accounts } from 'meteor/accounts-base';
export function loginWithUserId(userId) {
return new Promise((resolve, reject) => {
Accounts.callLoginMethod({
methodArguments: [{ userId }],
userCallback: (err) => (err ? reject(err) : resolve()),
});
});
}
================================================
FILE: specs/client/simpleddp.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import gql from 'graphql-tag';
import { Promise } from 'meteor/promise';
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { getDDPLink } from '@swydo/apollo-link-ddp';
import { FOO_CHANGED_TOPIC } from '../data/resolvers';
describe('Using SimpleDDP', function () {
beforeEach(function () {
// The ApolloClient won't recognize Promise in package tests unless exported like this
global.Promise = Promise;
// eslint-disable-next-line global-require
const SimpleDDP = require('simpleddp');
const connection = new SimpleDDP({
endpoint: 'ws://localhost:3000/websocket',
SocketConstructor: global.WebSocket,
});
this.link = getDDPLink({
connection,
socket: connection.ddpConnection.socket,
});
this.client = new ApolloClient({
link: this.link,
cache: new InMemoryCache(),
});
});
afterEach(function () {
this.link.subscriptionLink.ddpSubscription.unsubscribe();
});
describe('#query', function () {
it('returns query data', async function () {
const { data } = await this.client.query({ query: gql`query { foo }` });
chai.expect(data.foo).to.be.a('string');
});
});
describe('#subscribe', function () {
it('returns subscription data', function (done) {
const message = { fooSub: 'bar' };
const observer = this.client.subscribe({ query: gql`subscription { fooSub }` });
const subscription = observer.subscribe({
next: ({ data }) => {
try {
chai.expect(data).to.deep.equal(message);
subscription.unsubscribe();
done();
} catch (e) {
done(e);
}
},
});
this.link.subscriptionLink.connection.call('ddp-apollo/publish', FOO_CHANGED_TOPIC, message);
});
});
});
================================================
FILE: specs/client.js
================================================
import './client/exports';
import './client/apollo-link-ddp';
import './client/apollo-client';
import './client/apollo-link-meteor-auth';
import './client/apollo-link-meteor';
import './client/graphQLFetcher';
import './client/asteroid';
import './client/simpleddp';
================================================
FILE: specs/data/pubsub.js
================================================
import { PubSub } from 'graphql-subscriptions';
export const pubsub = new PubSub();
================================================
FILE: specs/data/resolvers.js
================================================
import { pubsub } from './pubsub';
export const FOO_CHANGED_TOPIC = 'foo_changed';
export const resolvers = {
Query: {
foo: () => 'bar',
userId: (_, __, { userId } = {}) => userId,
// Using Meteor.userId() yourself is not recommended. Use the context userId.
// But to support a lot of Meteor packages it's useful, because they use it underwater.
// See https://github.com/apollographql/meteor-integration/issues/92
meteorUserId: () => Meteor.userId(),
ddpContextValue: (_, __, { ddpContext } = {}) => ddpContext,
contextToString: (_, __, context = {}) => JSON.stringify(context),
isDDP: (_, __, { ddpConnection } = {}) => Boolean(ddpConnection),
somethingBad: () => { throw new Error('SOMETHING_BAD'); },
},
Mutation: {
foo: () => 'fooMutated',
},
Subscription: {
fooSub: {
subscribe: () => pubsub.asyncIterator(FOO_CHANGED_TOPIC),
},
},
};
================================================
FILE: specs/data/typeDefs.js
================================================
import gql from 'graphql-tag';
export const typeDefs = gql`
type Query {
foo: String
userId: String
isDDP: Boolean
meteorUserId: String
ddpContextValue: String
contextToString: String
somethingBad: String
}
type Mutation {
foo: String
}
type Subscription {
fooSub: String!
}
`;
================================================
FILE: specs/server/exports.js
================================================
/* eslint-disable prefer-arrow-callback, func-names, import/named */
/* eslint-env mocha */
import { expect } from 'chai';
import {
setup,
createGraphQLPublication,
setupHttpEndpoint,
} from '../../server';
describe('Server exports', function () {
it('exports the setup function', function () {
expect(setup).to.be.a('function');
});
it('exports the createGraphQLPublication function', function () {
expect(createGraphQLPublication).to.be.a('function');
});
it('exports the setupHttpEndpoint function', function () {
expect(setupHttpEndpoint).to.be.a('function');
});
});
================================================
FILE: specs/server/getUserIdByLoginToken.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import { getUserIdByLoginToken, NO_VALID_USER_ERROR } from '../../src/getUserIdByLoginToken';
describe('getUserIdByLoginToken', function () {
it('throws error for bad tokens', async function () {
try {
await getUserIdByLoginToken('foo');
chai.expect(false, 'this should not be touched').to.equal(true);
} catch (err) {
chai.expect(err).to.equal(NO_VALID_USER_ERROR);
}
});
});
================================================
FILE: specs/server/helpers/setupLoginByUserId.js
================================================
import { Accounts } from 'meteor/accounts-base';
Meteor.methods({
createTestUser() {
return Meteor.users.insert({});
},
});
Accounts.registerLoginHandler('testLogin', (request) => {
if (!(typeof request.userId === 'string')) {
return undefined;
}
const user = Meteor.users.findOne(request.userId);
if (!user) {
return { error: new Meteor.Error('USER_NOT_FOUND') };
}
return { userId: user._id };
});
================================================
FILE: specs/server/helpers.js
================================================
import { makeExecutableSchema } from 'graphql-tools';
import {
DEFAULT_METHOD,
DEFAULT_PUBLICATION,
} from '@swydo/apollo-link-ddp';
import { pubsub } from '../data/pubsub';
import { setup, setupHttpEndpoint } from '../../src/setup';
import { typeDefs } from '../data/typeDefs';
import { resolvers } from '../data/resolvers';
export function reset() {
delete Meteor.server.publish_handlers[DEFAULT_PUBLICATION];
delete Meteor.server.method_handlers[DEFAULT_METHOD];
}
Meteor.methods({
'ddp-apollo/setup': async function setupDdpApollo() {
reset();
const schema = makeExecutableSchema({
resolvers,
typeDefs,
});
// Add the client context to the previous context for testing
const context = (previousContext, clientContext) => ({
...previousContext,
ddpContext: clientContext,
});
await setup({
schema,
context,
});
await setupHttpEndpoint({
schema,
context,
});
},
'ddp-apollo/publish': function publish(topic, data) {
pubsub.publish(topic, data);
},
});
================================================
FILE: specs/server/server.js
================================================
import '../../server';
================================================
FILE: specs/server/setup.js
================================================
/* eslint-disable prefer-arrow-callback, func-names */
/* eslint-env mocha */
import chai from 'chai';
import { makeExecutableSchema } from 'graphql-tools';
import gql from 'graphql-tag';
import { ApolloGateway, LocalGraphQLDataSource } from '@apollo/gateway';
import { buildFederatedSchema } from '@apollo/federation';
import { DEFAULT_METHOD } from '@swydo/apollo-link-ddp';
import { setup } from '../../src/setup';
import { DDP_APOLLO_SCHEMA_REQUIRED } from '../../src/initSchema';
import { createGraphQLMethod } from '../../src/createGraphQLMethod';
import { typeDefs } from '../data/typeDefs';
import { resolvers } from '../data/resolvers';
import { reset } from './helpers';
import { callPromise } from '../client/helpers/callPromise';
async function callMethod(...args) {
return callPromise.apply(this, [DEFAULT_METHOD, ...args]);
}
describe('#setup', function () {
beforeEach(function () {
reset();
});
it('requires a schema', async function () {
try {
await setup();
throw new Error('Setup without schema should fail!');
} catch (e) {
chai.expect(e.message).to.equal(DDP_APOLLO_SCHEMA_REQUIRED);
}
});
describe('method', function () {
beforeEach(async function () {
const schema = makeExecutableSchema({
resolvers,
typeDefs,
});
await setup({ schema });
});
it('should add a method', function (done) {
Meteor.call(DEFAULT_METHOD, done);
});
it('should return data', async function () {
const request = {
query: gql`{ foo }`,
};
const { data } = await callMethod(request);
chai.expect(data.foo).to.equal('bar');
});
});
describe('context', function () {
it('accepts an object', async function () {
const schema = makeExecutableSchema({
resolvers: {
Query: {
foo: (_, __, { foo, bar }) => [foo, bar].join(':'),
},
},
typeDefs,
});
const context = {
foo: 'baz',
bar: 'qux',
};
const request = {
query: gql`{ foo }`,
};
await setup({ schema, context });
const { data } = await callMethod(request);
chai.expect(data.foo).to.equal('baz:qux');
});
it('accepts a function', async function () {
const schema = makeExecutableSchema({
resolvers: {
Query: {
foo: (_, __, { foo, bar }) => [foo, bar].join(':'),
},
},
typeDefs,
});
const context = () => ({
foo: 'baz',
bar: 'qux',
});
const request = {
query: gql`{ foo }`,
};
await setup({ schema, context });
const { data } = await callMethod(request);
chai.expect(data.foo).to.equal('baz:qux');
});
it('accepts an async function', async function () {
const schema = makeExecutableSchema({
resolvers: {
Query: {
foo: (_, __, { foo, bar }) => [foo, bar].join(':'),
},
},
typeDefs,
});
const getQux = async () => 'qux';
const context = async () => ({
foo: 'baz',
bar: await getQux(),
});
const request = {
query: gql`{ foo }`,
};
await setup({ schema, context });
const { data } = await callMethod(request);
chai.expect(data.foo).to.equal('baz:qux');
});
it('leaves the original values alone', async function (done) {
const schema = makeExecutableSchema({
resolvers: {
Query: {
foo: (_, __, context) => {
chai.expect(Object.getOwnPropertyNames(context)).to.include('userId');
chai.expect(Object.getOwnPropertyNames(context)).to.include('foo');
done();
},
},
},
typeDefs,
});
const context = { foo: 'baz' };
const request = { query: gql`{ foo }` };
await setup({ schema, context });
await callMethod(request);
});
});
describe('createContext', function () {
it('is called with the current context', function (done) {
const request = { query: gql`{ foo }` };
const schema = makeExecutableSchema({
resolvers,
typeDefs,
});
function context(currentContext) {
chai.expect(Object.getOwnPropertyNames(currentContext)).to.include('userId');
done();
}
createGraphQLMethod({ schema, context })(request).catch(done);
});
it('returns a modified context', async function () {
const request = { query: gql`{ foo }` };
const schema = makeExecutableSchema({
resolvers: {
Query: {
foo: (_, __, { foo, bar }) => [foo, bar].join(':'),
},
},
typeDefs,
});
const context = () => ({ foo: 'baz', bar: 'qux' });
const { data } = await createGraphQLMethod({ schema, context })(request);
chai.expect(data.foo).to.equal('baz:qux');
});
it('accepts a ddp context param', async function () {
const request = { query: gql`{ foo }` };
const schema = makeExecutableSchema({
resolvers: {
Query: { foo: (_, __, { foo }) => foo },
},
typeDefs,
});
const context = (_, clientContext) => clientContext;
const { data } = await createGraphQLMethod({ schema, context })(request, { foo: 'bar' });
chai.expect(data.foo).to.equal('bar');
});
});
describe('gateway', function () {
beforeEach(async function () {
const {
Subscription,
...resolversWithoutSubscriptions
} = resolvers;
const typeDefsWithoutSubscriptions = {
...typeDefs,
definitions: typeDefs.definitions.filter((def) => def.name.value !== 'Subscription'),
};
const schema = buildFederatedSchema([{
resolvers: resolversWithoutSubscriptions,
typeDefs: typeDefsWithoutSubscriptions,
}]);
const gateway = new ApolloGateway({
serviceList: [{ name: 'local', url: 'foo' }],
buildService: () => new LocalGraphQLDataSource(schema),
});
await setup({ gateway });
});
it('returns data via method', async function () {
const request = {
query: gql`{ foo }`,
};
const { data } = await callMethod(request);
chai.expect(data.foo).to.equal('bar');
});
it('supports context with userId', async function () {
const request = {
query: gql`{ contextToString }`,
};
const { data } = await callMethod(request);
chai.expect(data.contextToString).to.be.ok;
chai.expect(JSON.parse(data.contextToString)).to.have.property('userId');
});
});
});
================================================
FILE: specs/server.js
================================================
// helpers
import './server/helpers';
import './server/helpers/setupLoginByUserId';
// specs
import './server/exports';
import './server/server';
import './server/setup';
import './server/getUserIdByLoginToken';
================================================
FILE: src/contextToFunction.js
================================================
import { DEFAULT_CREATE_CONTEXT } from '@swydo/apollo-link-ddp';
export function contextToFunction(context) {
switch (typeof context) {
case 'object':
return (defaultContext) => ({ ...defaultContext, ...context });
case 'function':
return context;
default:
return DEFAULT_CREATE_CONTEXT;
}
}
================================================
FILE: src/createExecutor.js
================================================
import { execute } from 'graphql';
export function createExecutor(gatewayExecutor) {
return function executor({
schema,
query,
context,
operationName,
variables,
}) {
if (gatewayExecutor) {
return gatewayExecutor({
document: query,
operationName,
context,
request: {
query,
operationName,
variables,
},
});
}
return execute({
schema,
document: query,
rootValue: {},
contextValue: context,
variableValues: variables,
operationName,
});
};
}
================================================
FILE: src/createGraphQLMethod.js
================================================
import { createExecutor } from './createExecutor';
import { contextToFunction } from './contextToFunction';
export const DDP_APOLLO_SCHEMA_REQUIRED = 'DDP_APOLLO_SCHEMA_REQUIRED';
export function createGraphQLMethod({
schema,
context,
execute = createExecutor(),
}) {
if (!schema) {
throw new Error(DDP_APOLLO_SCHEMA_REQUIRED);
}
const createContext = contextToFunction(context);
// eslint-disable-next-line default-param-last
return async function graphQlMethod({ query, variables, operationName } = {}, clientContext) {
if (!query) {
return {};
}
if (this.unblock) {
this.unblock();
}
const { userId, connection: ddpConnection } = this;
const completeContext = await createContext({ userId, ddpConnection }, clientContext);
return execute({
schema,
query,
context: completeContext,
variables,
operationName,
});
};
}
================================================
FILE: src/createGraphQLMiddleware.js
================================================
import { parse } from 'graphql';
import { createExecutor } from './createExecutor';
import { contextToFunction } from './contextToFunction';
import { invokeDDP } from './invokeDDP';
export function createGraphQLMiddleware({
schema,
context,
execute = createExecutor(),
}) {
const createContext = contextToFunction(context);
return async function handleRequest(req, res) {
const {
query,
variables,
operationName,
} = req.body;
const data = await invokeDDP(async () => execute({
schema,
query: parse(query),
context: await createContext({ userId: req.userId }),
operationName,
variables,
}), { userId: req.userId });
const json = JSON.stringify(data);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Length', Buffer.byteLength(json, 'utf8'));
res.write(json);
res.end();
};
}
================================================
FILE: src/createGraphQLPublication.js
================================================
import { Meteor } from 'meteor/meteor';
import {
DEFAULT_PUBLICATION,
GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE,
} from '@swydo/apollo-link-ddp';
import { subscribe } from 'graphql';
import forAwaitEach from './forAwaitEach';
import { contextToFunction } from './contextToFunction';
// The 'pong' message is the only messages that is ignored by the client-side DDP parser
const SUBSCRIPTION_MESSAGE_TYPE = 'pong';
const { warn } = console;
export function createGraphQLPublication({
schema,
context,
publication = DEFAULT_PUBLICATION,
} = {}) {
if (!subscribe) {
warn('DDP-Apollo: You need graphl@0.11 or higher for subscription support');
return;
}
const createContext = contextToFunction(context);
// eslint-disable-next-line default-param-last
Meteor.publish(publication, function publishGraphQL({
query,
variables,
operationName,
} = {}, clientContext) {
const {
userId,
_subscriptionId: subId,
_session: session,
connection: ddpConnection,
} = this;
if (!query) {
this.stop();
return;
}
Promise.resolve()
.then(() => createContext({ userId, ddpConnection }, clientContext))
.then((completeContext) => subscribe({
schema,
document: query,
rootValue: {},
contextValue: completeContext,
variableValues: variables,
operationName,
}))
.then((iterator) => {
this.ready();
// When the Meteor subscriptions stops we should break out of the iterator
this.onStop(() => iterator.return && iterator.return());
return forAwaitEach(iterator, (graphqlData) => {
session.socket.send(JSON.stringify({
msg: SUBSCRIPTION_MESSAGE_TYPE,
type: GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE,
subId,
graphqlData,
}));
});
})
// When the GraphQL subscriptions stops we should stop the Meteor subscription
.then(() => this.stop())
.catch((err) => this.error(err));
});
}
================================================
FILE: src/forAwaitEach.js
================================================
function createAsyncIterator(source) {
const method = source && (source[Symbol.asyncIterator] || source['@@asyncIterator']);
if (typeof method === 'function') {
return method.call(source);
}
return null;
}
export default function forAwaitEach(source, callback, thisArg) {
const asyncIterator = createAsyncIterator(source);
if (asyncIterator) {
let i = 0;
return new Promise((resolve, reject) => {
function next() {
asyncIterator
.next()
.then((step) => {
if (!step.done) {
Promise.resolve(callback.call(thisArg, step.value, i++, source))
.then(next)
.catch(reject);
} else {
resolve();
}
})
.catch(reject);
}
next();
});
}
return Promise.resolve();
}
================================================
FILE: src/getUserIdByLoginToken.js
================================================
const USER_TOKEN_PATH = 'services.resume.loginTokens.hashedToken';
export const NO_VALID_USER_ERROR = new Error('NO_VALID_USER');
export async function getUserIdByLoginToken(loginToken) {
// `accounts-base` is a weak dependency, so we'll try to require it
// eslint-disable-next-line global-require, prefer-destructuring
const { Accounts } = require('meteor/accounts-base');
if (!loginToken) { throw NO_VALID_USER_ERROR; }
if (typeof loginToken !== 'string') {
throw new Error("GraphQL login token isn't a string");
}
// the hashed token is the key to find the possible current user in the db
const hashedToken = Accounts._hashLoginToken(loginToken);
// get the possible user from the database with minimal fields
const fields = { _id: 1, 'services.resume': 1 };
const user = await Meteor.users.rawCollection()
.findOne({ [USER_TOKEN_PATH]: hashedToken }, { fields });
if (!user) { throw NO_VALID_USER_ERROR; }
// find the corresponding token: the user may have several open sessions on different clients
const currentToken = user.services.resume.loginTokens
.find((token) => token.hashedToken === hashedToken);
const tokenExpiresAt = Accounts._tokenExpiration(currentToken.when);
const isTokenExpired = tokenExpiresAt < new Date();
if (isTokenExpired) { throw NO_VALID_USER_ERROR; }
return user._id;
}
================================================
FILE: src/initSchema.js
================================================
export const DDP_APOLLO_SCHEMA_REQUIRED = 'DDP_APOLLO_SCHEMA_REQUIRED';
export const DDP_APOLLO_SCHEMA_AND_GATEWAY = 'DDP_APOLLO_CANNOT_COMBINE_SCHEMA_AND_GATEWAY';
export async function initSchema({
schema,
gateway,
}) {
if (!schema && !gateway) {
throw new Error(DDP_APOLLO_SCHEMA_REQUIRED);
}
if (schema && gateway) {
throw new Error(DDP_APOLLO_SCHEMA_AND_GATEWAY);
}
if (gateway) {
return gateway.load();
}
return { schema };
}
================================================
FILE: src/invokeDDP.js
================================================
import { DDP } from 'meteor/ddp';
import { Random } from 'meteor/random';
const { DDPCommon } = Package['ddp-common'];
// For details, please see https://github.com/meteor/meteor/issues/6388
function createInvocation(options) {
return new DDPCommon.MethodInvocation({
isSimulation: false,
setUserId: () => {},
unblock: () => {},
connection: {},
randomSeed: Random.id(),
...options,
});
}
export function invokeDDP(func, options) {
const invocation = createInvocation(options);
return DDP._CurrentMethodInvocation.withValue(invocation, func);
}
================================================
FILE: src/meteorAuthMiddleware.js
================================================
import { getUserIdByLoginToken, NO_VALID_USER_ERROR } from './getUserIdByLoginToken';
export function meteorAuthMiddleware(req, res, next) {
let tokenType;
let loginToken;
// get the login token from the request headers, given by meteorAuthLink
if (req.headers && req.headers.authorization) {
const parts = req.headers.authorization.split(' ');
[tokenType, loginToken] = parts;
if (parts.length !== 2 || tokenType !== 'Bearer') {
// Not a valid login token, so unset
loginToken = undefined;
}
}
// get the user for the context
getUserIdByLoginToken(loginToken)
.then((userId) => {
req.userId = userId;
next();
})
.catch((err) => {
err === NO_VALID_USER_ERROR ? next() : next(err);
});
}
================================================
FILE: src/setup.js
================================================
import { Meteor } from 'meteor/meteor';
import {
DEFAULT_METHOD,
} from '@swydo/apollo-link-ddp';
import { initSchema } from './initSchema';
import { createExecutor } from './createExecutor';
import { createGraphQLMethod } from './createGraphQLMethod';
import { createGraphQLPublication } from './createGraphQLPublication';
import { setupHttpEndpoint } from './setupHttpEndpoint';
export async function setup({
schema,
gateway,
method = DEFAULT_METHOD,
publication,
context,
} = {}) {
const {
schema: initializedSchema,
executor: gatewayExecutor,
} = await initSchema({
schema,
gateway,
});
Meteor.methods({
[method]: createGraphQLMethod({
schema: initializedSchema,
execute: createExecutor(gatewayExecutor),
context,
}),
});
if (!gateway) {
createGraphQLPublication({
schema: initializedSchema,
publication,
context,
});
}
}
export {
createGraphQLPublication,
setupHttpEndpoint,
};
================================================
FILE: src/setupHttpEndpoint.js
================================================
import { WebApp } from 'meteor/webapp';
import {
DEFAULT_PATH,
} from '@swydo/apollo-link-ddp';
import { initSchema } from './initSchema';
import { createExecutor } from './createExecutor';
import { createGraphQLPublication } from './createGraphQLPublication';
import { createGraphQLMiddleware } from './createGraphQLMiddleware';
import { meteorAuthMiddleware } from './meteorAuthMiddleware';
export async function setupHttpEndpoint({
schema,
gateway,
path = DEFAULT_PATH,
context,
engine,
jsonParser,
authMiddleware = meteorAuthMiddleware,
} = {}) {
const {
schema: initializedSchema,
executor: gatewayExecutor,
} = await initSchema({
schema,
gateway,
});
const graphQLMiddleware = createGraphQLMiddleware({
schema: initializedSchema,
context,
execute: createExecutor(gatewayExecutor),
});
if (engine && engine.expressMiddleware) {
WebApp.connectHandlers.use(engine.expressMiddleware());
}
if (!jsonParser) {
// Only require the body-parser for users who actually use the http version
// eslint-disable-next-line global-require
const bodyParser = require('body-parser');
// eslint-disable-next-line no-param-reassign
jsonParser = bodyParser.json();
}
WebApp.connectHandlers.use(path, jsonParser);
if (authMiddleware) {
WebApp.connectHandlers.use(path, authMiddleware);
}
WebApp.connectHandlers.use(path, (req, res, next) => graphQLMiddleware(req, res).catch(next));
}
export {
createGraphQLPublication,
};
gitextract_3ipm2_dq/
├── .eslintrc.json
├── .gitignore
├── .meteorignore
├── .npmrc
├── .travis.yml
├── .versions
├── CHANGELOG.md
├── LICENSE
├── README.md
├── client.js
├── docs/
│ └── MIGRATION_GUIDE_1_0.md
├── package.js
├── package.json
├── packages/
│ └── apollo-link-ddp/
│ ├── .babelrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── .npmrc
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ └── src/
│ ├── client/
│ │ ├── apollo-link-ddp.js
│ │ ├── apollo-link-meteor-auth.js
│ │ ├── apollo-link-meteor.js
│ │ ├── getLoginToken.js
│ │ ├── graphQLFetcher.js
│ │ └── listenToGraphQLMessages.js
│ ├── common/
│ │ ├── defaults.js
│ │ └── isSubscription.js
│ └── index.js
├── server.js
├── specs/
│ ├── client/
│ │ ├── apollo-client.js
│ │ ├── apollo-link-ddp.js
│ │ ├── apollo-link-meteor-auth.js
│ │ ├── apollo-link-meteor.js
│ │ ├── asteroid.js
│ │ ├── exports.js
│ │ ├── graphQLFetcher.js
│ │ ├── helpers/
│ │ │ ├── callPromise.js
│ │ │ └── login.js
│ │ └── simpleddp.js
│ ├── client.js
│ ├── data/
│ │ ├── pubsub.js
│ │ ├── resolvers.js
│ │ └── typeDefs.js
│ ├── server/
│ │ ├── exports.js
│ │ ├── getUserIdByLoginToken.js
│ │ ├── helpers/
│ │ │ └── setupLoginByUserId.js
│ │ ├── helpers.js
│ │ ├── server.js
│ │ └── setup.js
│ └── server.js
└── src/
├── contextToFunction.js
├── createExecutor.js
├── createGraphQLMethod.js
├── createGraphQLMiddleware.js
├── createGraphQLPublication.js
├── forAwaitEach.js
├── getUserIdByLoginToken.js
├── initSchema.js
├── invokeDDP.js
├── meteorAuthMiddleware.js
├── setup.js
└── setupHttpEndpoint.js
SYMBOL INDEX (55 symbols across 24 files)
FILE: packages/apollo-link-ddp/src/client/apollo-link-ddp.js
function getDefaultMeteorConnection (line 15) | function getDefaultMeteorConnection() {
function getClientContext (line 25) | function getClientContext(operation, key = DEFAULT_CLIENT_CONTEXT_KEY) {
function callPromise (line 29) | function callPromise(connection, name, args, options) {
class DDPMethodLink (line 38) | class DDPMethodLink extends ApolloLink {
method constructor (line 39) | constructor({
method request (line 52) | request(operation = {}) {
class DDPSubscriptionLink (line 68) | class DDPSubscriptionLink extends ApolloLink {
method constructor (line 69) | constructor({
method request (line 102) | request(operation = {}) {
class DDPLink (line 128) | class DDPLink extends ApolloLink {
method constructor (line 129) | constructor(options) {
method request (line 135) | request(operation = {}) {
function getDDPLink (line 144) | function getDDPLink(options) {
FILE: packages/apollo-link-ddp/src/client/apollo-link-meteor.js
class MeteorLink (line 7) | class MeteorLink extends ApolloLink {
method constructor (line 8) | constructor(options = {}) {
method request (line 21) | request(operation = {}) {
FILE: packages/apollo-link-ddp/src/client/getLoginToken.js
constant LOGIN_TOKEN_KEY (line 2) | const LOGIN_TOKEN_KEY = 'Meteor.loginToken';
function getLoginToken (line 4) | function getLoginToken() {
FILE: packages/apollo-link-ddp/src/client/graphQLFetcher.js
function createGraphQLFetcher (line 8) | function createGraphQLFetcher({
FILE: packages/apollo-link-ddp/src/client/listenToGraphQLMessages.js
function filterGraphQLMessages (line 4) | function filterGraphQLMessages(callback) {
function createClientStreamObserver (line 29) | function createClientStreamObserver(stream) {
function createSocketObserver (line 48) | function createSocketObserver(socket) {
FILE: packages/apollo-link-ddp/src/common/defaults.js
constant DEFAULT_METHOD (line 1) | const DEFAULT_METHOD = '__graphql';
constant DEFAULT_PUBLICATION (line 2) | const DEFAULT_PUBLICATION = '__graphql-subscriptions';
constant DEFAULT_SUBSCRIPTION_ID_KEY (line 3) | const DEFAULT_SUBSCRIPTION_ID_KEY = 'subscriptionId';
constant DEFAULT_CLIENT_CONTEXT_KEY (line 4) | const DEFAULT_CLIENT_CONTEXT_KEY = 'ddpContext';
constant GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE (line 5) | const GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE = 'graphql-sub-message';
constant DEFAULT_PATH (line 6) | const DEFAULT_PATH = '/graphql';
FILE: specs/client/helpers/callPromise.js
function callPromise (line 1) | function callPromise(name, ...args) {
FILE: specs/client/helpers/login.js
function loginWithUserId (line 3) | function loginWithUserId(userId) {
FILE: specs/data/resolvers.js
constant FOO_CHANGED_TOPIC (line 3) | const FOO_CHANGED_TOPIC = 'foo_changed';
FILE: specs/server/helpers.js
function reset (line 12) | function reset() {
FILE: specs/server/helpers/setupLoginByUserId.js
method createTestUser (line 4) | createTestUser() {
FILE: specs/server/setup.js
function callMethod (line 19) | async function callMethod(...args) {
function context (line 176) | function context(currentContext) {
FILE: src/contextToFunction.js
function contextToFunction (line 3) | function contextToFunction(context) {
FILE: src/createExecutor.js
function createExecutor (line 3) | function createExecutor(gatewayExecutor) {
FILE: src/createGraphQLMethod.js
constant DDP_APOLLO_SCHEMA_REQUIRED (line 4) | const DDP_APOLLO_SCHEMA_REQUIRED = 'DDP_APOLLO_SCHEMA_REQUIRED';
function createGraphQLMethod (line 6) | function createGraphQLMethod({
FILE: src/createGraphQLMiddleware.js
function createGraphQLMiddleware (line 6) | function createGraphQLMiddleware({
FILE: src/createGraphQLPublication.js
constant SUBSCRIPTION_MESSAGE_TYPE (line 11) | const SUBSCRIPTION_MESSAGE_TYPE = 'pong';
function createGraphQLPublication (line 14) | function createGraphQLPublication({
FILE: src/forAwaitEach.js
function createAsyncIterator (line 1) | function createAsyncIterator(source) {
function forAwaitEach (line 9) | function forAwaitEach(source, callback, thisArg) {
FILE: src/getUserIdByLoginToken.js
constant USER_TOKEN_PATH (line 1) | const USER_TOKEN_PATH = 'services.resume.loginTokens.hashedToken';
constant NO_VALID_USER_ERROR (line 2) | const NO_VALID_USER_ERROR = new Error('NO_VALID_USER');
function getUserIdByLoginToken (line 4) | async function getUserIdByLoginToken(loginToken) {
FILE: src/initSchema.js
constant DDP_APOLLO_SCHEMA_REQUIRED (line 1) | const DDP_APOLLO_SCHEMA_REQUIRED = 'DDP_APOLLO_SCHEMA_REQUIRED';
constant DDP_APOLLO_SCHEMA_AND_GATEWAY (line 2) | const DDP_APOLLO_SCHEMA_AND_GATEWAY = 'DDP_APOLLO_CANNOT_COMBINE_SCHEMA_...
function initSchema (line 4) | async function initSchema({
FILE: src/invokeDDP.js
function createInvocation (line 8) | function createInvocation(options) {
function invokeDDP (line 19) | function invokeDDP(func, options) {
FILE: src/meteorAuthMiddleware.js
function meteorAuthMiddleware (line 3) | function meteorAuthMiddleware(req, res, next) {
FILE: src/setup.js
function setup (line 11) | async function setup({
FILE: src/setupHttpEndpoint.js
function setupHttpEndpoint (line 11) | async function setupHttpEndpoint({
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
{
"path": ".eslintrc.json",
"chars": 482,
"preview": "{\n \"extends\": \"airbnb-base\",\n \"rules\": {\n \"no-plusplus\": 0,\n \"no-underscore-dangle\": 0,\n \"no-"
},
{
"path": ".gitignore",
"chars": 21,
"preview": "node_modules\ntest/**\n"
},
{
"path": ".meteorignore",
"chars": 22,
"preview": "test/**\ndocs\npackages\n"
},
{
"path": ".npmrc",
"chars": 35,
"preview": "save-exact=true\npackage-lock=false\n"
},
{
"path": ".travis.yml",
"chars": 433,
"preview": "dist: trusty\nsudo: required\nlanguage: node_js\nnode_js:\n - \"8\"\naddons:\n chrome: stable\ncache:\n directories:\n - ~/.n"
},
{
"path": ".versions",
"chars": 1137,
"preview": "accounts-base@2.2.2\nallow-deny@1.1.1\nbabel-compiler@7.9.0\nbabel-runtime@1.5.0\nbase64@1.0.12\nbinary-heap@1.0.11\nboilerpla"
},
{
"path": "CHANGELOG.md",
"chars": 2251,
"preview": "## vNEXT\n\n-\n\n## 4.0.2\n- Bump package versions\n- Replace old imports using apollo-link-ddp to @swydo/apollo-link-ddp\n\n## "
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2016-present Swydo\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 9445,
"preview": "# DDP-Apollo\nDDP-Apollo leverages the power of DDP for GraphQL queries and subscriptions. Meteor developers do not need "
},
{
"path": "client.js",
"chars": 190,
"preview": "import { DDPLink } from '@swydo/apollo-link-ddp';\n\nexport * from '@swydo/apollo-link-ddp';\n\n// It is common for Apollo L"
},
{
"path": "docs/MIGRATION_GUIDE_1_0.md",
"chars": 2214,
"preview": "# Migration guide to 1.0\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T ED"
},
{
"path": "package.js",
"chars": 730,
"preview": "/* eslint-disable no-var, prefer-arrow-callback */\nvar packages = [\n 'ecmascript',\n 'promise',\n 'webapp',\n 'random',"
},
{
"path": "package.json",
"chars": 1977,
"preview": "{\n \"name\": \"ddp-apollo\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"description\": \"DDP integration for the Apollo Clie"
},
{
"path": "packages/apollo-link-ddp/.babelrc",
"chars": 237,
"preview": "{\n \"presets\": [\n [\n \"env\",\n {\n \"targets\": {\n \"browsers\": [\">0.25%, not op_mini"
},
{
"path": "packages/apollo-link-ddp/.gitignore",
"chars": 18,
"preview": "node_modules\ndist\n"
},
{
"path": "packages/apollo-link-ddp/.npmignore",
"chars": 13,
"preview": "node_modules\n"
},
{
"path": "packages/apollo-link-ddp/.npmrc",
"chars": 35,
"preview": "save-exact=true\npackage-lock=false\n"
},
{
"path": "packages/apollo-link-ddp/LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2016-present Swydo\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "packages/apollo-link-ddp/README.md",
"chars": 1981,
"preview": "# Apollo Link using DDP\nThis is the client part of the DDP setup for Apollo. It works out of the box in a Meteor environ"
},
{
"path": "packages/apollo-link-ddp/package.json",
"chars": 864,
"preview": "{\n \"name\": \"@swydo/apollo-link-ddp\",\n \"version\": \"4.0.2\",\n \"description\": \"Apollo Link using DDP\",\n \"main\": \"src/ind"
},
{
"path": "packages/apollo-link-ddp/src/client/apollo-link-ddp.js",
"chars": 4228,
"preview": "const { ApolloLink, Observable, split } = require('@apollo/client/core');\nconst isSubscription = require('../common/isSu"
},
{
"path": "packages/apollo-link-ddp/src/client/apollo-link-meteor-auth.js",
"chars": 355,
"preview": "const { ApolloLink } = require('@apollo/client/core');\nconst getLoginToken = require('./getLoginToken');\n\nconst meteorAu"
},
{
"path": "packages/apollo-link-ddp/src/client/apollo-link-meteor.js",
"chars": 854,
"preview": "const { ApolloLink, HttpLink, split } = require('@apollo/client/core');\nconst isSubscription = require('../common/isSubs"
},
{
"path": "packages/apollo-link-ddp/src/client/getLoginToken.js",
"chars": 253,
"preview": "/* global localStorage */\nconst LOGIN_TOKEN_KEY = 'Meteor.loginToken';\n\nfunction getLoginToken() {\n if (typeof localSto"
},
{
"path": "packages/apollo-link-ddp/src/client/graphQLFetcher.js",
"chars": 648,
"preview": "/* global window, fetch */\nconst { DEFAULT_PATH } = require('../common/defaults');\nconst getLoginToken = require('./getL"
},
{
"path": "packages/apollo-link-ddp/src/client/listenToGraphQLMessages.js",
"chars": 1444,
"preview": "const { Observable } = require('@apollo/client/core');\nconst { GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE } = require('../common/"
},
{
"path": "packages/apollo-link-ddp/src/common/defaults.js",
"chars": 575,
"preview": "const DEFAULT_METHOD = '__graphql';\nconst DEFAULT_PUBLICATION = '__graphql-subscriptions';\nconst DEFAULT_SUBSCRIPTION_ID"
},
{
"path": "packages/apollo-link-ddp/src/common/isSubscription.js",
"chars": 279,
"preview": "const isSubscriptionDefinition = ({ kind, operation }) => kind === 'OperationDefinition' && operation === 'subscription'"
},
{
"path": "packages/apollo-link-ddp/src/index.js",
"chars": 591,
"preview": "const defaults = require('./common/defaults');\nconst isSubscription = require('./common/isSubscription');\n\nconst {\n get"
},
{
"path": "server.js",
"chars": 29,
"preview": "export * from './src/setup';\n"
},
{
"path": "specs/client/apollo-client.js",
"chars": 2514,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport gql from '"
},
{
"path": "specs/client/apollo-link-ddp.js",
"chars": 7962,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport gql from '"
},
{
"path": "specs/client/apollo-link-meteor-auth.js",
"chars": 931,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport gql from '"
},
{
"path": "specs/client/apollo-link-meteor.js",
"chars": 2385,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport gql from '"
},
{
"path": "specs/client/asteroid.js",
"chars": 1953,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport gql from '"
},
{
"path": "specs/client/exports.js",
"chars": 790,
"preview": "/* eslint-disable prefer-arrow-callback, func-names, import/named */\n/* eslint-env mocha */\nimport { expect } from 'chai"
},
{
"path": "specs/client/graphQLFetcher.js",
"chars": 1301,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport { createGr"
},
{
"path": "specs/client/helpers/callPromise.js",
"chars": 192,
"preview": "export function callPromise(name, ...args) {\n return new Promise((resolve, reject) => {\n Meteor.apply(name, args, (e"
},
{
"path": "specs/client/helpers/login.js",
"chars": 282,
"preview": "import { Accounts } from 'meteor/accounts-base';\n\nexport function loginWithUserId(userId) {\n return new Promise((resolv"
},
{
"path": "specs/client/simpleddp.js",
"chars": 1930,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport gql from '"
},
{
"path": "specs/client.js",
"chars": 267,
"preview": "import './client/exports';\nimport './client/apollo-link-ddp';\nimport './client/apollo-client';\nimport './client/apollo-l"
},
{
"path": "specs/data/pubsub.js",
"chars": 85,
"preview": "import { PubSub } from 'graphql-subscriptions';\n\nexport const pubsub = new PubSub();\n"
},
{
"path": "specs/data/resolvers.js",
"chars": 914,
"preview": "import { pubsub } from './pubsub';\n\nexport const FOO_CHANGED_TOPIC = 'foo_changed';\n\nexport const resolvers = {\n Query:"
},
{
"path": "specs/data/typeDefs.js",
"chars": 299,
"preview": "import gql from 'graphql-tag';\n\nexport const typeDefs = gql`\ntype Query {\n foo: String\n userId: String\n isDDP: Boolea"
},
{
"path": "specs/server/exports.js",
"chars": 605,
"preview": "/* eslint-disable prefer-arrow-callback, func-names, import/named */\n/* eslint-env mocha */\nimport { expect } from 'chai"
},
{
"path": "specs/server/getUserIdByLoginToken.js",
"chars": 515,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport { getUserI"
},
{
"path": "specs/server/helpers/setupLoginByUserId.js",
"chars": 431,
"preview": "import { Accounts } from 'meteor/accounts-base';\n\nMeteor.methods({\n createTestUser() {\n return Meteor.users.insert({"
},
{
"path": "specs/server/helpers.js",
"chars": 1070,
"preview": "import { makeExecutableSchema } from 'graphql-tools';\nimport {\n DEFAULT_METHOD,\n DEFAULT_PUBLICATION,\n} from '@swydo/a"
},
{
"path": "specs/server/server.js",
"chars": 23,
"preview": "import '../../server';\n"
},
{
"path": "specs/server/setup.js",
"chars": 6752,
"preview": "/* eslint-disable prefer-arrow-callback, func-names */\n/* eslint-env mocha */\nimport chai from 'chai';\nimport { makeExec"
},
{
"path": "specs/server.js",
"chars": 213,
"preview": "// helpers\nimport './server/helpers';\nimport './server/helpers/setupLoginByUserId';\n\n// specs\nimport './server/exports';"
},
{
"path": "src/contextToFunction.js",
"chars": 327,
"preview": "import { DEFAULT_CREATE_CONTEXT } from '@swydo/apollo-link-ddp';\n\nexport function contextToFunction(context) {\n switch "
},
{
"path": "src/createExecutor.js",
"chars": 602,
"preview": "import { execute } from 'graphql';\n\nexport function createExecutor(gatewayExecutor) {\n return function executor({\n s"
},
{
"path": "src/createGraphQLMethod.js",
"chars": 922,
"preview": "import { createExecutor } from './createExecutor';\nimport { contextToFunction } from './contextToFunction';\n\nexport cons"
},
{
"path": "src/createGraphQLMiddleware.js",
"chars": 902,
"preview": "import { parse } from 'graphql';\nimport { createExecutor } from './createExecutor';\nimport { contextToFunction } from '."
},
{
"path": "src/createGraphQLPublication.js",
"chars": 2041,
"preview": "import { Meteor } from 'meteor/meteor';\nimport {\n DEFAULT_PUBLICATION,\n GRAPHQL_SUBSCRIPTION_MESSAGE_TYPE,\n} from '@sw"
},
{
"path": "src/forAwaitEach.js",
"chars": 847,
"preview": "function createAsyncIterator(source) {\n const method = source && (source[Symbol.asyncIterator] || source['@@asyncIterat"
},
{
"path": "src/getUserIdByLoginToken.js",
"chars": 1361,
"preview": "const USER_TOKEN_PATH = 'services.resume.loginTokens.hashedToken';\nexport const NO_VALID_USER_ERROR = new Error('NO_VALI"
},
{
"path": "src/initSchema.js",
"chars": 466,
"preview": "export const DDP_APOLLO_SCHEMA_REQUIRED = 'DDP_APOLLO_SCHEMA_REQUIRED';\nexport const DDP_APOLLO_SCHEMA_AND_GATEWAY = 'DD"
},
{
"path": "src/invokeDDP.js",
"chars": 581,
"preview": "import { DDP } from 'meteor/ddp';\nimport { Random } from 'meteor/random';\n\nconst { DDPCommon } = Package['ddp-common'];\n"
},
{
"path": "src/meteorAuthMiddleware.js",
"chars": 767,
"preview": "import { getUserIdByLoginToken, NO_VALID_USER_ERROR } from './getUserIdByLoginToken';\n\nexport function meteorAuthMiddlew"
},
{
"path": "src/setup.js",
"chars": 985,
"preview": "import { Meteor } from 'meteor/meteor';\nimport {\n DEFAULT_METHOD,\n} from '@swydo/apollo-link-ddp';\nimport { initSchema "
},
{
"path": "src/setupHttpEndpoint.js",
"chars": 1515,
"preview": "import { WebApp } from 'meteor/webapp';\nimport {\n DEFAULT_PATH,\n} from '@swydo/apollo-link-ddp';\nimport { initSchema } "
}
]
About this extraction
This page contains the full source code of the Swydo/ddp-apollo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (74.4 KB), approximately 20.2k tokens, and a symbol index with 55 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.