Skip to content

Commit

Permalink
feat: router namespaces (#338)
Browse files Browse the repository at this point in the history
* feat: router namespaces

* feat: recursive controller loading and namespacing

* feat: add namespaced controllers to namespaces and resources

* feat: add namespaced serializers to controllers

* refactor: cleanup work for namespaces implementation

* refactor: cleanup continued

* fix: do not deep freeze props by default

* refactor: continued cleanup part 2

* refactor: continued cleanup part 3

* feat: enable generating nested resources

* feat: update routes.js file when resources are generated

* fix: new flow type errors

* fix: failing builds on windows

* fix: resolve .js files from rollup alias

* fix: include extension in test app aliased imports

* fix: user '/' as key separator in resolver
  • Loading branch information
zacharygolba authored Sep 4, 2016
1 parent 1f9094e commit 1adeb90
Show file tree
Hide file tree
Showing 143 changed files with 2,627 additions and 1,383 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"Reflect": true,
"WeakMap": true,
"WeakSet": true,
"Iterable": true
"Iterable": true,
"$PropertyType": true
},
"settings": {
"flowtype": {
Expand Down
49 changes: 24 additions & 25 deletions examples/social-network/app/routes.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
export default function routes() {
this.resource('comments');
this.resource('posts');
this.resource('reactions');
this.resource('tags');
this.resource('users');

this.route('actions', {
method: 'GET',
action: 'index'
this.resource('actions', {
only: ['show', 'index']
});

this.route('actions/:id', {
method: 'GET',
action: 'show'
});
this.resource('comments');

this.route('friendships', {
method: 'POST',
action: 'create'
this.resource('friendships', {
only: ['create', 'destroy']
});

this.route('notifications', {
method: 'GET',
action: 'index'
this.resource('notifications', {
only: ['show', 'index']
});

this.route('notifications/:id', {
method: 'GET',
action: 'show'
this.resource('posts');
this.resource('reactions');
this.resource('tags');

this.resource('users', function () {
this.collection(function () {
this.post('login');
});
});

this.route('users/login', {
method: 'POST',
action: 'login'
this.namespace('admin', function () {
this.resource('actions');
this.resource('comments');
this.resource('friendships');
this.resource('notifications');
this.resource('posts');
this.resource('reactions');
this.resource('tags');
this.resource('users');
});
}
3 changes: 3 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow
import { platform } from 'os';
import { worker, isWorker } from 'cluster';

const { env: ENV } = process;
Expand All @@ -9,3 +10,5 @@ export const PORT = parseInt(ENV.PORT, 10) || 4000;
export const NODE_ENV = ENV.NODE_ENV || 'development';
export const DATABASE_URL = ENV.DATABASE_URL;
export const LUX_CONSOLE = ENV.LUX_CONSOLE || false;
export const PLATFORM = platform();
export const BACKSLASH = /\\/g;
6 changes: 6 additions & 0 deletions src/interfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ export interface Lux$Collection<T> {
delete(key: T): boolean;
values(): Iterator<T>;
}

export interface Chain<T> {
pipe<U>(handler: (value: T) => U): Chain<U>;
value(): T;
construct<U, V: Class<U>>(constructor: V): Chain<U>;
}
4 changes: 2 additions & 2 deletions src/packages/application/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class Application {
* @instance
* @readonly
*/
serializers: Map<string, Serializer>;
serializers: Map<string, Serializer<*>>;

/**
* A reference to the instance of `Router`.
Expand Down Expand Up @@ -125,4 +125,4 @@ class Application {

export default Application;

export type { Application$opts } from './interfaces';
export type { Application$opts, Application$factoryOpts } from './interfaces';
197 changes: 66 additions & 131 deletions src/packages/application/initialize.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// @flow
import { pluralize, singularize } from 'inflection';

import { LUX_CONSOLE } from '../../constants';

import Database from '../database';
import Logger from '../logger';
import Router from '../router';
import Server from '../server';
import loader from '../loader';
import { build, createLoader } from '../loader';
import { freezeProps, deepFreezeProps } from '../freezeable';

import { ControllerMissingError, SerializerMissingError } from './errors';
import { ControllerMissingError } from './errors';

import { tryCatchSync } from '../../utils/try-catch';
import createController from './utils/create-controller';
import createSerializer from './utils/create-serializer';

import type Application, { Application$opts } from './index'; // eslint-disable-line no-unused-vars, max-len

Expand All @@ -25,10 +25,9 @@ export default async function initialize<T: Application>(app: T, {
database,
server: serverConfig
}: Application$opts): Promise<T> {
const routes = loader(path, 'routes');
const models = loader(path, 'models');
const controllers = loader(path, 'controllers');
const serializers = loader(path, 'serializers');
const load = createLoader(path);
const routes = load('routes');
const models = load('models');

const logger = new Logger(logging);

Expand All @@ -42,66 +41,51 @@ export default async function initialize<T: Application>(app: T, {

port = parseInt(port, 10);

models.forEach((model, name) => {
const resource = pluralize(name);

if (!controllers.get(resource)) {
throw new ControllerMissingError(resource);
}

if (!serializers.get(resource)) {
throw new SerializerMissingError(resource);
}
const serializers = build(load('serializers'), (key, value, parent) => {
return createSerializer(value, {
key,
store,
parent
});
});

serializers.forEach((serializer, name) => {
const model = models.get(singularize(name));
models.forEach(model => {
Reflect.defineProperty(model, 'serializer', {
value: serializers.get(model.resourceName),
writable: false,
enumerable: false,
configurable: false
});
});

serializer = new serializer({
model,
const controllers = build(load('controllers'), (key, value, parent) => {
return createController(value, {
key,
store,
parent,
serializers
});

if (model) {
Reflect.defineProperty(model, 'serializer', {
value: serializer,
writable: false,
enumerable: false,
configurable: false
});
}

serializers.set(name, serializer);
});

let appController = controllers.get('application');
appController = new appController({
store,
serializers,
serializer: serializers.get('application')
controllers.forEach(controller => {
Reflect.defineProperty(controller, 'controllers', {
value: controllers,
writable: true,
enumerable: false,
configurable: false
});
});

controllers.set('application', appController);

controllers.forEach((controller, key) => {
if (key !== 'application') {
const model = tryCatchSync(() => store.modelFor(singularize(key)));
const ApplicationController = controllers.get('application');

controller = new controller({
store,
model,
controllers,
serializer: serializers.get(key),
parentController: appController
});

controllers.set(key, controller);
}
});
if (!ApplicationController) {
throw new ControllerMissingError('application');
}

const router = new Router({
routes,
controllers
controllers,
controller: ApplicationController
});

const server = new Server({
Expand All @@ -120,84 +104,35 @@ export default async function initialize<T: Application>(app: T, {
});
}

Object.defineProperties(app, {
models: {
value: models,
writable: false,
enumerable: true,
configurable: false
},

controllers: {
value: controllers,
writable: false,
enumerable: true,
configurable: false
},

serializers: {
value: serializers,
writable: false,
enumerable: true,
configurable: false
},

logger: {
value: logger,
writable: false,
enumerable: true,
configurable: false
},

path: {
value: path,
writable: false,
enumerable: false,
configurable: false
},

port: {
value: port,
writable: false,
enumerable: false,
configurable: false
},

store: {
value: store,
writable: false,
enumerable: false,
configurable: false
},

router: {
value: router,
writable: false,
enumerable: false,
configurable: false
},

server: {
value: server,
writable: false,
enumerable: false,
configurable: false
}
Object.assign(app, {
logger,
models,
controllers,
serializers
});

Object.freeze(app);
Object.freeze(store);
Object.freeze(logger);
Object.freeze(router);
Object.freeze(server);
deepFreezeProps(app, true,
'logger',
'models',
'controllers',
'serializers'
);

models.forEach(Object.freeze);
controllers.forEach(Object.freeze);
serializers.forEach(Object.freeze);
Object.assign(app, {
path,
port,
store,
router,
server
});

models.freeze();
controllers.freeze();
serializers.freeze();
freezeProps(app, false,
'path',
'port',
'store',
'router',
'server'
);

return app;
return Object.freeze(app);
}
10 changes: 9 additions & 1 deletion src/packages/application/interfaces.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
// @flow
import type { Config } from '../config';
import type { Database$config } from '../database';
import type Database, { Database$config } from '../database';
import type Controller from '../controller';
import type Serializer from '../serializer';

export type Application$opts = Config & {
path: string;
port: number;
database: Database$config;
};

export type Application$factoryOpts<T: Controller | Serializer<*>> = {
key: string;
store: Database;
parent: ?T;
};
Loading

0 comments on commit 1adeb90

Please sign in to comment.