Skip to content

Commit

Permalink
Initial changes for Feathers v3 (#605)
Browse files Browse the repository at this point in the history
* Refactoring for Feathers v3. Framework independent, hooks in core.

* Further work on event dispatching

* Finalize basic v3 functionality

* Finalizing test coverage and initial v3 alpha

* Update commonns dependency and prepare for 3.0 prereleases
  • Loading branch information
daffl committed Jun 29, 2017
1 parent 9a6410d commit eee2bca
Show file tree
Hide file tree
Showing 31 changed files with 1,202 additions and 2,449 deletions.
20 changes: 5 additions & 15 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "feathers",
"description": "Build Better APIs, Faster than Ever.",
"version": "2.1.3",
"version": "3.0.0-pre.0",
"homepage": "http://feathersjs.com",
"repository": {
"type": "git",
Expand Down Expand Up @@ -32,11 +32,12 @@
"release:patch": "npm version patch && npm publish",
"release:minor": "npm version minor && npm publish",
"release:major": "npm version major && npm publish",
"release:prerelease": "npm version prerelease && npm publish --tag pegasus",
"release:pre": "npm version prerelease && npm publish --tag pegasus",
"compile": "rimraf lib/ && babel -d lib/ src/",
"watch": "babel --watch -d lib/ src/",
"lint": "eslint-if-supported semistandard --fix",
"mocha": "mocha --opts mocha.opts",
"mocha:watch": "mocha --opts mocha.opts --watch",
"coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --opts mocha.opts",
"test": "npm run compile && npm run lint && npm run coverage && nsp check"
},
Expand All @@ -51,31 +52,20 @@
"dependencies": {
"debug": "^2.6.0",
"events": "^1.1.1",
"express": "^4.14.0",
"feathers-commons": "^0.8.7",
"rubberduck": "^1.1.1",
"feathers-commons": "^1.0.0-pre.1",
"uberproto": "^1.2.0"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.21.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-preset-es2015": "^6.18.0",
"body-parser": "^1.15.2",
"eslint-if-supported": "^1.0.1",
"feathers-rest": "^1.5.3",
"feathers-socketio": "^2.0.0",
"istanbul": "^1.1.0-alpha.1",
"jshint": "^2.9.4",
"mocha": "^3.2.0",
"nsp": "^2.6.2",
"q": "^1.4.1",
"request": "^2.x",
"rimraf": "^2.5.4",
"semistandard": "^10.0.0",
"socket.io-client": "^2.0.0"
},
"browser": {
"./lib/index": "./lib/client/index"
"semistandard": "^10.0.0"
}
}
140 changes: 72 additions & 68 deletions src/application.js
@@ -1,52 +1,103 @@
import makeDebug from 'debug';
import { stripSlashes } from 'feathers-commons';
import Uberproto from 'uberproto';
import mixins from './mixins/index';

import events from './events';
import hooks from './hooks';

const debug = makeDebug('feathers:application');
const methods = ['find', 'get', 'create', 'update', 'patch', 'remove'];
const Proto = Uberproto.extend({
create: null
});

