UNPKG

memserver

Version:

in-memory database/ORM and http mock server you can run in-browser and node environments. Built for large frontend teams, fast tests and rapid prototyping

1,345 lines (1,212 loc) 167 kB
var MEMSERVER = (function () { 'use strict'; var models = { }; var fixtures = { }; function initializer(Models) {} function server(Models) { } var global$1 = (typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); // shim for using process in browser // based off https://github.com/defunctzombie/node-process/blob/master/browser.js function defaultSetTimout() { throw new Error('setTimeout has not been defined'); } function defaultClearTimeout () { throw new Error('clearTimeout has not been defined'); } var cachedSetTimeout = defaultSetTimout; var cachedClearTimeout = defaultClearTimeout; if (typeof global$1.setTimeout === 'function') { cachedSetTimeout = setTimeout; } if (typeof global$1.clearTimeout === 'function') { cachedClearTimeout = clearTimeout; } function runTimeout(fun) { if (cachedSetTimeout === setTimeout) { //normal enviroments in sane situations return setTimeout(fun, 0); } // if setTimeout wasn't available but was latter defined if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { cachedSetTimeout = setTimeout; return setTimeout(fun, 0); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedSetTimeout(fun, 0); } catch(e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedSetTimeout.call(null, fun, 0); } catch(e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error return cachedSetTimeout.call(this, fun, 0); } } } function runClearTimeout(marker) { if (cachedClearTimeout === clearTimeout) { //normal enviroments in sane situations return clearTimeout(marker); } // if clearTimeout wasn't available but was latter defined if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { cachedClearTimeout = clearTimeout; return clearTimeout(marker); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedClearTimeout(marker); } catch (e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedClearTimeout.call(null, marker); } catch (e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. // Some versions of I.E. have different rules for clearTimeout vs setTimeout return cachedClearTimeout.call(this, marker); } } } var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { if (!draining || !currentQueue) { return; } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = runTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; runClearTimeout(timeout); } function nextTick(fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { runTimeout(drainQueue); } } // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; var title = 'browser'; var platform = 'browser'; var browser = true; var env = {}; var argv = []; var version = ''; // empty string to avoid regexp issues var versions = {}; var release = {}; var config = {}; function noop() {} var on = noop; var addListener = noop; var once = noop; var off = noop; var removeListener = noop; var removeAllListeners = noop; var emit = noop; function binding(name) { throw new Error('process.binding is not supported'); } function cwd () { return '/' } function chdir (dir) { throw new Error('process.chdir is not supported'); }function umask() { return 0; } // from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js var performance = global$1.performance || {}; var performanceNow = performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || function(){ return (new Date()).getTime() }; // generate timestamp or delta // see http://nodejs.org/api/process.html#process_process_hrtime function hrtime(previousTimestamp){ var clocktime = performanceNow.call(performance)*1e-3; var seconds = Math.floor(clocktime); var nanoseconds = Math.floor((clocktime%1)*1e9); if (previousTimestamp) { seconds = seconds - previousTimestamp[0]; nanoseconds = nanoseconds - previousTimestamp[1]; if (nanoseconds<0) { seconds--; nanoseconds += 1e9; } } return [seconds,nanoseconds] } var startTime = new Date(); function uptime() { var currentTime = new Date(); var dif = currentTime - startTime; return dif / 1000; } var process = { nextTick: nextTick, title: title, browser: browser, env: env, argv: argv, version: version, versions: versions, on: on, addListener: addListener, once: once, off: off, removeListener: removeListener, removeAllListeners: removeAllListeners, emit: emit, binding: binding, cwd: cwd, chdir: chdir, umask: umask, hrtime: hrtime, platform: platform, release: release, config: config, uptime: uptime }; var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var symbols = createCommonjsModule(function (module) { const isWindows = process.platform === 'win32'; const isLinux = process.platform === 'linux'; const windows = { bullet: '•', check: '√', cross: '×', ellipsis: '...', heart: '❤', info: 'i', line: '─', middot: '·', minus: '-', plus: '+', question: '?', questionSmall: '﹖', pointer: '>', pointerSmall: '»', warning: '‼' }; const other = { ballotCross: '✘', bullet: '•', check: '✔', cross: '✖', ellipsis: '…', heart: '❤', info: 'ℹ', line: '─', middot: '·', minus: '-', plus: '+', question: '?', questionFull: '?', questionSmall: '﹖', pointer: isLinux ? '▸' : '❯', pointerSmall: isLinux ? '‣' : '›', warning: '⚠' }; module.exports = isWindows ? windows : other; Reflect.defineProperty(module.exports, 'windows', { enumerable: false, value: windows }); Reflect.defineProperty(module.exports, 'other', { enumerable: false, value: other }); }); const colors = { enabled: true, visible: true, styles: {}, keys: {} }; if ('FORCE_COLOR' in process.env) { colors.enabled = process.env.FORCE_COLOR !== '0'; } const ansi = style => { style.open = `\u001b[${style.codes[0]}m`; style.close = `\u001b[${style.codes[1]}m`; style.regex = new RegExp(`\\u001b\\[${style.codes[1]}m`, 'g'); return style; }; const wrap = (style, str, nl) => { let { open, close, regex } = style; str = open + (str.includes(close) ? str.replace(regex, close + open) : str) + close; // see https://github.com/chalk/chalk/pull/92, thanks to the // chalk contributors for this fix. However, we've confirmed that // this issue is also present in Windows terminals return nl ? str.replace(/\r?\n/g, `${close}$&${open}`) : str; }; const style = (input, stack) => { if (input === '' || input == null) return ''; if (colors.enabled === false) return input; if (colors.visible === false) return ''; let str = '' + input; let nl = str.includes('\n'); let n = stack.length; while (n-- > 0) str = wrap(colors.styles[stack[n]], str, nl); return str; }; const define = (name, codes, type) => { colors.styles[name] = ansi({ name, codes }); let t = colors.keys[type] || (colors.keys[type] = []); t.push(name); Reflect.defineProperty(colors, name, { get() { let color = input => style(input, color.stack); Reflect.setPrototypeOf(color, colors); color.stack = this.stack ? this.stack.concat(name) : [name]; return color; } }); }; define('reset', [0, 0], 'modifier'); define('bold', [1, 22], 'modifier'); define('dim', [2, 22], 'modifier'); define('italic', [3, 23], 'modifier'); define('underline', [4, 24], 'modifier'); define('inverse', [7, 27], 'modifier'); define('hidden', [8, 28], 'modifier'); define('strikethrough', [9, 29], 'modifier'); define('black', [30, 39], 'color'); define('red', [31, 39], 'color'); define('green', [32, 39], 'color'); define('yellow', [33, 39], 'color'); define('blue', [34, 39], 'color'); define('magenta', [35, 39], 'color'); define('cyan', [36, 39], 'color'); define('white', [37, 39], 'color'); define('gray', [90, 39], 'color'); define('grey', [90, 39], 'color'); define('bgBlack', [40, 49], 'bg'); define('bgRed', [41, 49], 'bg'); define('bgGreen', [42, 49], 'bg'); define('bgYellow', [43, 49], 'bg'); define('bgBlue', [44, 49], 'bg'); define('bgMagenta', [45, 49], 'bg'); define('bgCyan', [46, 49], 'bg'); define('bgWhite', [47, 49], 'bg'); define('blackBright', [90, 39], 'bright'); define('redBright', [91, 39], 'bright'); define('greenBright', [92, 39], 'bright'); define('yellowBright', [93, 39], 'bright'); define('blueBright', [94, 39], 'bright'); define('magentaBright', [95, 39], 'bright'); define('cyanBright', [96, 39], 'bright'); define('whiteBright', [97, 39], 'bright'); define('bgBlackBright', [100, 49], 'bgBright'); define('bgRedBright', [101, 49], 'bgBright'); define('bgGreenBright', [102, 49], 'bgBright'); define('bgYellowBright', [103, 49], 'bgBright'); define('bgBlueBright', [104, 49], 'bgBright'); define('bgMagentaBright', [105, 49], 'bgBright'); define('bgCyanBright', [106, 49], 'bgBright'); define('bgWhiteBright', [107, 49], 'bgBright'); /* eslint-disable no-control-regex */ const re = colors.ansiRegex = /\u001b\[\d+m/gm; colors.hasColor = colors.hasAnsi = str => !!str && typeof str === 'string' && re.test(str); colors.unstyle = str => typeof str === 'string' ? str.replace(re, '') : str; colors.none = colors.clear = colors.noop = str => str; // no-op, for programmatic usage colors.stripColor = colors.unstyle; colors.symbols = symbols; colors.define = define; var ansiColors = colors; function primaryKeyTypeSafetyCheck(targetPrimaryKeyType, primaryKey, modelName) { const primaryKeyType = typeof primaryKey; if (targetPrimaryKeyType === 'id' && (primaryKeyType !== 'number')) { throw new Error(ansiColors.red(`[MemServer] ${modelName} model primaryKey type is 'id'. Instead you've tried to enter id: ${primaryKey} with ${primaryKeyType} type`)); } else if (targetPrimaryKeyType === 'uuid' && (primaryKeyType !== 'string')) { throw new Error(ansiColors.red(`[MemServer] ${modelName} model primaryKey type is 'uuid'. Instead you've tried to enter uuid: ${primaryKey} with ${primaryKeyType} type`)); } return targetPrimaryKeyType; } var fake_xml_http_request = createCommonjsModule(function (module, exports) { (function (global, factory) { module.exports = factory(); }(commonjsGlobal, function () { /** * Minimal Event interface implementation * * Original implementation by Sven Fuchs: https://gist.github.com/995028 * Modifications and tests by Christian Johansen. * * @author Sven Fuchs (svenfuchs@artweb-design.de) * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2011 Sven Fuchs, Christian Johansen */ var _Event = function Event(type, bubbles, cancelable, target) { this.type = type; this.bubbles = bubbles; this.cancelable = cancelable; this.target = target; }; _Event.prototype = { stopPropagation: function () {}, preventDefault: function () { this.defaultPrevented = true; } }; /* Used to set the statusText property of an xhr object */ var httpStatusCodes = { 100: "Continue", 101: "Switching Protocols", 200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 300: "Multiple Choice", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 307: "Temporary Redirect", 400: "Bad Request", 401: "Unauthorized", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Request Entity Too Large", 414: "Request-URI Too Long", 415: "Unsupported Media Type", 416: "Requested Range Not Satisfiable", 417: "Expectation Failed", 422: "Unprocessable Entity", 500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported" }; /* Cross-browser XML parsing. Used to turn XML responses into Document objects Borrowed from JSpec */ function parseXML(text) { var xmlDoc; if (typeof DOMParser != "undefined") { var parser = new DOMParser(); xmlDoc = parser.parseFromString(text, "text/xml"); } else { xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = "false"; xmlDoc.loadXML(text); } return xmlDoc; } /* Without mocking, the native XMLHttpRequest object will throw an error when attempting to set these headers. We match this behavior. */ var unsafeHeaders = { "Accept-Charset": true, "Accept-Encoding": true, "Connection": true, "Content-Length": true, "Cookie": true, "Cookie2": true, "Content-Transfer-Encoding": true, "Date": true, "Expect": true, "Host": true, "Keep-Alive": true, "Referer": true, "TE": true, "Trailer": true, "Transfer-Encoding": true, "Upgrade": true, "User-Agent": true, "Via": true }; /* Adds an "event" onto the fake xhr object that just calls the same-named method. This is in case a library adds callbacks for these events. */ function _addEventListener(eventName, xhr){ xhr.addEventListener(eventName, function (event) { var listener = xhr["on" + eventName]; if (listener && typeof listener == "function") { listener.call(event.target, event); } }); } function EventedObject() { this._eventListeners = {}; var events = ["loadstart", "progress", "load", "abort", "loadend"]; for (var i = events.length - 1; i >= 0; i--) { _addEventListener(events[i], this); } } EventedObject.prototype = { /* Duplicates the behavior of native XMLHttpRequest's addEventListener function */ addEventListener: function addEventListener(event, listener) { this._eventListeners[event] = this._eventListeners[event] || []; this._eventListeners[event].push(listener); }, /* Duplicates the behavior of native XMLHttpRequest's removeEventListener function */ removeEventListener: function removeEventListener(event, listener) { var listeners = this._eventListeners[event] || []; for (var i = 0, l = listeners.length; i < l; ++i) { if (listeners[i] == listener) { return listeners.splice(i, 1); } } }, /* Duplicates the behavior of native XMLHttpRequest's dispatchEvent function */ dispatchEvent: function dispatchEvent(event) { var type = event.type; var listeners = this._eventListeners[type] || []; for (var i = 0; i < listeners.length; i++) { if (typeof listeners[i] == "function") { listeners[i].call(this, event); } else { listeners[i].handleEvent(event); } } return !!event.defaultPrevented; }, /* Triggers an `onprogress` event with the given parameters. */ _progress: function _progress(lengthComputable, loaded, total) { var event = new _Event('progress'); event.target = this; event.lengthComputable = lengthComputable; event.loaded = loaded; event.total = total; this.dispatchEvent(event); } }; /* Constructor for a fake window.XMLHttpRequest */ function FakeXMLHttpRequest() { EventedObject.call(this); this.readyState = FakeXMLHttpRequest.UNSENT; this.requestHeaders = {}; this.requestBody = null; this.status = 0; this.statusText = ""; this.upload = new EventedObject(); } FakeXMLHttpRequest.prototype = new EventedObject(); // These status codes are available on the native XMLHttpRequest // object, so we match that here in case a library is relying on them. FakeXMLHttpRequest.UNSENT = 0; FakeXMLHttpRequest.OPENED = 1; FakeXMLHttpRequest.HEADERS_RECEIVED = 2; FakeXMLHttpRequest.LOADING = 3; FakeXMLHttpRequest.DONE = 4; var FakeXMLHttpRequestProto = { UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4, async: true, withCredentials: false, /* Duplicates the behavior of native XMLHttpRequest's open function */ open: function open(method, url, async, username, password) { this.method = method; this.url = url; this.async = typeof async == "boolean" ? async : true; this.username = username; this.password = password; this.responseText = null; this.responseXML = null; this.requestHeaders = {}; this.sendFlag = false; this._readyStateChange(FakeXMLHttpRequest.OPENED); }, /* Duplicates the behavior of native XMLHttpRequest's setRequestHeader function */ setRequestHeader: function setRequestHeader(header, value) { verifyState(this); if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { throw new Error("Refused to set unsafe header \"" + header + "\""); } if (this.requestHeaders[header]) { this.requestHeaders[header] += "," + value; } else { this.requestHeaders[header] = value; } }, /* Duplicates the behavior of native XMLHttpRequest's send function */ send: function send(data) { verifyState(this); if (!/^(get|head)$/i.test(this.method)) { var hasContentTypeHeader = false; Object.keys(this.requestHeaders).forEach(function (key) { if (key.toLowerCase() === 'content-type') { hasContentTypeHeader = true; } }); if (!hasContentTypeHeader && !(data || '').toString().match('FormData')) { this.requestHeaders["Content-Type"] = "text/plain;charset=UTF-8"; } this.requestBody = data; } this.errorFlag = false; this.sendFlag = this.async; this._readyStateChange(FakeXMLHttpRequest.OPENED); if (typeof this.onSend == "function") { this.onSend(this); } this.dispatchEvent(new _Event("loadstart", false, false, this)); }, /* Duplicates the behavior of native XMLHttpRequest's abort function */ abort: function abort() { this.aborted = true; this.responseText = null; this.errorFlag = true; this.requestHeaders = {}; if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) { this._readyStateChange(FakeXMLHttpRequest.DONE); this.sendFlag = false; } this.readyState = FakeXMLHttpRequest.UNSENT; this.dispatchEvent(new _Event("abort", false, false, this)); if (typeof this.onerror === "function") { this.onerror(); } }, /* Duplicates the behavior of native XMLHttpRequest's getResponseHeader function */ getResponseHeader: function getResponseHeader(header) { if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { return null; } if (/^Set-Cookie2?$/i.test(header)) { return null; } header = header.toLowerCase(); for (var h in this.responseHeaders) { if (h.toLowerCase() == header) { return this.responseHeaders[h]; } } return null; }, /* Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function */ getAllResponseHeaders: function getAllResponseHeaders() { if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { return ""; } var headers = ""; for (var header in this.responseHeaders) { if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) { headers += header + ": " + this.responseHeaders[header] + "\r\n"; } } return headers; }, /* Duplicates the behavior of native XMLHttpRequest's overrideMimeType function */ overrideMimeType: function overrideMimeType(mimeType) { if (typeof mimeType === "string") { this.forceMimeType = mimeType.toLowerCase(); } }, /* Places a FakeXMLHttpRequest object into the passed state. */ _readyStateChange: function _readyStateChange(state) { this.readyState = state; if (typeof this.onreadystatechange == "function") { this.onreadystatechange(new _Event("readystatechange")); } this.dispatchEvent(new _Event("readystatechange")); if (this.readyState == FakeXMLHttpRequest.DONE) { this.dispatchEvent(new _Event("load", false, false, this)); this.dispatchEvent(new _Event("loadend", false, false, this)); } }, /* Sets the FakeXMLHttpRequest object's response headers and places the object into readyState 2 */ _setResponseHeaders: function _setResponseHeaders(headers) { this.responseHeaders = {}; for (var header in headers) { if (headers.hasOwnProperty(header)) { this.responseHeaders[header] = headers[header]; } } if (this.forceMimeType) { this.responseHeaders['Content-Type'] = this.forceMimeType; } if (this.async) { this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); } else { this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; } }, /* Sets the FakeXMLHttpRequest object's response body and if body text is XML, sets responseXML to parsed document object */ _setResponseBody: function _setResponseBody(body) { verifyRequestSent(this); verifyHeadersReceived(this); verifyResponseBodyType(body); var chunkSize = this.chunkSize || 10; var index = 0; this.responseText = ""; do { if (this.async) { this._readyStateChange(FakeXMLHttpRequest.LOADING); } this.responseText += body.substring(index, index + chunkSize); index += chunkSize; } while (index < body.length); var type = this.getResponseHeader("Content-Type"); if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { try { this.responseXML = parseXML(this.responseText); } catch (e) { // Unable to parse XML - no biggie } } if (this.async) { this._readyStateChange(FakeXMLHttpRequest.DONE); } else { this.readyState = FakeXMLHttpRequest.DONE; } }, /* Forces a response on to the FakeXMLHttpRequest object. This is the public API for faking responses. This function takes a number status, headers object, and string body: ``` xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.") ``` */ respond: function respond(status, headers, body) { this._setResponseHeaders(headers || {}); this.status = typeof status == "number" ? status : 200; this.statusText = httpStatusCodes[this.status]; this._setResponseBody(body || ""); } }; for (var property in FakeXMLHttpRequestProto) { FakeXMLHttpRequest.prototype[property] = FakeXMLHttpRequestProto[property]; } function verifyState(xhr) { if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { throw new Error("INVALID_STATE_ERR"); } if (xhr.sendFlag) { throw new Error("INVALID_STATE_ERR"); } } function verifyRequestSent(xhr) { if (xhr.readyState == FakeXMLHttpRequest.DONE) { throw new Error("Request done"); } } function verifyHeadersReceived(xhr) { if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { throw new Error("No headers received"); } } function verifyResponseBodyType(body) { if (typeof body != "string") { var error = new Error("Attempted to respond to fake XMLHttpRequest with " + body + ", which is not a string."); error.name = "InvalidBodyException"; throw error; } } var fake_xml_http_request = FakeXMLHttpRequest; return fake_xml_http_request; })); }); var createObject = Object.create; function createMap() { var map = createObject(null); map["__"] = undefined; delete map["__"]; return map; } var Target = function Target(path, matcher, delegate) { this.path = path; this.matcher = matcher; this.delegate = delegate; }; Target.prototype.to = function to (target, callback) { var delegate = this.delegate; if (delegate && delegate.willAddRoute) { target = delegate.willAddRoute(this.matcher.target, target); } this.matcher.add(this.path, target); if (callback) { if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); } this.matcher.addChild(this.path, target, callback, this.delegate); } }; var Matcher = function Matcher(target) { this.routes = createMap(); this.children = createMap(); this.target = target; }; Matcher.prototype.add = function add (path, target) { this.routes[path] = target; }; Matcher.prototype.addChild = function addChild (path, target, callback, delegate) { var matcher = new Matcher(target); this.children[path] = matcher; var match = generateMatch(path, matcher, delegate); if (delegate && delegate.contextEntered) { delegate.contextEntered(target, match); } callback(match); }; function generateMatch(startingPath, matcher, delegate) { function match(path, callback) { var fullPath = startingPath + path; if (callback) { callback(generateMatch(fullPath, matcher, delegate)); } else { return new Target(fullPath, matcher, delegate); } } return match; } function addRoute(routeArray, path, handler) { var len = 0; for (var i = 0; i < routeArray.length; i++) { len += routeArray[i].path.length; } path = path.substr(len); var route = { path: path, handler: handler }; routeArray.push(route); } function eachRoute(baseRoute, matcher, callback, binding) { var routes = matcher.routes; var paths = Object.keys(routes); for (var i = 0; i < paths.length; i++) { var path = paths[i]; var routeArray = baseRoute.slice(); addRoute(routeArray, path, routes[path]); var nested = matcher.children[path]; if (nested) { eachRoute(routeArray, nested, callback, binding); } else { callback.call(binding, routeArray); } } } var map = function (callback, addRouteCallback) { var matcher = new Matcher(); callback(generateMatch("", matcher, this.delegate)); eachRoute([], matcher, function (routes) { if (addRouteCallback) { addRouteCallback(this, routes); } else { this.add(routes); } }, this); }; // Normalizes percent-encoded values in `path` to upper-case and decodes percent-encoded // values that are not reserved (i.e., unicode characters, emoji, etc). The reserved // chars are "/" and "%". // Safe to call multiple times on the same path. // Normalizes percent-encoded values in `path` to upper-case and decodes percent-encoded function normalizePath(path) { return path.split("/") .map(normalizeSegment) .join("/"); } // We want to ensure the characters "%" and "/" remain in percent-encoded // form when normalizing paths, so replace them with their encoded form after // decoding the rest of the path var SEGMENT_RESERVED_CHARS = /%|\//g; function normalizeSegment(segment) { if (segment.length < 3 || segment.indexOf("%") === -1) { return segment; } return decodeURIComponent(segment).replace(SEGMENT_RESERVED_CHARS, encodeURIComponent); } // We do not want to encode these characters when generating dynamic path segments // See https://tools.ietf.org/html/rfc3986#section-3.3 // sub-delims: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" // others allowed by RFC 3986: ":", "@" // // First encode the entire path segment, then decode any of the encoded special chars. // // The chars "!", "'", "(", ")", "*" do not get changed by `encodeURIComponent`, // so the possible encoded chars are: // ['%24', '%26', '%2B', '%2C', '%3B', '%3D', '%3A', '%40']. var PATH_SEGMENT_ENCODINGS = /%(?:2(?:4|6|B|C)|3(?:B|D|A)|40)/g; function encodePathSegment(str) { return encodeURIComponent(str).replace(PATH_SEGMENT_ENCODINGS, decodeURIComponent); } var escapeRegex = /(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\)/g; var isArray = Array.isArray; var hasOwnProperty = Object.prototype.hasOwnProperty; function getParam(params, key) { if (typeof params !== "object" || params === null) { throw new Error("You must pass an object as the second argument to `generate`."); } if (!hasOwnProperty.call(params, key)) { throw new Error("You must provide param `" + key + "` to `generate`."); } var value = params[key]; var str = typeof value === "string" ? value : "" + value; if (str.length === 0) { throw new Error("You must provide a param `" + key + "`."); } return str; } var eachChar = []; eachChar[0 /* Static */] = function (segment, currentState) { var state = currentState; var value = segment.value; for (var i = 0; i < value.length; i++) { var ch = value.charCodeAt(i); state = state.put(ch, false, false); } return state; }; eachChar[1 /* Dynamic */] = function (_, currentState) { return currentState.put(47 /* SLASH */, true, true); }; eachChar[2 /* Star */] = function (_, currentState) { return currentState.put(-1 /* ANY */, false, true); }; eachChar[4 /* Epsilon */] = function (_, currentState) { return currentState; }; var regex = []; regex[0 /* Static */] = function (segment) { return segment.value.replace(escapeRegex, "\\$1"); }; regex[1 /* Dynamic */] = function () { return "([^/]+)"; }; regex[2 /* Star */] = function () { return "(.+)"; }; regex[4 /* Epsilon */] = function () { return ""; }; var generate = []; generate[0 /* Static */] = function (segment) { return segment.value; }; generate[1 /* Dynamic */] = function (segment, params) { var value = getParam(params, segment.value); if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) { return encodePathSegment(value); } else { return value; } }; generate[2 /* Star */] = function (segment, params) { return getParam(params, segment.value); }; generate[4 /* Epsilon */] = function () { return ""; }; var EmptyObject = Object.freeze({}); var EmptyArray = Object.freeze([]); // The `names` will be populated with the paramter name for each dynamic/star // segment. `shouldDecodes` will be populated with a boolean for each dyanamic/star // segment, indicating whether it should be decoded during recognition. function parse(segments, route, types) { // normalize route as not starting with a "/". Recognition will // also normalize. if (route.length > 0 && route.charCodeAt(0) === 47 /* SLASH */) { route = route.substr(1); } var parts = route.split("/"); var names = undefined; var shouldDecodes = undefined; for (var i = 0; i < parts.length; i++) { var part = parts[i]; var flags = 0; var type = 0; if (part === "") { type = 4 /* Epsilon */; } else if (part.charCodeAt(0) === 58 /* COLON */) { type = 1 /* Dynamic */; } else if (part.charCodeAt(0) === 42 /* STAR */) { type = 2 /* Star */; } else { type = 0 /* Static */; } flags = 2 << type; if (flags & 12 /* Named */) { part = part.slice(1); names = names || []; names.push(part); shouldDecodes = shouldDecodes || []; shouldDecodes.push((flags & 4 /* Decoded */) !== 0); } if (flags & 14 /* Counted */) { types[type]++; } segments.push({ type: type, value: normalizeSegment(part) }); } return { names: names || EmptyArray, shouldDecodes: shouldDecodes || EmptyArray, }; } function isEqualCharSpec(spec, char, negate) { return spec.char === char && spec.negate === negate; } // A State has a character specification and (`charSpec`) and a list of possible // subsequent states (`nextStates`). // // If a State is an accepting state, it will also have several additional // properties: // // * `regex`: A regular expression that is used to extract parameters from paths // that reached this accepting state. // * `handlers`: Information on how to convert the list of captures into calls // to registered handlers with the specified parameters // * `types`: How many static, dynamic or star segments in this route. Used to // decide which route to use if multiple registered routes match a path. // // Currently, State is implemented naively by looping over `nextStates` and // comparing a character specification against a character. A more efficient // implementation would use a hash of keys pointing at one or more next states. var State = function State(states, id, char, negate, repeat) { this.states = states; this.id = id; this.char = char; this.negate = negate; this.nextStates = repeat ? id : null; this.pattern = ""; this._regex = undefined; this.handlers = undefined; this.types = undefined; }; State.prototype.regex = function regex$1 () { if (!this._regex) { this._regex = new RegExp(this.pattern); } return this._regex; }; State.prototype.get = function get (char, negate) { var this$1 = this; var nextStates = this.nextStates; if (nextStates === null) { return; } if (isArray(nextStates)) { for (var i = 0; i < nextStates.length; i++) { var child = this$1.states[nextStates[i]]; if (isEqualCharSpec(child, char, negate)) { return child; } } } else { var child$1 = this.states[nextStates]; if (isEqualCharSpec(child$1, char, negate)) { return child$1; } } }; State.prototype.put = function put (char, negate, repeat) { var state; // If the character specification already exists in a child of the current // state, just return that state. if (state = this.get(char, negate)) { return state; } // Make a new state for the character spec var states = this.states; state = new State(states, states.length, char, negate, repeat); states[states.length] = state; // Insert the new state as a child of the current state if (this.nextStates == null) { this.nextStates = state.id; } else if (isArray(this.nextStates)) { this.nextStates.push(state.id); } else { this.nextStates = [this.nextStates, state.id]; } // Return the new state return state; }; // Find a list of child states matching the next character State.prototype.match = function match (ch) { var this$1 = this; var nextStates = this.nextStates; if (!nextStates) { return []; } var returned = []; if (isArray(nextStates)) { for (var i = 0; i < nextStates.length; i++) { var child = this$1.states[nextStates[i]]; if (isMatch(child, ch)) { returned.push(child); } } } else { var child$1 = this.states[nextStates]; if (isMatch(child$1, ch)) { returned.push(child$1); } } return returned; }; function isMatch(spec, char) { return spec.negate ? spec.char !== char && spec.char !== -1 /* ANY */ : spec.char === char || spec.char === -1 /* ANY */; } // This is a somewhat naive strategy, but should work in a lot of cases // A better strategy would properly resolve /posts/:id/new and /posts/edit/:id. // // This strategy generally prefers more static and less dynamic matching. // Specifically, it // // * prefers fewer stars to more, then // * prefers using stars for less of the match to more, then // * prefers fewer dynamic segments to more, then // * prefers more static segments to more function sortSolutions(states) { return states.sort(function (a, b) { var ref = a.types || [0, 0, 0]; var astatics = ref[0]; var adynamics = ref[1]; var astars = ref[2]; var ref$1 = b.types || [0, 0, 0]; var bstatics = ref$1[0]; var bdynamics = ref$1[1]; var bstars = ref$1[2]; if (astars !== bstars) { return astars - bstars; } if (astars) { if (astatics !== bstatics) { return bstatics - astatics; } if (adynamics !== bdynamics) { return bdynamics - adynamics; } } if (adynamics !== bdynamics) { return adynamics - bdynamics; } if (astatics !== bstatics) { return bstatics - astatics; } return 0; }); } function recognizeChar(states, ch) { var nextStates = []; for (var i = 0, l = states.length; i < l; i++) { var state = states[i]; nextStates = nextStates.concat(state.match(ch)); } return nextStates; } var RecognizeResults = function RecognizeResults(queryParams) { this.length = 0; this.queryParams = queryParams || {}; }; RecognizeResults.prototype.splice = Array.prototype.splice; RecognizeResults.prototype.slice = Array.prototype.slice; RecognizeResults.prototype.push = Array.prototype.push; function findHandler(state, originalPath, queryParams) { var handlers = state.handlers; var regex = state.regex(); if (!regex || !handlers) { throw new Error("state not initialized"); } var captures = originalPath.match(regex); var currentCapture = 1; var result = new RecognizeResults(queryParams); result.length = handlers.length