router.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. 'use strict';
  2. const { EventEmitter } = require('events');
  3. const Promise = require('bluebird');
  4. const Stream = require('stream');
  5. const { Readable } = Stream;
  6. class RouteStream extends Readable {
  7. constructor(data) {
  8. super({ objectMode: true });
  9. this._data = data.data;
  10. this._ended = false;
  11. this.modified = data.modified;
  12. }
  13. // Assume we only accept Buffer, plain object, or string
  14. _toBuffer(data) {
  15. if (data instanceof Buffer) {
  16. return data;
  17. }
  18. if (typeof data === 'object') {
  19. data = JSON.stringify(data);
  20. }
  21. if (typeof data === 'string') {
  22. return Buffer.from(data); // Assume string is UTF-8 encoded string
  23. }
  24. return null;
  25. }
  26. _read() {
  27. const data = this._data;
  28. if (typeof data !== 'function') {
  29. const bufferData = this._toBuffer(data);
  30. if (bufferData) {
  31. this.push(bufferData);
  32. }
  33. this.push(null);
  34. return;
  35. }
  36. // Don't read it twice!
  37. if (this._ended) return false;
  38. this._ended = true;
  39. data().then(data => {
  40. if (data instanceof Stream && data.readable) {
  41. data.on('data', d => {
  42. this.push(d);
  43. });
  44. data.on('end', () => {
  45. this.push(null);
  46. });
  47. data.on('error', err => {
  48. this.emit('error', err);
  49. });
  50. } else {
  51. const bufferData = this._toBuffer(data);
  52. if (bufferData) {
  53. this.push(bufferData);
  54. }
  55. this.push(null);
  56. }
  57. }).catch(err => {
  58. this.emit('error', err);
  59. this.push(null);
  60. });
  61. }
  62. }
  63. const _format = path => {
  64. path = path || '';
  65. if (typeof path !== 'string') throw new TypeError('path must be a string!');
  66. path = path
  67. .replace(/^\/+/, '') // Remove prefixed slashes
  68. .replace(/\\/g, '/') // Replaces all backslashes
  69. .replace(/\?.*$/, ''); // Remove query string
  70. // Appends `index.html` to the path with trailing slash
  71. if (!path || path.endsWith('/')) {
  72. path += 'index.html';
  73. }
  74. return path;
  75. };
  76. class Router extends EventEmitter {
  77. constructor() {
  78. super();
  79. this.routes = {};
  80. }
  81. list() {
  82. const { routes } = this;
  83. return Object.keys(routes).filter(key => routes[key]);
  84. }
  85. format(path) {
  86. return _format(path);
  87. }
  88. get(path) {
  89. if (typeof path !== 'string') throw new TypeError('path must be a string!');
  90. const data = this.routes[this.format(path)];
  91. if (data == null) return;
  92. return new RouteStream(data);
  93. }
  94. isModified(path) {
  95. if (typeof path !== 'string') throw new TypeError('path must be a string!');
  96. const data = this.routes[this.format(path)];
  97. return data ? data.modified : false;
  98. }
  99. set(path, data) {
  100. if (typeof path !== 'string') throw new TypeError('path must be a string!');
  101. if (data == null) throw new TypeError('data is required!');
  102. let obj;
  103. if (typeof data === 'object' && data.data != null) {
  104. obj = data;
  105. } else {
  106. obj = {
  107. data,
  108. modified: true
  109. };
  110. }
  111. if (typeof obj.data === 'function') {
  112. if (obj.data.length) {
  113. obj.data = Promise.promisify(obj.data);
  114. } else {
  115. obj.data = Promise.method(obj.data);
  116. }
  117. }
  118. path = this.format(path);
  119. this.routes[path] = {
  120. data: obj.data,
  121. modified: obj.modified == null ? true : obj.modified
  122. };
  123. this.emit('update', path);
  124. return this;
  125. }
  126. remove(path) {
  127. if (typeof path !== 'string') throw new TypeError('path must be a string!');
  128. path = this.format(path);
  129. this.routes[path] = null;
  130. this.emit('remove', path);
  131. return this;
  132. }
  133. }
  134. module.exports = Router;