export default {
const application = {
init () {
Object.assign(this, {
methods,
mixins: mixins(),
methods: [ 'find', 'get', 'create', 'update', 'patch', 'remove' ],
mixins: [],
services: {},
providers: [],
_setup: false
_setup: false,
settings: {}
});

this.configure(hooks());
this.configure(events());
},

service (location, service, options = {}) {
location = stripSlashes(location);
get (name) {
return this.settings[name];
},

if (!service) {
const current = this.services[location];
set (name, value) {
this.settings[name] = value;
return this;
},

if (typeof current === 'undefined' && typeof this.defaultService === 'function') {
return this.service(location, this.defaultService(location), options);
}
disable (name) {
this.settings[name] = false;
return this;
},

disabled (name) {
return !this.settings[name];
},

return current;
enable (name) {
this.settings[name] = true;
return this;
},

enabled (name) {
return !!this.settings[name];
},

configure (fn) {
fn.call(this);

return this;
},

service (path, service) {
if (typeof service !== 'undefined') {
throw new Error('Registering a new service with `app.service(path, service)` is no longer supported. Use `app.use(path, service)` instead.');
}

let protoService = Proto.extend(service);
const location = stripSlashes(path);
const current = this.services[location];

if (typeof current === 'undefined' && typeof this.defaultService === 'function') {
return this.use(`/${location}`, this.defaultService(location))
.service(location);
}

return current;
},

use (path, service, options = {}) {
const location = stripSlashes(path);
const hasMethod = methods => methods.some(name =>
(service && typeof service[name] === 'function')
);

if (!hasMethod(this.methods.concat('setup'))) {
throw new Error(`Invalid service object passed for path \`${location}\``);
}

const protoService = Proto.extend(service);

debug(`Registering new service at \`${location}\``);

// Add all the mixins
this.mixins.forEach(fn => fn.call(this, protoService));
this.mixins.forEach(fn => fn.call(this, protoService, location, options));

if (typeof protoService._setup === 'function') {
protoService._setup(this, location);
}

// Run the provider functions to register the service
this.providers.forEach(provider =>
provider.call(this, location, protoService, options)
provider.call(this, protoService, location, options)
);

// If we ran setup already, set this service up explicitly
Expand All @@ -55,38 +106,7 @@ export default {
protoService.setup(this, location);
}

return (this.services[location] = protoService);
},

use (location) {
let service;
let middleware = Array.from(arguments)
.slice(1)
.reduce(function (middleware, arg) {
if (typeof arg === 'function') {
middleware[service ? 'after' : 'before'].push(arg);
} else if (!service) {
service = arg;
} else {
throw new Error('invalid arg passed to app.use');
}
return middleware;
}, {
before: [],
after: []
});

const hasMethod = methods => methods.some(name =>
(service && typeof service[name] === 'function')
);

// Check for service (any object with at least one service method)
if (hasMethod(['handle', 'set']) || !hasMethod(this.methods.concat('setup'))) {
return this._super.apply(this, arguments);
}

// Any arguments left over are other middleware that we want to pass to the providers
this.service(location, service, { middleware });
this.services[location] = protoService;

return this;
},
Expand All @@ -97,6 +117,7 @@ export default {
const service = this.services[path];

debug(`Setting up service for \`${path}\``);

if (typeof service.setup === 'function') {
service.setup(this, path);
}
Expand All @@ -105,24 +126,7 @@ export default {
this._isSetup = true;

return this;
},

// Express 3.x configure is gone in 4.x but we'll keep a more basic version
// That just takes a function in order to keep Feathers plugin configuration easier.
// Environment specific configurations should be done as suggested in the 4.x migration guide:
// https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x
configure (fn) {
fn.call(this);

return this;
},

listen () {
const server = this._super.apply(this, arguments);

this.setup(server);
debug('Feathers application listening');

return server;
}
};

export default application;
47 changes: 0 additions & 47 deletions src/client/express.js

This file was deleted.

8 changes: 0 additions & 8 deletions src/client/index.js

This file was deleted.

82 changes: 82 additions & 0 deletions src/events.js
@@ -0,0 +1,82 @@
import { EventEmitter } from 'events';
import Proto from 'uberproto';

export function eventHook () {
return function (hook) {
const { app, service } = hook;
const eventName = app.eventMappings[hook.method];
const isHookEvent = service._hookEvents && service._hookEvents.indexOf(eventName) !== -1;

// If this event is not being sent yet and we are not in an error hook
if (eventName && isHookEvent && hook.type !== 'error') {
service.emit(eventName, hook.result, hook);
}
};
}

export function eventMixin (service) {
if (service._serviceEvents) {
return;
}

const app = this;
// Indicates if the service is already an event emitter
const isEmitter = typeof service.on === 'function' &&
typeof service.emit === 'function';

// If not, mix it in (the service is always an Uberproto object that has a .mixin)
if (typeof service.mixin === 'function' && !isEmitter) {
service.mixin(EventEmitter.prototype);
}

// Define non-enumerable properties of
Object.defineProperties(service, {
// A list of all events that this service sends
_serviceEvents: {
value: Array.isArray(service.events) ? service.events.slice() : []
},

// A list of events that should be handled through the event hooks
_hookEvents: {
value: []
}
});

// `app.eventMappings` has the mapping from method name to event name
Object.keys(app.eventMappings).forEach(method => {
const event = app.eventMappings[method];
const alreadyEmits = service._serviceEvents.indexOf(event) !== -1;

// Add events for known methods to _serviceEvents and _hookEvents
// if the service indicated it does not send it itself yet
if (typeof service[method] === 'function' && !alreadyEmits) {
service._serviceEvents.push(event);
service._hookEvents.push(event);
}
});
}

export default function () {
return function () {
const app = this;

// Mappings from service method to event name
Object.assign(app, {
eventMappings: {
create: 'created',
update: 'updated',
remove: 'removed',
patch: 'patched'
}
});

// Register the event hook
// `finally` hooks always run last after `error` and `after` hooks
app.hooks({ finally: eventHook() });

// Make the app an event emitter
Proto.mixin(EventEmitter.prototype, app);

app.mixins.push(eventMixin);
};
}

0 comments on commit eee2bca

Please sign in to comment.