Skip to content

Commit 419a48d

Browse files
committed
Initial changes for Feathers v3 (#605)
* 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
1 parent ad79b22 commit 419a48d

31 files changed

Lines changed: 1205 additions & 2448 deletions

package.json

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"name": "feathers",
33
"description": "Build Better APIs, Faster than Ever.",
4+
<<<<<<< HEAD
45
"version": "2.2.2",
6+
=======
7+
"version": "3.0.0-pre.0",
8+
>>>>>>> Initial changes for Feathers v3 (#605)
59
"homepage": "http://feathersjs.com",
610
"repository": {
711
"type": "git",
@@ -32,11 +36,12 @@
3236
"release:patch": "npm version patch && npm publish",
3337
"release:minor": "npm version minor && npm publish",
3438
"release:major": "npm version major && npm publish",
35-
"release:prerelease": "npm version prerelease && npm publish --tag pegasus",
39+
"release:pre": "npm version prerelease && npm publish --tag pegasus",
3640
"compile": "rimraf lib/ && babel -d lib/ src/",
3741
"watch": "babel --watch -d lib/ src/",
3842
"lint": "eslint-if-supported semistandard --fix",
3943
"mocha": "mocha --opts mocha.opts",
44+
"mocha:watch": "mocha --opts mocha.opts --watch",
4045
"coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --opts mocha.opts",
4146
"test": "npm run compile && npm run lint && npm run coverage && nsp check"
4247
},
@@ -51,9 +56,7 @@
5156
"dependencies": {
5257
"debug": "^3.1.0",
5358
"events": "^1.1.1",
54-
"express": "^4.16.1",
55-
"feathers-commons": "^0.8.7",
56-
"rubberduck": "^1.1.1",
59+
"feathers-commons": "^1.0.0-pre.1",
5760
"uberproto": "^1.2.0"
5861
},
5962
"devDependencies": {
@@ -62,21 +65,12 @@
6265
"babel-plugin-add-module-exports": "^0.2.1",
6366
"babel-plugin-transform-runtime": "^6.23.0",
6467
"babel-preset-es2015": "^6.18.0",
65-
"body-parser": "^1.15.2",
6668
"eslint-if-supported": "^1.0.1",
67-
"feathers-rest": "^1.5.3",
68-
"feathers-socketio": "^2.0.0",
6969
"istanbul": "^1.1.0-alpha.1",
7070
"jshint": "^2.9.4",
7171
"mocha": "^4.0.0",
7272
"nsp": "^2.6.2",
73-
"q": "^1.4.1",
74-
"request": "^2.x",
7573
"rimraf": "^2.5.4",
76-
"semistandard": "^10.0.0",
77-
"socket.io-client": "^2.0.0"
78-
},
79-
"browser": {
80-
"./lib/index": "./lib/client/index"
74+
"semistandard": "^10.0.0"
8175
}
8276
}

src/application.js

Lines changed: 72 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,103 @@
11
import makeDebug from 'debug';
22
import { stripSlashes } from 'feathers-commons';
33
import Uberproto from 'uberproto';
4-
import mixins from './mixins/index';
4+
5+
import events from './events';
6+
import hooks from './hooks';
57

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

12-
export default {
13+
const application = {
1314
init () {
1415
Object.assign(this, {
15-
methods,
16-
mixins: mixins(),
16+
methods: [ 'find', 'get', 'create', 'update', 'patch', 'remove' ],
17+
mixins: [],
1718
services: {},
1819
providers: [],
19-
_setup: false
20+
_setup: false,
21+
settings: {}
2022
});
23+
24+
this.configure(hooks());
25+
this.configure(events());
2126
},
2227

23-
service (location, service, options = {}) {
24-
location = stripSlashes(location);
28+
get (name) {
29+
return this.settings[name];
30+
},
2531

26-
if (!service) {
27-
const current = this.services[location];
32+
set (name, value) {
33+
this.settings[name] = value;
34+
return this;
35+
},
2836

29-
if (typeof current === 'undefined' && typeof this.defaultService === 'function') {
30-
return this.service(location, this.defaultService(location), options);
31-
}
37+
disable (name) {
38+
this.settings[name] = false;
39+
return this;
40+
},
41+
42+
disabled (name) {
43+
return !this.settings[name];
44+
},
3245

33-
return current;
46+
enable (name) {
47+
this.settings[name] = true;
48+
return this;
49+
},
50+
51+
enabled (name) {
52+
return !!this.settings[name];
53+
},
54+
55+
configure (fn) {
56+
fn.call(this);
57+
58+
return this;
59+
},
60+
61+
service (path, service) {
62+
if (typeof service !== 'undefined') {
63+
throw new Error('Registering a new service with `app.service(path, service)` is no longer supported. Use `app.use(path, service)` instead.');
3464
}
3565

36-
let protoService = Proto.extend(service);
66+
const location = stripSlashes(path);
67+
const current = this.services[location];
68+
69+
if (typeof current === 'undefined' && typeof this.defaultService === 'function') {
70+
return this.use(`/${location}`, this.defaultService(location))
71+
.service(location);
72+
}
73+
74+
return current;
75+
},
76+
77+
use (path, service, options = {}) {
78+
const location = stripSlashes(path);
79+
const hasMethod = methods => methods.some(name =>
80+
(service && typeof service[name] === 'function')
81+
);
82+
83+
if (!hasMethod(this.methods.concat('setup'))) {
84+
throw new Error(`Invalid service object passed for path \`${location}\``);
85+
}
86+
87+
const protoService = Proto.extend(service);
3788

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

4091
// Add all the mixins
41-
this.mixins.forEach(fn => fn.call(this, protoService));
92+
this.mixins.forEach(fn => fn.call(this, protoService, location, options));
4293

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

4798
// Run the provider functions to register the service
4899
this.providers.forEach(provider =>
49-
provider.call(this, location, protoService, options)
100+
provider.call(this, protoService, location, options)
50101
);
51102

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

58-
return (this.services[location] = protoService);
59-
},
60-
61-
use (location) {
62-
let service;
63-
let middleware = Array.from(arguments)
64-
.slice(1)
65-
.reduce(function (middleware, arg) {
66-
if (typeof arg === 'function') {
67-
middleware[service ? 'after' : 'before'].push(arg);
68-
} else if (!service) {
69-
service = arg;
70-
} else {
71-
throw new Error('invalid arg passed to app.use');
72-
}
73-
return middleware;
74-
}, {
75-
before: [],
76-
after: []
77-
});
78-
79-
const hasMethod = methods => methods.some(name =>
80-
(service && typeof service[name] === 'function')
81-
);
82-
83-
// Check for service (any object with at least one service method)
84-
if (hasMethod(['handle', 'set']) || !hasMethod(this.methods.concat('setup'))) {
85-
return this._super.apply(this, arguments);
86-
}
87-
88-
// Any arguments left over are other middleware that we want to pass to the providers
89-
this.service(location, service, { middleware });
109+
this.services[location] = protoService;
90110

91111
return this;
92112
},
@@ -97,6 +117,7 @@ export default {
97117
const service = this.services[path];
98118

99119
debug(`Setting up service for \`${path}\``);
120+
100121
if (typeof service.setup === 'function') {
101122
service.setup(this, path);
102123
}
@@ -105,24 +126,7 @@ export default {
105126
this._isSetup = true;
106127

107128
return this;
108-
},
109-
110-
// Express 3.x configure is gone in 4.x but we'll keep a more basic version
111-
// That just takes a function in order to keep Feathers plugin configuration easier.
112-
// Environment specific configurations should be done as suggested in the 4.x migration guide:
113-
// https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x
114-
configure (fn) {
115-
fn.call(this);
116-
117-
return this;
118-
},
119-
120-
listen () {
121-
const server = this._super.apply(this, arguments);
122-
123-
this.setup(server);
124-
debug('Feathers application listening');
125-
126-
return server;
127129
}
128130
};
131+
132+
export default application;

src/client/express.js

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/client/index.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/events.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { EventEmitter } from 'events';
2+
import Proto from 'uberproto';
3+
4+
export function eventHook () {
5+
return function (hook) {
6+
const { app, service } = hook;
7+
const eventName = app.eventMappings[hook.method];
8+
const isHookEvent = service._hookEvents && service._hookEvents.indexOf(eventName) !== -1;
9+
10+
// If this event is not being sent yet and we are not in an error hook
11+
if (eventName && isHookEvent && hook.type !== 'error') {
12+
service.emit(eventName, hook.result, hook);
13+
}
14+
};
15+
}
16+
17+
export function eventMixin (service) {
18+
if (service._serviceEvents) {
19+
return;
20+
}
21+
22+
const app = this;
23+
// Indicates if the service is already an event emitter
24+
const isEmitter = typeof service.on === 'function' &&
25+
typeof service.emit === 'function';
26+
27+
// If not, mix it in (the service is always an Uberproto object that has a .mixin)
28+
if (typeof service.mixin === 'function' && !isEmitter) {
29+
service.mixin(EventEmitter.prototype);
30+
}
31+
32+
// Define non-enumerable properties of
33+
Object.defineProperties(service, {
34+
// A list of all events that this service sends
35+
_serviceEvents: {
36+
value: Array.isArray(service.events) ? service.events.slice() : []
37+
},
38+
39+
// A list of events that should be handled through the event hooks
40+
_hookEvents: {
41+
value: []
42+
}
43+
});
44+
45+
// `app.eventMappings` has the mapping from method name to event name
46+
Object.keys(app.eventMappings).forEach(method => {
47+
const event = app.eventMappings[method];
48+
const alreadyEmits = service._serviceEvents.indexOf(event) !== -1;
49+
50+
// Add events for known methods to _serviceEvents and _hookEvents
51+
// if the service indicated it does not send it itself yet
52+
if (typeof service[method] === 'function' && !alreadyEmits) {
53+
service._serviceEvents.push(event);
54+
service._hookEvents.push(event);
55+
}
56+
});
57+
}
58+
59+
export default function () {
60+
return function () {
61+
const app = this;
62+
63+
// Mappings from service method to event name
64+
Object.assign(app, {
65+
eventMappings: {
66+
create: 'created',
67+
update: 'updated',
68+
remove: 'removed',
69+
patch: 'patched'
70+
}
71+
});
72+
73+
// Register the event hook
74+
// `finally` hooks always run last after `error` and `after` hooks
75+
app.hooks({ finally: eventHook() });
76+
77+
// Make the app an event emitter
78+
Proto.mixin(EventEmitter.prototype, app);
79+
80+
app.mixins.push(eventMixin);
81+
};
82+
}

0 commit comments

Comments
 (0)