123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- 'use strict';
- const { EventEmitter } = require('events');
- const Promise = require('bluebird');
- const Stream = require('stream');
- const { Readable } = Stream;
- class RouteStream extends Readable {
- constructor(data) {
- super({ objectMode: true });
- this._data = data.data;
- this._ended = false;
- this.modified = data.modified;
- }
- // Assume we only accept Buffer, plain object, or string
- _toBuffer(data) {
- if (data instanceof Buffer) {
- return data;
- }
- if (typeof data === 'object') {
- data = JSON.stringify(data);
- }
- if (typeof data === 'string') {
- return Buffer.from(data); // Assume string is UTF-8 encoded string
- }
- return null;
- }
- _read() {
- const data = this._data;
- if (typeof data !== 'function') {
- const bufferData = this._toBuffer(data);
- if (bufferData) {
- this.push(bufferData);
- }
- this.push(null);
- return;
- }
- // Don't read it twice!
- if (this._ended) return false;
- this._ended = true;
- data().then(data => {
- if (data instanceof Stream && data.readable) {
- data.on('data', d => {
- this.push(d);
- });
- data.on('end', () => {
- this.push(null);
- });
- data.on('error', err => {
- this.emit('error', err);
- });
- } else {
- const bufferData = this._toBuffer(data);
- if (bufferData) {
- this.push(bufferData);
- }
- this.push(null);
- }
- }).catch(err => {
- this.emit('error', err);
- this.push(null);
- });
- }
- }
- const _format = path => {
- path = path || '';
- if (typeof path !== 'string') throw new TypeError('path must be a string!');
- path = path
- .replace(/^\/+/, '') // Remove prefixed slashes
- .replace(/\\/g, '/') // Replaces all backslashes
- .replace(/\?.*$/, ''); // Remove query string
- // Appends `index.html` to the path with trailing slash
- if (!path || path.endsWith('/')) {
- path += 'index.html';
- }
- return path;
- };
- class Router extends EventEmitter {
- constructor() {
- super();
- this.routes = {};
- }
- list() {
- const { routes } = this;
- return Object.keys(routes).filter(key => routes[key]);
- }
- format(path) {
- return _format(path);
- }
- get(path) {
- if (typeof path !== 'string') throw new TypeError('path must be a string!');
- const data = this.routes[this.format(path)];
- if (data == null) return;
- return new RouteStream(data);
- }
- isModified(path) {
- if (typeof path !== 'string') throw new TypeError('path must be a string!');
- const data = this.routes[this.format(path)];
- return data ? data.modified : false;
- }
- set(path, data) {
- if (typeof path !== 'string') throw new TypeError('path must be a string!');
- if (data == null) throw new TypeError('data is required!');
- let obj;
- if (typeof data === 'object' && data.data != null) {
- obj = data;
- } else {
- obj = {
- data,
- modified: true
- };
- }
- if (typeof obj.data === 'function') {
- if (obj.data.length) {
- obj.data = Promise.promisify(obj.data);
- } else {
- obj.data = Promise.method(obj.data);
- }
- }
- path = this.format(path);
- this.routes[path] = {
- data: obj.data,
- modified: obj.modified == null ? true : obj.modified
- };
- this.emit('update', path);
- return this;
- }
- remove(path) {
- if (typeof path !== 'string') throw new TypeError('path must be a string!');
- path = this.format(path);
- this.routes[path] = null;
- this.emit('remove', path);
- return this;
- }
- }
- module.exports = Router;
|