mirror of
https://github.com/wassname/talk.git
synced 2026-07-31 12:50:48 +08:00
@@ -14,3 +14,7 @@ dump.rdb
|
||||
coverage/
|
||||
.tags
|
||||
.tags1
|
||||
|
||||
# remove plugin folders
|
||||
plugins
|
||||
plugins.json
|
||||
|
||||
+5
-2
@@ -12,9 +12,12 @@ EXPOSE 5000
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json yarn.lock /usr/src/app/
|
||||
RUN yarn install --production
|
||||
RUN yarn install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
CMD [ "yarn", "start" ]
|
||||
# Build static assets
|
||||
RUN yarn build
|
||||
|
||||
CMD ["yarn", "start"]
|
||||
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
# Talk Plugins
|
||||
|
||||
Plugins for Talk can take various forms, currently we are only supporting server
|
||||
side plugins.
|
||||
|
||||
## Plugin Registration: `plugins.json`
|
||||
|
||||
All plugins must be registered in the root file `plugins.json`.
|
||||
|
||||
The format for this file is thus:
|
||||
|
||||
```js
|
||||
{
|
||||
"server": [
|
||||
"people"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Where we have a `server` key with an array of plugins that match the folder
|
||||
name in the `plugins/` folder. For example, the above `plugins.json` would
|
||||
require a plugin from `plugins/people`, which must provide a `index.js` file
|
||||
that returns an object that matches the Plugin Specification.
|
||||
|
||||
## Server Plugins
|
||||
|
||||
### Specification
|
||||
|
||||
Each plugin should export a single object with all hooks available on it.
|
||||
|
||||
_**Note: You will have access to the whole core and other plugin's typeDefs,
|
||||
context, loaders, mutators, resolvers, hooks. This is intentional, as it
|
||||
encourages composing plugins to merge functionality, like a Slack plugin which
|
||||
provides a Slack notify context function as well as having the loader for
|
||||
comments.**_
|
||||
|
||||
The following are the hooks available:
|
||||
|
||||
#### Field: `typeDefs`
|
||||
|
||||
```graphql
|
||||
enum COLOUR {
|
||||
RED
|
||||
BLUE
|
||||
}
|
||||
|
||||
type Person {
|
||||
name: String!
|
||||
colour: COLOUR!
|
||||
}
|
||||
|
||||
type RootMutation {
|
||||
createPerson(name: String!): Person
|
||||
}
|
||||
|
||||
type RootQuery {
|
||||
people: [Person!]
|
||||
}
|
||||
```
|
||||
|
||||
Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of
|
||||
`typeDefs` should be a string that will be _merged_ with the existing type
|
||||
definitions. `enum`'s will be appended to, types will be appended, and new types
|
||||
will be added.
|
||||
|
||||
#### Field: `context`
|
||||
|
||||
```js
|
||||
{
|
||||
Slack: (context) => ({
|
||||
notify: (message) => {
|
||||
// return a promise after we're done sending notifications.
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Any property provided here will be added to the context parameter available
|
||||
inside all resolvers, loaders, mutators, and of course, other context based
|
||||
plugins.
|
||||
|
||||
The top level item must accept a context for the request which it should use to
|
||||
configure the context plugin before it would be mounted at `context.plugins`.
|
||||
This plugin above would mount at: `context.plugins.Slack`, or, if you're using
|
||||
[object destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), `{plugins: {Slack}}`.
|
||||
|
||||
#### Field: `loaders`
|
||||
|
||||
```js
|
||||
(context) => ({
|
||||
People: {
|
||||
load: () => db.people.find({user: context.user})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Loaders should be provided as a function which returns a map which is used in
|
||||
the resolvers function. These must return a promise or a value.
|
||||
|
||||
#### Field: `mutators`
|
||||
|
||||
```js
|
||||
(context) => ({
|
||||
People: {
|
||||
create: (name) => {
|
||||
return db.people.insert({user: context.user, name});
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Mutators should be provided as a function which returns a map which is used in
|
||||
the resolvers function. These must return a promise or a value.
|
||||
|
||||
#### Field: `resolvers`
|
||||
|
||||
```js
|
||||
{
|
||||
Person: {
|
||||
name(obj, args, context) {
|
||||
return obj.name;
|
||||
},
|
||||
colour(obj, args, context) {
|
||||
// Bill likes the colour red, everyone else likes blue.
|
||||
return obj.name === 'bill' ? 'RED' : 'BLUE';
|
||||
}
|
||||
},
|
||||
RootQuery: {
|
||||
people(obj, args, {loaders: {People}}) {
|
||||
return People.load();
|
||||
}
|
||||
},
|
||||
RootMutation: {
|
||||
createPerson(obj, {name}, {mutators: {People}}) {
|
||||
return People.create(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Should return a resolver map as described in the
|
||||
[Apollo Docs](http://dev.apollodata.com/tools/graphql-tools/resolvers.html#Resolver-map).
|
||||
|
||||
This will merge with the existing resolvers in core and from previous plugins.
|
||||
|
||||
#### Field: `hooks`
|
||||
|
||||
```js
|
||||
{
|
||||
RootMutation: {
|
||||
createPerson: {
|
||||
post: async (obj, args, {plugins: {Slack}}, person) {
|
||||
if (!person) {
|
||||
return person;
|
||||
}
|
||||
|
||||
await Slack.notify(`A new person just was created with name ${person.name}`);
|
||||
|
||||
return person;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Hooks here are pretty special, for each resolver field, you can specify a
|
||||
pre/post hook that will execute pre and post field resolution.
|
||||
|
||||
If your post function accepts four parameters, then it can modify the field
|
||||
result. It is *required* that the function resolves a promise (or returns) with
|
||||
the modified value or simply the original if you didn't modify it.
|
||||
|
||||
### Full Example
|
||||
|
||||
Contents of `plugins.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": [
|
||||
"people"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Located in `plugins/people/index.js`:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
typeDefs: `
|
||||
enum COLOUR {
|
||||
RED
|
||||
BLUE
|
||||
}
|
||||
|
||||
type Person {
|
||||
name: String!
|
||||
colour: COLOUR!
|
||||
}
|
||||
|
||||
type RootMutation {
|
||||
createPerson(name: String!): Person
|
||||
}
|
||||
|
||||
type RootQuery {
|
||||
people: [Person!]
|
||||
}
|
||||
`,
|
||||
context: {
|
||||
Slack: () => ({
|
||||
notify: (message) => {
|
||||
// return a promise after we're done sending notifications.
|
||||
}
|
||||
})
|
||||
},
|
||||
loaders: ({user}) => ({
|
||||
People: {
|
||||
load: () => db.people.find({user})
|
||||
}
|
||||
}),
|
||||
mutators: ({user}) => ({
|
||||
People: {
|
||||
create: (name) => {
|
||||
return db.people.insert({user, name});
|
||||
}
|
||||
}
|
||||
}),
|
||||
resolvers: {
|
||||
Person: {
|
||||
name(obj, args, context) {
|
||||
return obj.name;
|
||||
},
|
||||
colour(obj, args, context) {
|
||||
// Bill likes the colour red, everyone else likes blue.
|
||||
return obj.name === 'bill' ? 'RED' : 'BLUE';
|
||||
}
|
||||
},
|
||||
RootQuery: {
|
||||
people(obj, args, {loaders: {People}}) {
|
||||
return People.load();
|
||||
}
|
||||
},
|
||||
RootMutation: {
|
||||
createPerson(obj, {name}, {mutators: {People}}) {
|
||||
return People.create(name);
|
||||
}
|
||||
}
|
||||
},
|
||||
hooks: {
|
||||
RootMutation: {
|
||||
createPerson: {
|
||||
post: async (obj, args, {plugins: {Slack}}, person) => {
|
||||
if (!person) {
|
||||
return person;
|
||||
}
|
||||
|
||||
await Slack.notify(`A new person just was created with name ${person.name}`);
|
||||
|
||||
return person;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
@@ -1,6 +1,32 @@
|
||||
const loaders = require('./loaders');
|
||||
const mutators = require('./mutators');
|
||||
|
||||
const plugins = require('../plugins');
|
||||
const debug = require('debug')('talk:graph:context');
|
||||
|
||||
/**
|
||||
* Contains the array of plugins that provide context to the server, these top
|
||||
* level functions all need the context reference.
|
||||
* @type {Array}
|
||||
*/
|
||||
const contextPlugins = plugins.get('server', 'context').map(({plugin, context}) => {
|
||||
debug(`added plugin '${plugin.name}'`);
|
||||
return {context};
|
||||
});
|
||||
|
||||
/**
|
||||
* This should itterate over the passed in plugins and load them all with the
|
||||
* current graph context.
|
||||
* @return {Object} the saturated plugins object
|
||||
*/
|
||||
const decorateContextPlugins = (context, contextPlugins) => contextPlugins.reduce((acc, plugin) => {
|
||||
Object.keys(plugin.context).forEach((service) => {
|
||||
acc[service] = plugin.context[service](context);
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
/**
|
||||
* Stores the request context.
|
||||
*/
|
||||
@@ -17,6 +43,9 @@ class Context {
|
||||
|
||||
// Create the mutators.
|
||||
this.mutators = mutators(this);
|
||||
|
||||
// Decorate the plugin context.
|
||||
this.plugins = decorateContextPlugins(this, contextPlugins);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
const {forEachField} = require('graphql-tools');
|
||||
const debug = require('debug')('talk:graph:schema');
|
||||
|
||||
/**
|
||||
* XXX taken from graphql-js: src/execution/execute.js, because that function
|
||||
* is not exported
|
||||
*
|
||||
* If a resolve function is not given, then a default resolve behavior is used
|
||||
* which takes the property of the source object of the same name as the field
|
||||
* and returns it as the result, or if it's a function, returns the result
|
||||
* of calling that function.
|
||||
*/
|
||||
const defaultResolveFn = (source, args, context, {fieldName}) => {
|
||||
|
||||
// ensure source is a value for which property access is acceptable.
|
||||
if (typeof source === 'object' || typeof source === 'function') {
|
||||
const property = source[fieldName];
|
||||
if (typeof property === 'function') {
|
||||
return source[fieldName](args, context);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorates the schema with pre and post hooks as provided by the Plugin
|
||||
* Manager.
|
||||
* @param {GraphQLSchema} schema the schema to decorate
|
||||
* @param {Array} hooks hooks to apply to the schema
|
||||
* @return {void}
|
||||
*/
|
||||
const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeName, fieldName) => {
|
||||
|
||||
// Pull out the pre/post hooks from the available hooks.
|
||||
const {
|
||||
pre,
|
||||
post
|
||||
} = hooks
|
||||
|
||||
// Only grab hooks that are associated with thie field and typeName.
|
||||
.filter(({hooks}) => (typeName in hooks) && (fieldName in hooks[typeName]))
|
||||
|
||||
// Grab the hooks we need.
|
||||
.map(({plugin, hooks}) => ({plugin, hooks: hooks[typeName][fieldName]}))
|
||||
|
||||
// Combine the pre/post hooks from each plugin into an array we can
|
||||
// execute.
|
||||
.reduce((acc, {plugin, hooks}) => {
|
||||
|
||||
// Itterate over the hooks on the fields and look at it with a switch
|
||||
// block to check for misconfigured plugins.
|
||||
Object.keys(hooks).forEach((hook) => {
|
||||
switch (hook) {
|
||||
case 'pre':
|
||||
debug(`adding pre hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`);
|
||||
|
||||
if (typeof hooks.pre !== 'function') {
|
||||
throw new Error(`expected ${hook} hook on resolver ${typeName}.${fieldName} from plugin '${plugin.name}' to be a function, it was a '${typeof hooks[hook]}'`);
|
||||
}
|
||||
|
||||
acc.pre.push(hooks.pre);
|
||||
break;
|
||||
case 'post':
|
||||
debug(`adding post hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`);
|
||||
|
||||
if (typeof hooks.post !== 'function') {
|
||||
throw new Error(`expected ${hook} hook on resolver ${typeName}.${fieldName} from plugin '${plugin.name}' to be a function, it was a '${typeof hooks[hook]}'`);
|
||||
}
|
||||
|
||||
acc.post.unshift(hooks.post);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`invalid hook '${hook}' on resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`);
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {
|
||||
pre: [],
|
||||
post: []
|
||||
});
|
||||
|
||||
// If we have no hooks to add here, don't try to modify anything.
|
||||
if (pre.length === 0 && post.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the original resolve function, this emulates the beheviour found in
|
||||
// graphql-tools: https://github.com/apollographql/graphql-tools/blob/6e9cc124b10d673448386041e6c3d058bc205a02/src/schemaGenerator.ts#L423-L425
|
||||
let resolve = field.resolve;
|
||||
if (typeof resolve === 'undefined') {
|
||||
resolve = defaultResolveFn;
|
||||
}
|
||||
|
||||
// Apply our async resolve function which will fire all pre functions (and
|
||||
// wait until they resolve) followed by waiting for the response and then
|
||||
// firing their post hooks. Lastly, we respond with the result of the
|
||||
// original resolver.
|
||||
field.resolve = async (obj, args, context, info) => {
|
||||
|
||||
// Issue all pre hooks before we resolve the field.
|
||||
await Promise.all(pre.map((pre) => pre(obj, args, context, info)));
|
||||
|
||||
// Resolve the field.
|
||||
let result = resolve(obj, args, context, info);
|
||||
|
||||
// Insure all post hooks after we've resolved the field with the result
|
||||
// passed in as the fifth argument.
|
||||
return await post.reduce(async (result, post) => {
|
||||
|
||||
// Wait for the accumulator to resolve before we continue.
|
||||
result = await result;
|
||||
|
||||
// Check to see if this post function accepts a result, if it does, we
|
||||
// expect that it modifies the result, otherwise, just fire the post hook,
|
||||
// wait till it's done, then move onto the next hook.
|
||||
if (post.length === 5) {
|
||||
return await post(obj, args, context, info, result);
|
||||
}
|
||||
|
||||
// Wait for the post hook to finish.
|
||||
await post(obj, args, context, info);
|
||||
|
||||
// Return the result, which we already awaited for before.
|
||||
return result;
|
||||
}, result);
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
decorateWithHooks
|
||||
};
|
||||
+23
-8
@@ -1,4 +1,5 @@
|
||||
const _ = require('lodash');
|
||||
const debug = require('debug')('talk:graph:loaders');
|
||||
|
||||
const Actions = require('./actions');
|
||||
const Assets = require('./assets');
|
||||
@@ -7,6 +8,27 @@ const Metrics = require('./metrics');
|
||||
const Settings = require('./settings');
|
||||
const Users = require('./users');
|
||||
|
||||
const plugins = require('../../plugins');
|
||||
|
||||
let loaders = [
|
||||
|
||||
// Load the core loaders.
|
||||
Actions,
|
||||
Assets,
|
||||
Comments,
|
||||
Metrics,
|
||||
Settings,
|
||||
Users,
|
||||
|
||||
// Load the plugin loaders from the manager.
|
||||
...plugins
|
||||
.get('server', 'loaders').map(({plugin, loaders}) => {
|
||||
debug(`added plugin '${plugin.name}'`);
|
||||
|
||||
return loaders;
|
||||
})
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a set of loaders based on a GraphQL context.
|
||||
* @param {Object} context the context of the GraphQL request
|
||||
@@ -15,14 +37,7 @@ const Users = require('./users');
|
||||
module.exports = (context) => {
|
||||
|
||||
// We need to return an object to be accessed.
|
||||
return _.merge(...[
|
||||
Actions,
|
||||
Assets,
|
||||
Comments,
|
||||
Metrics,
|
||||
Settings,
|
||||
Users
|
||||
].map((loaders) => {
|
||||
return _.merge(...loaders.map((loaders) => {
|
||||
|
||||
// Each loader is a function which takes the context.
|
||||
return loaders(context);
|
||||
|
||||
+25
-5
@@ -1,17 +1,37 @@
|
||||
const _ = require('lodash');
|
||||
const debug = require('debug')('talk:graph:mutators');
|
||||
|
||||
const Comment = require('./comment');
|
||||
const Action = require('./action');
|
||||
const User = require('./user');
|
||||
|
||||
const plugins = require('../../plugins');
|
||||
|
||||
let mutators = [
|
||||
|
||||
// Load in the core mutators.
|
||||
Comment,
|
||||
Action,
|
||||
User,
|
||||
|
||||
// Load the plugin mutators from the manager.
|
||||
...plugins
|
||||
.get('server', 'mutators').map(({plugin, mutators}) => {
|
||||
debug(`added plugin '${plugin.name}'`);
|
||||
|
||||
return mutators;
|
||||
})
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a set of mutators based on a GraphQL context.
|
||||
* @param {Object} context the context of the GraphQL request
|
||||
* @return {Object} object of mutators
|
||||
*/
|
||||
module.exports = (context) => {
|
||||
|
||||
// We need to return an object to be accessed.
|
||||
return _.merge(...[
|
||||
Comment,
|
||||
Action,
|
||||
User,
|
||||
].map((mutators) => {
|
||||
return _.merge(...mutators.map((mutators) => {
|
||||
|
||||
// Each set of mutators is a function which takes the context.
|
||||
return mutators(context);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
const _ = require('lodash');
|
||||
const debug = require('debug')('talk:graph:resolvers');
|
||||
|
||||
const ActionSummary = require('./action_summary');
|
||||
const Action = require('./action');
|
||||
const AssetActionSummary = require('./asset_action_summary');
|
||||
@@ -17,7 +20,10 @@ const UserError = require('./user_error');
|
||||
const User = require('./user');
|
||||
const ValidationUserError = require('./validation_user_error');
|
||||
|
||||
module.exports = {
|
||||
const plugins = require('../../plugins');
|
||||
|
||||
// Provide the core resolvers.
|
||||
let resolvers = {
|
||||
ActionSummary,
|
||||
Action,
|
||||
AssetActionSummary,
|
||||
@@ -37,3 +43,16 @@ module.exports = {
|
||||
User,
|
||||
ValidationUserError,
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin support requires that we merge in existing resolvers with our new
|
||||
* plugin based ones. This allows plugins to extend existing resolvers as well
|
||||
* as provide new ones.
|
||||
*/
|
||||
resolvers = plugins.get('server', 'resolvers').reduce((resolvers, {plugin}) => {
|
||||
debug(`added plugin '${plugin.name}'`);
|
||||
|
||||
return _.merge(resolvers, plugin.resolvers);
|
||||
}, resolvers);
|
||||
|
||||
module.exports = resolvers;
|
||||
|
||||
+9
-3
@@ -1,11 +1,17 @@
|
||||
const tools = require('graphql-tools');
|
||||
const maskErrors = require('graphql-errors').maskErrors;
|
||||
const {makeExecutableSchema} = require('graphql-tools');
|
||||
const {maskErrors} = require('graphql-errors');
|
||||
const {decorateWithHooks} = require('./hooks');
|
||||
|
||||
const plugins = require('../plugins');
|
||||
const resolvers = require('./resolvers');
|
||||
const typeDefs = require('./typeDefs');
|
||||
|
||||
const schema = tools.makeExecutableSchema({typeDefs, resolvers});
|
||||
const schema = makeExecutableSchema({typeDefs, resolvers});
|
||||
|
||||
// Plugin to the schema level resolvers to provide an before/after hook.
|
||||
decorateWithHooks(schema, plugins.get('server', 'hooks'));
|
||||
|
||||
// If we are in production mode, don't show server errors to the front end.
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
|
||||
// Mask errors that are thrown if we are in a production environment.
|
||||
|
||||
+20
-2
@@ -4,8 +4,26 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {mergeStrings} = require('gql-merge');
|
||||
const debug = require('debug')('talk:graph:typeDefs');
|
||||
const plugins = require('../plugins');
|
||||
|
||||
// Load the typeDefs from the graphql file.
|
||||
const typeDefs = fs.readFileSync(path.join(__dirname, 'typeDefs.graphql'), 'utf8');
|
||||
/**
|
||||
* Plugin support requires us to merge the type definitions from the loaded
|
||||
* graphql tags, this gives us the ability to extend any portion of the
|
||||
* available graph.
|
||||
*/
|
||||
const typeDefs = mergeStrings([
|
||||
|
||||
// Load the core graph definitions from the filesystem.
|
||||
fs.readFileSync(path.join(__dirname, 'typeDefs.graphql'), 'utf8'),
|
||||
|
||||
// Load the plugin definitions from the manager.
|
||||
...plugins.get('server', 'typeDefs').map(({plugin, typeDefs}) => {
|
||||
debug(`added plugin '${plugin.name}'`);
|
||||
|
||||
return typeDefs;
|
||||
})
|
||||
]);
|
||||
|
||||
module.exports = typeDefs;
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"env-rewrite": "^1.0.2",
|
||||
"express": "^4.14.0",
|
||||
"express-session": "^1.14.2",
|
||||
"gql-merge": "^0.0.4",
|
||||
"form-data": "^2.1.2",
|
||||
"graphql": "^0.8.2",
|
||||
"graphql-errors": "^2.1.0",
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let plugins = {};
|
||||
|
||||
// Try to parse the plugins.json file, logging out an error if the plugins.json
|
||||
// file isn't loaded, but continuing. Else, like a parsing error, throw it and
|
||||
// crash the program.
|
||||
try {
|
||||
plugins = JSON.parse(fs.readFileSync(path.join(__dirname, 'plugins.json'), 'utf8'));
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.error('plugins.json not found, plugins will not be active');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a reference to a section for a section of Plugins.
|
||||
*/
|
||||
class PluginSection {
|
||||
constructor(plugin_names) {
|
||||
this.plugins = plugin_names.map((plugin_name) => {
|
||||
let plugin = require(`./plugins/${plugin_name}`);
|
||||
|
||||
// Ensure we have a default plugin name, but allow the name to be
|
||||
// overrided by the plugin.
|
||||
plugin.name = plugin.name || plugin_name;
|
||||
|
||||
return plugin;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This itterates over the section to provide all plugin hooks that are
|
||||
* available.
|
||||
*/
|
||||
hook(hook) {
|
||||
return this.plugins
|
||||
.filter((plugin) => hook in plugin)
|
||||
.map((plugin) => ({plugin, [hook]: plugin[hook]}));
|
||||
}
|
||||
}
|
||||
|
||||
const NullPluginSection = new PluginSection([]);
|
||||
|
||||
/**
|
||||
* Stores references to all the plugins available on the application.
|
||||
*/
|
||||
class PluginManager {
|
||||
constructor(plugins) {
|
||||
this.sections = {};
|
||||
|
||||
for (let section in plugins) {
|
||||
this.sections[section] = new PluginSection(plugins[section]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function which combines the Plugins.section and PluginSection.hook
|
||||
* calls.
|
||||
*/
|
||||
get(section, hook) {
|
||||
return this.section(section).hook(hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the named section if it exists, otherwise it returns an empty
|
||||
* plugin section.
|
||||
*/
|
||||
section(section) {
|
||||
if (section in this.sections) {
|
||||
return this.sections[section];
|
||||
}
|
||||
|
||||
return NullPluginSection;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PluginManager(plugins);
|
||||
@@ -1133,7 +1133,7 @@ bluebird@3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.3.4.tgz#f780fe43e1a7a6510f67abd7d0d79533a40ddde6"
|
||||
|
||||
bluebird@3.4.6:
|
||||
bluebird@3.4.6, bluebird@^3.4.6:
|
||||
version "3.4.6"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.6.tgz#01da8d821d87813d158967e743d5fe6c62cf8c0f"
|
||||
|
||||
@@ -3230,7 +3230,7 @@ glob@7.0.5, glob@7.0.x:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5:
|
||||
glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
|
||||
dependencies:
|
||||
@@ -3302,6 +3302,29 @@ got@^3.2.0:
|
||||
read-all-stream "^3.0.0"
|
||||
timed-out "^2.0.0"
|
||||
|
||||
gql-format@^0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/gql-format/-/gql-format-0.0.4.tgz#8237de7647de37f00aba2d0073abf6087e2da119"
|
||||
dependencies:
|
||||
commander "^2.9.0"
|
||||
graphql "^0.7.2"
|
||||
|
||||
gql-merge@^0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/gql-merge/-/gql-merge-0.0.4.tgz#1cb1d4cc8bb8768172cf08a45c5a4fbd0ecedc9f"
|
||||
dependencies:
|
||||
commander "^2.9.0"
|
||||
gql-format "^0.0.4"
|
||||
gql-utils "^0.0.2"
|
||||
graphql "^0.7.2"
|
||||
|
||||
gql-utils@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/gql-utils/-/gql-utils-0.0.2.tgz#962b3c1b34bf965a45d2564a93d3072921f61e86"
|
||||
dependencies:
|
||||
bluebird "^3.4.6"
|
||||
glob "^7.1.1"
|
||||
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
||||
@@ -3369,6 +3392,12 @@ graphql-tools@^0.9.0:
|
||||
optionalDependencies:
|
||||
"@types/graphql" "^0.8.5"
|
||||
|
||||
graphql@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.7.2.tgz#cc894a32823399b8a0cb012b9e9ecad35cd00f72"
|
||||
dependencies:
|
||||
iterall "1.0.2"
|
||||
|
||||
graphql@^0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.8.2.tgz#eb1bb524b38104bbf2c9157f9abc67db2feba7d2"
|
||||
|
||||
Reference in New Issue
Block a user