<--------------- currentCursor.element
*
*
<--------------- currentCursor.candidate -> cursor.element
* <- currentCursor.candidate.firstChild -> cursor.candidate
* Foo
*
*
* <-- becomes currentCursor.candidate
*/
// where to rehydrate from if we are in rehydration mode
cursor.candidate = element.firstChild; // where to continue when we pop
currentCursor.candidate = element.nextSibling;
}
}
this.cursorStack.push(cursor);
}
clearMismatch(candidate) {
var current = candidate;
var currentCursor = this.currentCursor;
if (currentCursor !== null) {
var openBlockDepth = currentCursor.openBlockDepth;
if (openBlockDepth >= currentCursor.startingBlockDepth) {
while (current && !(isComment(current) && getCloseBlockDepth(current) === openBlockDepth)) {
current = this.remove(current);
}
} else {
while (current !== null) {
current = this.remove(current);
}
} // current cursor parentNode should be openCandidate if element
// or openCandidate.parentNode if comment
currentCursor.nextSibling = current; // disable rehydration until we popElement or closeBlock for openBlockDepth
currentCursor.candidate = null;
}
}
__openBlock() {
var {
currentCursor
} = this;
if (currentCursor === null) return;
var blockDepth = this.blockDepth;
this.blockDepth++;
var {
candidate
} = currentCursor;
if (candidate === null) return;
var {
tagName
} = currentCursor.element;
if (isComment(candidate) && getOpenBlockDepth(candidate) === blockDepth) {
currentCursor.candidate = this.remove(candidate);
currentCursor.openBlockDepth = blockDepth;
} else if (tagName !== 'TITLE' && tagName !== 'SCRIPT' && tagName !== 'STYLE') {
this.clearMismatch(candidate);
}
}
__closeBlock() {
var {
currentCursor
} = this;
if (currentCursor === null) return; // openBlock is the last rehydrated open block
var openBlockDepth = currentCursor.openBlockDepth; // this currently is the expected next open block depth
this.blockDepth--;
var {
candidate
} = currentCursor; // rehydrating
if (candidate !== null) {
if (isComment(candidate) && getCloseBlockDepth(candidate) === openBlockDepth) {
currentCursor.candidate = this.remove(candidate);
currentCursor.openBlockDepth--;
} else {
this.clearMismatch(candidate);
} // if the openBlockDepth matches the blockDepth we just closed to
// then restore rehydration
}
if (currentCursor.openBlockDepth === this.blockDepth) {
currentCursor.candidate = this.remove(currentCursor.nextSibling);
currentCursor.openBlockDepth--;
}
}
__appendNode(node) {
var {
candidate
} = this; // This code path is only used when inserting precisely one node. It needs more
// comparison logic, but we can probably lean on the cases where this code path
// is actually used.
if (candidate) {
return candidate;
} else {
return super.__appendNode(node);
}
}
__appendHTML(html) {
var candidateBounds = this.markerBounds();
if (candidateBounds) {
var first = candidateBounds.firstNode();
var last = candidateBounds.lastNode();
var newBounds = new ConcreteBounds(this.element, first.nextSibling, last.previousSibling);
var possibleEmptyMarker = this.remove(first);
this.remove(last);
if (possibleEmptyMarker !== null && isEmpty$1(possibleEmptyMarker)) {
this.candidate = this.remove(possibleEmptyMarker);
if (this.candidate !== null) {
this.clearMismatch(this.candidate);
}
}
return newBounds;
} else {
return super.__appendHTML(html);
}
}
remove(node) {
var element = node.parentNode;
var next = node.nextSibling;
element.removeChild(node);
return next;
}
markerBounds() {
var _candidate = this.candidate;
if (_candidate && isMarker(_candidate)) {
var first = _candidate;
var last = first.nextSibling;
while (last && !isMarker(last)) {
last = last.nextSibling;
}
return new ConcreteBounds(this.element, first, last);
} else {
return null;
}
}
__appendText(string) {
var {
candidate
} = this;
if (candidate) {
if (isTextNode(candidate)) {
if (candidate.nodeValue !== string) {
candidate.nodeValue = string;
}
this.candidate = candidate.nextSibling;
return candidate;
} else if (candidate && (isSeparator(candidate) || isEmpty$1(candidate))) {
this.candidate = candidate.nextSibling;
this.remove(candidate);
return this.__appendText(string);
} else if (isEmpty$1(candidate)) {
var next = this.remove(candidate);
this.candidate = next;
var text = this.dom.createTextNode(string);
this.dom.insertBefore(this.element, text, next);
return text;
} else {
this.clearMismatch(candidate);
return super.__appendText(string);
}
} else {
return super.__appendText(string);
}
}
__appendComment(string) {
var _candidate = this.candidate;
if (_candidate && isComment(_candidate)) {
if (_candidate.nodeValue !== string) {
_candidate.nodeValue = string;
}
this.candidate = _candidate.nextSibling;
return _candidate;
} else if (_candidate) {
this.clearMismatch(_candidate);
}
return super.__appendComment(string);
}
__openElement(tag) {
var _candidate = this.candidate;
if (_candidate && isElement(_candidate) && isSameNodeType(_candidate, tag)) {
this.unmatchedAttributes = [].slice.call(_candidate.attributes);
return _candidate;
} else if (_candidate) {
if (isElement(_candidate) && _candidate.tagName === 'TBODY') {
this.pushElement(_candidate, null);
this.currentCursor.injectedOmittedNode = true;
return this.__openElement(tag);
}
this.clearMismatch(_candidate);
}
return super.__openElement(tag);
}
__setAttribute(name, value$$1, namespace) {
var unmatched = this.unmatchedAttributes;
if (unmatched) {
var attr = findByName(unmatched, name);
if (attr) {
if (attr.value !== value$$1) {
attr.value = value$$1;
}
unmatched.splice(unmatched.indexOf(attr), 1);
return;
}
}
return super.__setAttribute(name, value$$1, namespace);
}
__setProperty(name, value$$1) {
var unmatched = this.unmatchedAttributes;
if (unmatched) {
var attr = findByName(unmatched, name);
if (attr) {
if (attr.value !== value$$1) {
attr.value = value$$1;
}
unmatched.splice(unmatched.indexOf(attr), 1);
return;
}
}
return super.__setProperty(name, value$$1);
}
__flushElement(parent, constructing) {
var {
unmatchedAttributes: unmatched
} = this;
if (unmatched) {
for (var i = 0; i < unmatched.length; i++) {
this.constructing.removeAttribute(unmatched[i].name);
}
this.unmatchedAttributes = null;
} else {
super.__flushElement(parent, constructing);
}
}
willCloseElement() {
var {
candidate,
currentCursor
} = this;
if (candidate !== null) {
this.clearMismatch(candidate);
}
if (currentCursor && currentCursor.injectedOmittedNode) {
this.popElement();
}
super.willCloseElement();
}
getMarker(element, guid) {
var marker = element.querySelector(`script[glmr="${guid}"]`);
if (marker) {
return marker;
}
throw new Error('Cannot find serialized cursor for `in-element`');
}
__pushRemoteElement(element, cursorId, nextSibling = null) {
var marker = this.getMarker(element, cursorId);
if (marker.parentNode === element) {
var currentCursor = this.currentCursor;
var candidate = currentCursor.candidate;
this.pushElement(element, nextSibling);
currentCursor.candidate = candidate;
this.candidate = this.remove(marker);
var tracker = new RemoteBlockTracker(element);
this.pushBlockTracker(tracker, true);
}
}
didAppendBounds(bounds) {
super.didAppendBounds(bounds);
if (this.candidate) {
var last = bounds.lastNode();
this.candidate = last && last.nextSibling;
}
return bounds;
}
}
_exports.RehydrateBuilder = RehydrateBuilder;
function isTextNode(node) {
return node.nodeType === 3;
}
function isComment(node) {
return node.nodeType === 8;
}
function getOpenBlockDepth(node) {
var boundsDepth = node.nodeValue.match(/^%\+b:(\d+)%$/);
if (boundsDepth && boundsDepth[1]) {
return Number(boundsDepth[1]);
} else {
return null;
}
}
function getCloseBlockDepth(node) {
var boundsDepth = node.nodeValue.match(/^%\-b:(\d+)%$/);
if (boundsDepth && boundsDepth[1]) {
return Number(boundsDepth[1]);
} else {
return null;
}
}
function isElement(node) {
return node.nodeType === 1;
}
function isMarker(node) {
return node.nodeType === 8 && node.nodeValue === '%glmr%';
}
function isSeparator(node) {
return node.nodeType === 8 && node.nodeValue === '%|%';
}
function isEmpty$1(node) {
return node.nodeType === 8 && node.nodeValue === '% %';
}
function isSameNodeType(candidate, tag) {
if (candidate.namespaceURI === SVG_NAMESPACE) {
return candidate.tagName === tag;
}
return candidate.tagName === tag.toUpperCase();
}
function findByName(array, name) {
for (var i = 0; i < array.length; i++) {
var attr = array[i];
if (attr.name === name) return attr;
}
return undefined;
}
function rehydrationBuilder(env, cursor) {
return RehydrateBuilder.forInitialRender(env, cursor);
}
});
define("@glimmer/util", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.assert = debugAssert;
_exports.assign = assign;
_exports.fillNulls = fillNulls;
_exports.ensureGuid = ensureGuid;
_exports.initializeGuid = initializeGuid;
_exports.dict = dict;
_exports.unwrap = unwrap;
_exports.expect = expect;
_exports.unreachable = unreachable;
_exports.EMPTY_ARRAY = _exports.ListSlice = _exports.ListNode = _exports.LinkedList = _exports.EMPTY_SLICE = _exports.DictSet = _exports.Stack = void 0;
function unwrap(val) {
if (val === null || val === undefined) throw new Error(`Expected value to be present`);
return val;
}
function expect(val, message) {
if (val === null || val === undefined) throw new Error(message);
return val;
}
function unreachable(message = 'unreachable') {
return new Error(message);
} // import Logger from './logger';
// let alreadyWarned = false;
function debugAssert(test, msg) {
// if (!alreadyWarned) {
// alreadyWarned = true;
// Logger.warn("Don't leave debug assertions on in public builds");
// }
if (!test) {
throw new Error(msg || 'assertion failure');
}
}
var {
keys: objKeys
} = Object;
function assign(obj) {
for (var i = 1; i < arguments.length; i++) {
var assignment = arguments[i];
if (assignment === null || typeof assignment !== 'object') continue;
var keys = objKeys(assignment);
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
obj[key] = assignment[key];
}
}
return obj;
}
function fillNulls(count) {
var arr = new Array(count);
for (var i = 0; i < count; i++) {
arr[i] = null;
}
return arr;
}
var GUID = 0;
function initializeGuid(object) {
return object._guid = ++GUID;
}
function ensureGuid(object) {
return object._guid || initializeGuid(object);
}
function dict() {
return Object.create(null);
}
class DictSet {
constructor() {
this.dict = dict();
}
add(obj) {
if (typeof obj === 'string') this.dict[obj] = obj;else this.dict[ensureGuid(obj)] = obj;
return this;
}
delete(obj) {
if (typeof obj === 'string') delete this.dict[obj];else if (obj._guid) delete this.dict[obj._guid];
}
}
_exports.DictSet = DictSet;
class Stack {
constructor() {
this.stack = [];
this.current = null;
}
get size() {
return this.stack.length;
}
push(item) {
this.current = item;
this.stack.push(item);
}
pop() {
var item = this.stack.pop();
var len = this.stack.length;
this.current = len === 0 ? null : this.stack[len - 1];
return item === undefined ? null : item;
}
isEmpty() {
return this.stack.length === 0;
}
}
_exports.Stack = Stack;
class ListNode {
constructor(value) {
this.next = null;
this.prev = null;
this.value = value;
}
}
_exports.ListNode = ListNode;
class LinkedList {
constructor() {
this.clear();
}
head() {
return this._head;
}
tail() {
return this._tail;
}
clear() {
this._head = this._tail = null;
}
toArray() {
var out = [];
this.forEachNode(n => out.push(n));
return out;
}
nextNode(node) {
return node.next;
}
forEachNode(callback) {
var node = this._head;
while (node !== null) {
callback(node);
node = node.next;
}
}
insertBefore(node, reference = null) {
if (reference === null) return this.append(node);
if (reference.prev) reference.prev.next = node;else this._head = node;
node.prev = reference.prev;
node.next = reference;
reference.prev = node;
return node;
}
append(node) {
var tail = this._tail;
if (tail) {
tail.next = node;
node.prev = tail;
node.next = null;
} else {
this._head = node;
}
return this._tail = node;
}
remove(node) {
if (node.prev) node.prev.next = node.next;else this._head = node.next;
if (node.next) node.next.prev = node.prev;else this._tail = node.prev;
return node;
}
}
_exports.LinkedList = LinkedList;
class ListSlice {
constructor(head, tail) {
this._head = head;
this._tail = tail;
}
forEachNode(callback) {
var node = this._head;
while (node !== null) {
callback(node);
node = this.nextNode(node);
}
}
head() {
return this._head;
}
tail() {
return this._tail;
}
toArray() {
var out = [];
this.forEachNode(n => out.push(n));
return out;
}
nextNode(node) {
if (node === this._tail) return null;
return node.next;
}
}
_exports.ListSlice = ListSlice;
var EMPTY_SLICE = new ListSlice(null, null);
_exports.EMPTY_SLICE = EMPTY_SLICE;
var EMPTY_ARRAY = Object.freeze([]);
_exports.EMPTY_ARRAY = EMPTY_ARRAY;
});
define("@glimmer/vm", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.Register = void 0;
/**
* Registers
*
* For the most part, these follows MIPS naming conventions, however the
* register numbers are different.
*/
var Register;
_exports.Register = Register;
(function (Register) {
// $0 or $pc (program counter): pointer into `program` for the next insturction; -1 means exit
Register[Register["pc"] = 0] = "pc"; // $1 or $ra (return address): pointer into `program` for the return
Register[Register["ra"] = 1] = "ra"; // $2 or $fp (frame pointer): pointer into the `evalStack` for the base of the stack
Register[Register["fp"] = 2] = "fp"; // $3 or $sp (stack pointer): pointer into the `evalStack` for the top of the stack
Register[Register["sp"] = 3] = "sp"; // $4-$5 or $s0-$s1 (saved): callee saved general-purpose registers
Register[Register["s0"] = 4] = "s0";
Register[Register["s1"] = 5] = "s1"; // $6-$7 or $t0-$t1 (temporaries): caller saved general-purpose registers
Register[Register["t0"] = 6] = "t0";
Register[Register["t1"] = 7] = "t1"; // $8 or $v0 (return value)
Register[Register["v0"] = 8] = "v0";
})(Register || (_exports.Register = Register = {}));
});
define("@glimmer/wire-format", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.is = is;
_exports.isAttribute = isAttribute;
_exports.isArgument = isArgument;
_exports.isMaybeLocal = _exports.isGet = _exports.isFlushElement = _exports.Ops = void 0;
var Opcodes;
_exports.Ops = Opcodes;
(function (Opcodes) {
// Statements
Opcodes[Opcodes["Text"] = 0] = "Text";
Opcodes[Opcodes["Append"] = 1] = "Append";
Opcodes[Opcodes["Comment"] = 2] = "Comment";
Opcodes[Opcodes["Modifier"] = 3] = "Modifier";
Opcodes[Opcodes["Block"] = 4] = "Block";
Opcodes[Opcodes["Component"] = 5] = "Component";
Opcodes[Opcodes["DynamicComponent"] = 6] = "DynamicComponent";
Opcodes[Opcodes["OpenElement"] = 7] = "OpenElement";
Opcodes[Opcodes["FlushElement"] = 8] = "FlushElement";
Opcodes[Opcodes["CloseElement"] = 9] = "CloseElement";
Opcodes[Opcodes["StaticAttr"] = 10] = "StaticAttr";
Opcodes[Opcodes["DynamicAttr"] = 11] = "DynamicAttr";
Opcodes[Opcodes["ComponentAttr"] = 12] = "ComponentAttr";
Opcodes[Opcodes["AttrSplat"] = 13] = "AttrSplat";
Opcodes[Opcodes["Yield"] = 14] = "Yield";
Opcodes[Opcodes["Partial"] = 15] = "Partial";
Opcodes[Opcodes["DynamicArg"] = 16] = "DynamicArg";
Opcodes[Opcodes["StaticArg"] = 17] = "StaticArg";
Opcodes[Opcodes["TrustingAttr"] = 18] = "TrustingAttr";
Opcodes[Opcodes["TrustingComponentAttr"] = 19] = "TrustingComponentAttr";
Opcodes[Opcodes["Debugger"] = 20] = "Debugger";
Opcodes[Opcodes["ClientSideStatement"] = 21] = "ClientSideStatement"; // Expressions
Opcodes[Opcodes["Unknown"] = 22] = "Unknown";
Opcodes[Opcodes["Get"] = 23] = "Get";
Opcodes[Opcodes["MaybeLocal"] = 24] = "MaybeLocal";
Opcodes[Opcodes["HasBlock"] = 25] = "HasBlock";
Opcodes[Opcodes["HasBlockParams"] = 26] = "HasBlockParams";
Opcodes[Opcodes["Undefined"] = 27] = "Undefined";
Opcodes[Opcodes["Helper"] = 28] = "Helper";
Opcodes[Opcodes["Concat"] = 29] = "Concat";
Opcodes[Opcodes["ClientSideExpression"] = 30] = "ClientSideExpression";
})(Opcodes || (_exports.Ops = Opcodes = {}));
function is(variant) {
return function (value) {
return Array.isArray(value) && value[0] === variant;
};
} // Statements
var isFlushElement = is(Opcodes.FlushElement);
_exports.isFlushElement = isFlushElement;
function isAttribute(val) {
return val[0] === Opcodes.StaticAttr || val[0] === Opcodes.DynamicAttr || val[0] === Opcodes.ComponentAttr || val[0] === Opcodes.TrustingAttr || val[0] === Opcodes.TrustingComponentAttr || val[0] === Opcodes.AttrSplat || val[0] === Opcodes.Modifier;
}
function isArgument(val) {
return val[0] === Opcodes.StaticArg || val[0] === Opcodes.DynamicArg;
} // Expressions
var isGet = is(Opcodes.Get);
_exports.isGet = isGet;
var isMaybeLocal = is(Opcodes.MaybeLocal);
_exports.isMaybeLocal = isMaybeLocal;
});
define("backburner", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.buildPlatform = buildPlatform;
_exports.default = void 0;
var SET_TIMEOUT = setTimeout;
var NOOP = () => {};
function buildNext(flush) {
// Using "promises first" here to:
//
// 1) Ensure more consistent experience on browsers that
// have differently queued microtasks (separate queues for
// MutationObserver vs Promises).
// 2) Ensure better debugging experiences (it shows up in Chrome
// call stack as "Promise.then (async)") which is more consistent
// with user expectations
//
// When Promise is unavailable use MutationObserver (mostly so that we
// still get microtasks on IE11), and when neither MutationObserver and
// Promise are present use a plain old setTimeout.
if (typeof Promise === 'function') {
var autorunPromise = Promise.resolve();
return () => autorunPromise.then(flush);
} else if (typeof MutationObserver === 'function') {
var iterations = 0;
var observer = new MutationObserver(flush);
var node = document.createTextNode('');
observer.observe(node, {
characterData: true
});
return () => {
iterations = ++iterations % 2;
node.data = '' + iterations;
return iterations;
};
} else {
return () => SET_TIMEOUT(flush, 0);
}
}
function buildPlatform(flush) {
var clearNext = NOOP;
return {
setTimeout(fn, ms) {
return setTimeout(fn, ms);
},
clearTimeout(timerId) {
return clearTimeout(timerId);
},
now() {
return Date.now();
},
next: buildNext(flush),
clearNext
};
}
var NUMBER = /\d+/;
var TIMERS_OFFSET = 6;
function isCoercableNumber(suspect) {
var type = typeof suspect;
return type === 'number' && suspect === suspect || type === 'string' && NUMBER.test(suspect);
}
function getOnError(options) {
return options.onError || options.onErrorTarget && options.onErrorTarget[options.onErrorMethod];
}
function findItem(target, method, collection) {
var index = -1;
for (var i = 0, l = collection.length; i < l; i += 4) {
if (collection[i] === target && collection[i + 1] === method) {
index = i;
break;
}
}
return index;
}
function findTimerItem(target, method, collection) {
var index = -1;
for (var i = 2, l = collection.length; i < l; i += 6) {
if (collection[i] === target && collection[i + 1] === method) {
index = i - 2;
break;
}
}
return index;
}
function getQueueItems(items, queueItemLength, queueItemPositionOffset = 0) {
var queueItems = [];
for (var i = 0; i < items.length; i += queueItemLength) {
var maybeError = items[i + 3
/* stack */
+ queueItemPositionOffset];
var queueItem = {
target: items[i + 0
/* target */
+ queueItemPositionOffset],
method: items[i + 1
/* method */
+ queueItemPositionOffset],
args: items[i + 2
/* args */
+ queueItemPositionOffset],
stack: maybeError !== undefined && 'stack' in maybeError ? maybeError.stack : ''
};
queueItems.push(queueItem);
}
return queueItems;
}
function binarySearch(time, timers) {
var start = 0;
var end = timers.length - TIMERS_OFFSET;
var middle;
var l;
while (start < end) {
// since timers is an array of pairs 'l' will always
// be an integer
l = (end - start) / TIMERS_OFFSET; // compensate for the index in case even number
// of pairs inside timers
middle = start + l - l % TIMERS_OFFSET;
if (time >= timers[middle]) {
start = middle + TIMERS_OFFSET;
} else {
end = middle;
}
}
return time >= timers[start] ? start + TIMERS_OFFSET : start;
}
var QUEUE_ITEM_LENGTH = 4;
class Queue {
constructor(name, options = {}, globalOptions = {}) {
this._queueBeingFlushed = [];
this.targetQueues = new Map();
this.index = 0;
this._queue = [];
this.name = name;
this.options = options;
this.globalOptions = globalOptions;
}
stackFor(index) {
if (index < this._queue.length) {
var entry = this._queue[index * 3 + QUEUE_ITEM_LENGTH];
if (entry) {
return entry.stack;
} else {
return null;
}
}
}
flush(sync) {
var {
before,
after
} = this.options;
var target;
var method;
var args;
var errorRecordedForStack;
this.targetQueues.clear();
if (this._queueBeingFlushed.length === 0) {
this._queueBeingFlushed = this._queue;
this._queue = [];
}
if (before !== undefined) {
before();
}
var invoke;
var queueItems = this._queueBeingFlushed;
if (queueItems.length > 0) {
var onError = getOnError(this.globalOptions);
invoke = onError ? this.invokeWithOnError : this.invoke;
for (var i = this.index; i < queueItems.length; i += QUEUE_ITEM_LENGTH) {
this.index += QUEUE_ITEM_LENGTH;
method = queueItems[i + 1]; // method could have been nullified / canceled during flush
if (method !== null) {
//
// ** Attention intrepid developer **
//
// To find out the stack of this task when it was scheduled onto
// the run loop, add the following to your app.js:
//
// Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production.
//
// Once that is in place, when you are at a breakpoint and navigate
// here in the stack explorer, you can look at `errorRecordedForStack.stack`,
// which will be the captured stack when this job was scheduled.
//
// One possible long-term solution is the following Chrome issue:
// https://bugs.chromium.org/p/chromium/issues/detail?id=332624
//
target = queueItems[i];
args = queueItems[i + 2];
errorRecordedForStack = queueItems[i + 3]; // Debugging assistance
invoke(target, method, args, onError, errorRecordedForStack);
}
if (this.index !== this._queueBeingFlushed.length && this.globalOptions.mustYield && this.globalOptions.mustYield()) {
return 1
/* Pause */
;
}
}
}
if (after !== undefined) {
after();
}
this._queueBeingFlushed.length = 0;
this.index = 0;
if (sync !== false && this._queue.length > 0) {
// check if new items have been added
this.flush(true);
}
}
hasWork() {
return this._queueBeingFlushed.length > 0 || this._queue.length > 0;
}
cancel({
target,
method
}) {
var queue = this._queue;
var targetQueueMap = this.targetQueues.get(target);
if (targetQueueMap !== undefined) {
targetQueueMap.delete(method);
}
var index = findItem(target, method, queue);
if (index > -1) {
queue.splice(index, QUEUE_ITEM_LENGTH);
return true;
} // if not found in current queue
// could be in the queue that is being flushed
queue = this._queueBeingFlushed;
index = findItem(target, method, queue);
if (index > -1) {
queue[index + 1] = null;
return true;
}
return false;
}
push(target, method, args, stack) {
this._queue.push(target, method, args, stack);
return {
queue: this,
target,
method
};
}
pushUnique(target, method, args, stack) {
var localQueueMap = this.targetQueues.get(target);
if (localQueueMap === undefined) {
localQueueMap = new Map();
this.targetQueues.set(target, localQueueMap);
}
var index = localQueueMap.get(method);
if (index === undefined) {
var queueIndex = this._queue.push(target, method, args, stack) - QUEUE_ITEM_LENGTH;
localQueueMap.set(method, queueIndex);
} else {
var queue = this._queue;
queue[index + 2] = args; // replace args
queue[index + 3] = stack; // replace stack
}
return {
queue: this,
target,
method
};
}
_getDebugInfo(debugEnabled) {
if (debugEnabled) {
var debugInfo = getQueueItems(this._queue, QUEUE_ITEM_LENGTH);
return debugInfo;
}
return undefined;
}
invoke(target, method, args
/*, onError, errorRecordedForStack */
) {
if (args === undefined) {
method.call(target);
} else {
method.apply(target, args);
}
}
invokeWithOnError(target, method, args, onError, errorRecordedForStack) {
try {
if (args === undefined) {
method.call(target);
} else {
method.apply(target, args);
}
} catch (error) {
onError(error, errorRecordedForStack);
}
}
}
class DeferredActionQueues {
constructor(queueNames = [], options) {
this.queues = {};
this.queueNameIndex = 0;
this.queueNames = queueNames;
queueNames.reduce(function (queues, queueName) {
queues[queueName] = new Queue(queueName, options[queueName], options);
return queues;
}, this.queues);
}
/**
* @method schedule
* @param {String} queueName
* @param {Any} target
* @param {Any} method
* @param {Any} args
* @param {Boolean} onceFlag
* @param {Any} stack
* @return queue
*/
schedule(queueName, target, method, args, onceFlag, stack) {
var queues = this.queues;
var queue = queues[queueName];
if (queue === undefined) {
throw new Error(`You attempted to schedule an action in a queue (${queueName}) that doesn\'t exist`);
}
if (method === undefined || method === null) {
throw new Error(`You attempted to schedule an action in a queue (${queueName}) for a method that doesn\'t exist`);
}
this.queueNameIndex = 0;
if (onceFlag) {
return queue.pushUnique(target, method, args, stack);
} else {
return queue.push(target, method, args, stack);
}
}
/**
* DeferredActionQueues.flush() calls Queue.flush()
*
* @method flush
* @param {Boolean} fromAutorun
*/
flush(fromAutorun = false) {
var queue;
var queueName;
var numberOfQueues = this.queueNames.length;
while (this.queueNameIndex < numberOfQueues) {
queueName = this.queueNames[this.queueNameIndex];
queue = this.queues[queueName];
if (queue.hasWork() === false) {
this.queueNameIndex++;
if (fromAutorun && this.queueNameIndex < numberOfQueues) {
return 1
/* Pause */
;
}
} else {
if (queue.flush(false
/* async */
) === 1
/* Pause */
) {
return 1
/* Pause */
;
}
}
}
}
/**
* Returns debug information for the current queues.
*
* @method _getDebugInfo
* @param {Boolean} debugEnabled
* @returns {IDebugInfo | undefined}
*/
_getDebugInfo(debugEnabled) {
if (debugEnabled) {
var debugInfo = {};
var queue;
var queueName;
var numberOfQueues = this.queueNames.length;
var i = 0;
while (i < numberOfQueues) {
queueName = this.queueNames[i];
queue = this.queues[queueName];
debugInfo[queueName] = queue._getDebugInfo(debugEnabled);
i++;
}
return debugInfo;
}
return;
}
}
function iteratorDrain(fn) {
var iterator = fn();
var result = iterator.next();
while (result.done === false) {
result.value();
result = iterator.next();
}
}
var noop = function () {};
var DISABLE_SCHEDULE = Object.freeze([]);
function parseArgs() {
var length = arguments.length;
var args;
var method;
var target;
if (length === 0) {} else if (length === 1) {
target = null;
method = arguments[0];
} else {
var argsIndex = 2;
var methodOrTarget = arguments[0];
var methodOrArgs = arguments[1];
var type = typeof methodOrArgs;
if (type === 'function') {
target = methodOrTarget;
method = methodOrArgs;
} else if (methodOrTarget !== null && type === 'string' && methodOrArgs in methodOrTarget) {
target = methodOrTarget;
method = target[methodOrArgs];
} else if (typeof methodOrTarget === 'function') {
argsIndex = 1;
target = null;
method = methodOrTarget;
}
if (length > argsIndex) {
var len = length - argsIndex;
args = new Array(len);
for (var i = 0; i < len; i++) {
args[i] = arguments[i + argsIndex];
}
}
}
return [target, method, args];
}
function parseTimerArgs() {
var [target, method, args] = parseArgs(...arguments);
var wait = 0;
var length = args !== undefined ? args.length : 0;
if (length > 0) {
var last = args[length - 1];
if (isCoercableNumber(last)) {
wait = parseInt(args.pop(), 10);
}
}
return [target, method, args, wait];
}
function parseDebounceArgs() {
var target;
var method;
var isImmediate;
var args;
var wait;
if (arguments.length === 2) {
method = arguments[0];
wait = arguments[1];
target = null;
} else {
[target, method, args] = parseArgs(...arguments);
if (args === undefined) {
wait = 0;
} else {
wait = args.pop();
if (!isCoercableNumber(wait)) {
isImmediate = wait === true;
wait = args.pop();
}
}
}
wait = parseInt(wait, 10);
return [target, method, args, wait, isImmediate];
}
var UUID = 0;
var beginCount = 0;
var endCount = 0;
var beginEventCount = 0;
var endEventCount = 0;
var runCount = 0;
var joinCount = 0;
var deferCount = 0;
var scheduleCount = 0;
var scheduleIterableCount = 0;
var deferOnceCount = 0;
var scheduleOnceCount = 0;
var setTimeoutCount = 0;
var laterCount = 0;
var throttleCount = 0;
var debounceCount = 0;
var cancelTimersCount = 0;
var cancelCount = 0;
var autorunsCreatedCount = 0;
var autorunsCompletedCount = 0;
var deferredActionQueuesCreatedCount = 0;
var nestedDeferredActionQueuesCreated = 0;
class Backburner {
constructor(queueNames, options) {
this.DEBUG = false;
this.currentInstance = null;
this.instanceStack = [];
this._eventCallbacks = {
end: [],
begin: []
};
this._timerTimeoutId = null;
this._timers = [];
this._autorun = false;
this._autorunStack = null;
this.queueNames = queueNames;
this.options = options || {};
if (typeof this.options.defaultQueue === 'string') {
this._defaultQueue = this.options.defaultQueue;
} else {
this._defaultQueue = this.queueNames[0];
}
this._onBegin = this.options.onBegin || noop;
this._onEnd = this.options.onEnd || noop;
this._boundRunExpiredTimers = this._runExpiredTimers.bind(this);
this._boundAutorunEnd = () => {
autorunsCompletedCount++; // if the autorun was already flushed, do nothing
if (this._autorun === false) {
return;
}
this._autorun = false;
this._autorunStack = null;
this._end(true
/* fromAutorun */
);
};
var builder = this.options._buildPlatform || buildPlatform;
this._platform = builder(this._boundAutorunEnd);
}
get counters() {
return {
begin: beginCount,
end: endCount,
events: {
begin: beginEventCount,
end: endEventCount
},
autoruns: {
created: autorunsCreatedCount,
completed: autorunsCompletedCount
},
run: runCount,
join: joinCount,
defer: deferCount,
schedule: scheduleCount,
scheduleIterable: scheduleIterableCount,
deferOnce: deferOnceCount,
scheduleOnce: scheduleOnceCount,
setTimeout: setTimeoutCount,
later: laterCount,
throttle: throttleCount,
debounce: debounceCount,
cancelTimers: cancelTimersCount,
cancel: cancelCount,
loops: {
total: deferredActionQueuesCreatedCount,
nested: nestedDeferredActionQueuesCreated
}
};
}
get defaultQueue() {
return this._defaultQueue;
}
/*
@method begin
@return instantiated class DeferredActionQueues
*/
begin() {
beginCount++;
var options = this.options;
var previousInstance = this.currentInstance;
var current;
if (this._autorun !== false) {
current = previousInstance;
this._cancelAutorun();
} else {
if (previousInstance !== null) {
nestedDeferredActionQueuesCreated++;
this.instanceStack.push(previousInstance);
}
deferredActionQueuesCreatedCount++;
current = this.currentInstance = new DeferredActionQueues(this.queueNames, options);
beginEventCount++;
this._trigger('begin', current, previousInstance);
}
this._onBegin(current, previousInstance);
return current;
}
end() {
endCount++;
this._end(false);
}
on(eventName, callback) {
if (typeof callback !== 'function') {
throw new TypeError(`Callback must be a function`);
}
var callbacks = this._eventCallbacks[eventName];
if (callbacks !== undefined) {
callbacks.push(callback);
} else {
throw new TypeError(`Cannot on() event ${eventName} because it does not exist`);
}
}
off(eventName, callback) {
var callbacks = this._eventCallbacks[eventName];
if (!eventName || callbacks === undefined) {
throw new TypeError(`Cannot off() event ${eventName} because it does not exist`);
}
var callbackFound = false;
if (callback) {
for (var i = 0; i < callbacks.length; i++) {
if (callbacks[i] === callback) {
callbackFound = true;
callbacks.splice(i, 1);
i--;
}
}
}
if (!callbackFound) {
throw new TypeError(`Cannot off() callback that does not exist`);
}
}
run() {
runCount++;
var [target, method, args] = parseArgs(...arguments);
return this._run(target, method, args);
}
join() {
joinCount++;
var [target, method, args] = parseArgs(...arguments);
return this._join(target, method, args);
}
/**
* @deprecated please use schedule instead.
*/
defer(queueName, target, method, ...args) {
deferCount++;
return this.schedule(queueName, target, method, ...args);
}
schedule(queueName, ..._args) {
scheduleCount++;
var [target, method, args] = parseArgs(..._args);
var stack = this.DEBUG ? new Error() : undefined;
return this._ensureInstance().schedule(queueName, target, method, args, false, stack);
}
/*
Defer the passed iterable of functions to run inside the specified queue.
@method scheduleIterable
@param {String} queueName
@param {Iterable} an iterable of functions to execute
@return method result
*/
scheduleIterable(queueName, iterable) {
scheduleIterableCount++;
var stack = this.DEBUG ? new Error() : undefined;
return this._ensureInstance().schedule(queueName, null, iteratorDrain, [iterable], false, stack);
}
/**
* @deprecated please use scheduleOnce instead.
*/
deferOnce(queueName, target, method, ...args) {
deferOnceCount++;
return this.scheduleOnce(queueName, target, method, ...args);
}
scheduleOnce(queueName, ..._args) {
scheduleOnceCount++;
var [target, method, args] = parseArgs(..._args);
var stack = this.DEBUG ? new Error() : undefined;
return this._ensureInstance().schedule(queueName, target, method, args, true, stack);
}
setTimeout() {
setTimeoutCount++;
return this.later(...arguments);
}
later() {
laterCount++;
var [target, method, args, wait] = parseTimerArgs(...arguments);
return this._later(target, method, args, wait);
}
throttle() {
throttleCount++;
var [target, method, args, wait, isImmediate = true] = parseDebounceArgs(...arguments);
var index = findTimerItem(target, method, this._timers);
var timerId;
if (index === -1) {
timerId = this._later(target, method, isImmediate ? DISABLE_SCHEDULE : args, wait);
if (isImmediate) {
this._join(target, method, args);
}
} else {
timerId = this._timers[index + 1];
var argIndex = index + 4;
if (this._timers[argIndex] !== DISABLE_SCHEDULE) {
this._timers[argIndex] = args;
}
}
return timerId;
}
debounce() {
debounceCount++;
var [target, method, args, wait, isImmediate = false] = parseDebounceArgs(...arguments);
var _timers = this._timers;
var index = findTimerItem(target, method, _timers);
var timerId;
if (index === -1) {
timerId = this._later(target, method, isImmediate ? DISABLE_SCHEDULE : args, wait);
if (isImmediate) {
this._join(target, method, args);
}
} else {
var executeAt = this._platform.now() + wait;
var argIndex = index + 4;
if (_timers[argIndex] === DISABLE_SCHEDULE) {
args = DISABLE_SCHEDULE;
}
timerId = _timers[index + 1];
var i = binarySearch(executeAt, _timers);
if (index + TIMERS_OFFSET === i) {
_timers[index] = executeAt;
_timers[argIndex] = args;
} else {
var stack = this._timers[index + 5];
this._timers.splice(i, 0, executeAt, timerId, target, method, args, stack);
this._timers.splice(index, TIMERS_OFFSET);
}
if (index === 0) {
this._reinstallTimerTimeout();
}
}
return timerId;
}
cancelTimers() {
cancelTimersCount++;
this._clearTimerTimeout();
this._timers = [];
this._cancelAutorun();
}
hasTimers() {
return this._timers.length > 0 || this._autorun;
}
cancel(timer) {
cancelCount++;
if (timer === null || timer === undefined) {
return false;
}
var timerType = typeof timer;
if (timerType === 'number') {
// we're cancelling a setTimeout or throttle or debounce
return this._cancelLaterTimer(timer);
} else if (timerType === 'object' && timer.queue && timer.method) {
// we're cancelling a deferOnce
return timer.queue.cancel(timer);
}
return false;
}
ensureInstance() {
this._ensureInstance();
}
/**
* Returns debug information related to the current instance of Backburner
*
* @method getDebugInfo
* @returns {Object | undefined} Will return and Object containing debug information if
* the DEBUG flag is set to true on the current instance of Backburner, else undefined.
*/
getDebugInfo() {
if (this.DEBUG) {
return {
autorun: this._autorunStack,
counters: this.counters,
timers: getQueueItems(this._timers, TIMERS_OFFSET, 2),
instanceStack: [this.currentInstance, ...this.instanceStack].map(deferredActionQueue => deferredActionQueue && deferredActionQueue._getDebugInfo(this.DEBUG))
};
}
return undefined;
}
_end(fromAutorun) {
var currentInstance = this.currentInstance;
var nextInstance = null;
if (currentInstance === null) {
throw new Error(`end called without begin`);
} // Prevent double-finally bug in Safari 6.0.2 and iOS 6
// This bug appears to be resolved in Safari 6.0.5 and iOS 7
var finallyAlreadyCalled = false;
var result;
try {
result = currentInstance.flush(fromAutorun);
} finally {
if (!finallyAlreadyCalled) {
finallyAlreadyCalled = true;
if (result === 1
/* Pause */
) {
var plannedNextQueue = this.queueNames[currentInstance.queueNameIndex];
this._scheduleAutorun(plannedNextQueue);
} else {
this.currentInstance = null;
if (this.instanceStack.length > 0) {
nextInstance = this.instanceStack.pop();
this.currentInstance = nextInstance;
}
this._trigger('end', currentInstance, nextInstance);
this._onEnd(currentInstance, nextInstance);
}
}
}
}
_join(target, method, args) {
if (this.currentInstance === null) {
return this._run(target, method, args);
}
if (target === undefined && args === undefined) {
return method();
} else {
return method.apply(target, args);
}
}
_run(target, method, args) {
var onError = getOnError(this.options);
this.begin();
if (onError) {
try {
return method.apply(target, args);
} catch (error) {
onError(error);
} finally {
this.end();
}
} else {
try {
return method.apply(target, args);
} finally {
this.end();
}
}
}
_cancelAutorun() {
if (this._autorun) {
this._platform.clearNext();
this._autorun = false;
this._autorunStack = null;
}
}
_later(target, method, args, wait) {
var stack = this.DEBUG ? new Error() : undefined;
var executeAt = this._platform.now() + wait;
var id = UUID++;
if (this._timers.length === 0) {
this._timers.push(executeAt, id, target, method, args, stack);
this._installTimerTimeout();
} else {
// find position to insert
var i = binarySearch(executeAt, this._timers);
this._timers.splice(i, 0, executeAt, id, target, method, args, stack); // always reinstall since it could be out of sync
this._reinstallTimerTimeout();
}
return id;
}
_cancelLaterTimer(timer) {
for (var i = 1; i < this._timers.length; i += TIMERS_OFFSET) {
if (this._timers[i] === timer) {
this._timers.splice(i - 1, TIMERS_OFFSET);
if (i === 1) {
this._reinstallTimerTimeout();
}
return true;
}
}
return false;
}
/**
Trigger an event. Supports up to two arguments. Designed around
triggering transition events from one run loop instance to the
next, which requires an argument for the instance and then
an argument for the next instance.
@private
@method _trigger
@param {String} eventName
@param {any} arg1
@param {any} arg2
*/
_trigger(eventName, arg1, arg2) {
var callbacks = this._eventCallbacks[eventName];
if (callbacks !== undefined) {
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](arg1, arg2);
}
}
}
_runExpiredTimers() {
this._timerTimeoutId = null;
if (this._timers.length > 0) {
this.begin();
this._scheduleExpiredTimers();
this.end();
}
}
_scheduleExpiredTimers() {
var timers = this._timers;
var i = 0;
var l = timers.length;
var defaultQueue = this._defaultQueue;
var n = this._platform.now();
for (; i < l; i += TIMERS_OFFSET) {
var executeAt = timers[i];
if (executeAt > n) {
break;
}
var args = timers[i + 4];
if (args !== DISABLE_SCHEDULE) {
var target = timers[i + 2];
var method = timers[i + 3];
var stack = timers[i + 5];
this.currentInstance.schedule(defaultQueue, target, method, args, false, stack);
}
}
timers.splice(0, i);
this._installTimerTimeout();
}
_reinstallTimerTimeout() {
this._clearTimerTimeout();
this._installTimerTimeout();
}
_clearTimerTimeout() {
if (this._timerTimeoutId === null) {
return;
}
this._platform.clearTimeout(this._timerTimeoutId);
this._timerTimeoutId = null;
}
_installTimerTimeout() {
if (this._timers.length === 0) {
return;
}
var minExpiresAt = this._timers[0];
var n = this._platform.now();
var wait = Math.max(0, minExpiresAt - n);
this._timerTimeoutId = this._platform.setTimeout(this._boundRunExpiredTimers, wait);
}
_ensureInstance() {
var currentInstance = this.currentInstance;
if (currentInstance === null) {
this._autorunStack = this.DEBUG ? new Error() : undefined;
currentInstance = this.begin();
this._scheduleAutorun(this.queueNames[0]);
}
return currentInstance;
}
_scheduleAutorun(plannedNextQueue) {
autorunsCreatedCount++;
var next = this._platform.next;
var flush = this.options.flush;
if (flush) {
flush(plannedNextQueue, next);
} else {
next();
}
this._autorun = true;
}
}
Backburner.Queue = Queue;
Backburner.buildPlatform = buildPlatform;
Backburner.buildNext = buildNext;
var _default = Backburner;
_exports.default = _default;
});
define("dag-map", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
* A topologically ordered map of key/value pairs with a simple API for adding constraints.
*
* Edges can forward reference keys that have not been added yet (the forward reference will
* map the key to undefined).
*/
var DAG = function () {
function DAG() {
this._vertices = new Vertices();
}
/**
* Adds a key/value pair with dependencies on other key/value pairs.
*
* @public
* @param key The key of the vertex to be added.
* @param value The value of that vertex.
* @param before A key or array of keys of the vertices that must
* be visited before this vertex.
* @param after An string or array of strings with the keys of the
* vertices that must be after this vertex is visited.
*/
DAG.prototype.add = function (key, value, before, after) {
if (!key) throw new Error('argument `key` is required');
var vertices = this._vertices;
var v = vertices.add(key);
v.val = value;
if (before) {
if (typeof before === "string") {
vertices.addEdge(v, vertices.add(before));
} else {
for (var i = 0; i < before.length; i++) {
vertices.addEdge(v, vertices.add(before[i]));
}
}
}
if (after) {
if (typeof after === "string") {
vertices.addEdge(vertices.add(after), v);
} else {
for (var i = 0; i < after.length; i++) {
vertices.addEdge(vertices.add(after[i]), v);
}
}
}
};
/**
* @deprecated please use add.
*/
DAG.prototype.addEdges = function (key, value, before, after) {
this.add(key, value, before, after);
};
/**
* Visits key/value pairs in topological order.
*
* @public
* @param callback The function to be invoked with each key/value.
*/
DAG.prototype.each = function (callback) {
this._vertices.walk(callback);
};
/**
* @deprecated please use each.
*/
DAG.prototype.topsort = function (callback) {
this.each(callback);
};
return DAG;
}();
var _default = DAG;
/** @private */
_exports.default = _default;
var Vertices = function () {
function Vertices() {
this.length = 0;
this.stack = new IntStack();
this.path = new IntStack();
this.result = new IntStack();
}
Vertices.prototype.add = function (key) {
if (!key) throw new Error("missing key");
var l = this.length | 0;
var vertex;
for (var i = 0; i < l; i++) {
vertex = this[i];
if (vertex.key === key) return vertex;
}
this.length = l + 1;
return this[l] = {
idx: l,
key: key,
val: undefined,
out: false,
flag: false,
length: 0
};
};
Vertices.prototype.addEdge = function (v, w) {
this.check(v, w.key);
var l = w.length | 0;
for (var i = 0; i < l; i++) {
if (w[i] === v.idx) return;
}
w.length = l + 1;
w[l] = v.idx;
v.out = true;
};
Vertices.prototype.walk = function (cb) {
this.reset();
for (var i = 0; i < this.length; i++) {
var vertex = this[i];
if (vertex.out) continue;
this.visit(vertex, "");
}
this.each(this.result, cb);
};
Vertices.prototype.check = function (v, w) {
if (v.key === w) {
throw new Error("cycle detected: " + w + " <- " + w);
} // quick check
if (v.length === 0) return; // shallow check
for (var i = 0; i < v.length; i++) {
var key = this[v[i]].key;
if (key === w) {
throw new Error("cycle detected: " + w + " <- " + v.key + " <- " + w);
}
} // deep check
this.reset();
this.visit(v, w);
if (this.path.length > 0) {
var msg_1 = "cycle detected: " + w;
this.each(this.path, function (key) {
msg_1 += " <- " + key;
});
throw new Error(msg_1);
}
};
Vertices.prototype.reset = function () {
this.stack.length = 0;
this.path.length = 0;
this.result.length = 0;
for (var i = 0, l = this.length; i < l; i++) {
this[i].flag = false;
}
};
Vertices.prototype.visit = function (start, search) {
var _a = this,
stack = _a.stack,
path = _a.path,
result = _a.result;
stack.push(start.idx);
while (stack.length) {
var index = stack.pop() | 0;
if (index >= 0) {
// enter
var vertex = this[index];
if (vertex.flag) continue;
vertex.flag = true;
path.push(index);
if (search === vertex.key) break; // push exit
stack.push(~index);
this.pushIncoming(vertex);
} else {
// exit
path.pop();
result.push(~index);
}
}
};
Vertices.prototype.pushIncoming = function (incomming) {
var stack = this.stack;
for (var i = incomming.length - 1; i >= 0; i--) {
var index = incomming[i];
if (!this[index].flag) {
stack.push(index);
}
}
};
Vertices.prototype.each = function (indices, cb) {
for (var i = 0, l = indices.length; i < l; i++) {
var vertex = this[indices[i]];
cb(vertex.key, vertex.val);
}
};
return Vertices;
}();
/** @private */
var IntStack = function () {
function IntStack() {
this.length = 0;
}
IntStack.prototype.push = function (n) {
this[this.length++] = n | 0;
};
IntStack.prototype.pop = function () {
return this[--this.length] | 0;
};
return IntStack;
}();
});
define("ember-babel", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.wrapNativeSuper = wrapNativeSuper;
_exports.classCallCheck = classCallCheck;
_exports.inheritsLoose = inheritsLoose;
_exports.taggedTemplateLiteralLoose = taggedTemplateLiteralLoose;
_exports.createClass = createClass;
_exports.assertThisInitialized = assertThisInitialized;
_exports.possibleConstructorReturn = possibleConstructorReturn;
_exports.objectDestructuringEmpty = objectDestructuringEmpty;
var setPrototypeOf = Object.setPrototypeOf;
var nativeWrapperCache = new Map(); // Super minimal version of Babel's wrapNativeSuper. We only use this for
// extending Function, for ComputedDecoratorImpl and AliasDecoratorImpl. We know
// we will never directly create an instance of these classes so no need to
// include `construct` code or other helpers.
function wrapNativeSuper(Class) {
if (nativeWrapperCache.has(Class)) {
return nativeWrapperCache.get(Class);
}
function Wrapper() {}
Wrapper.prototype = Object.create(Class.prototype, {
constructor: {
value: Wrapper,
enumerable: false,
writable: true,
configurable: true
}
});
nativeWrapperCache.set(Class, Wrapper);
return setPrototypeOf(Wrapper, Class);
}
function classCallCheck(instance, Constructor) {
if (true
/* DEBUG */
) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
}
/*
Overrides default `inheritsLoose` to _also_ call `Object.setPrototypeOf`.
This is needed so that we can use `loose` option with the
`@babel/plugin-transform-classes` (because we want simple assignment to the
prototype whereever possible) but also keep our constructor based prototypal
inheritance working properly
*/
function inheritsLoose(subClass, superClass) {
if (true
/* DEBUG */
) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function');
}
}
subClass.prototype = Object.create(superClass === null ? null : superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass !== null) {
setPrototypeOf(subClass, superClass);
}
}
function taggedTemplateLiteralLoose(strings, raw) {
if (!raw) {
raw = strings.slice(0);
}
strings.raw = raw;
return strings;
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
/*
Differs from default implementation by avoiding boolean coercion of
`protoProps` and `staticProps`.
*/
function createClass(Constructor, protoProps, staticProps) {
if (protoProps !== null && protoProps !== undefined) {
_defineProperties(Constructor.prototype, protoProps);
}
if (staticProps !== null && staticProps !== undefined) {
_defineProperties(Constructor, staticProps);
}
return Constructor;
}
function assertThisInitialized(self) {
if (true
/* DEBUG */
&& self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
/*
Adds `DEBUG` guard to error being thrown, and avoids boolean coercion of `call`.
*/
function possibleConstructorReturn(self, call) {
if (typeof call === 'object' && call !== null || typeof call === 'function') {
return call;
}
return assertThisInitialized(self);
}
function objectDestructuringEmpty(obj) {
if (true
/* DEBUG */
&& (obj === null || obj === undefined)) {
throw new TypeError('Cannot destructure undefined');
}
}
});
define("ember-testing/index", ["exports", "ember-testing/lib/test", "ember-testing/lib/adapters/adapter", "ember-testing/lib/setup_for_testing", "ember-testing/lib/adapters/qunit", "ember-testing/lib/support", "ember-testing/lib/ext/application", "ember-testing/lib/ext/rsvp", "ember-testing/lib/helpers", "ember-testing/lib/initializers"], function (_exports, _test, _adapter, _setup_for_testing, _qunit, _support, _application, _rsvp, _helpers, _initializers) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "Test", {
enumerable: true,
get: function () {
return _test.default;
}
});
Object.defineProperty(_exports, "Adapter", {
enumerable: true,
get: function () {
return _adapter.default;
}
});
Object.defineProperty(_exports, "setupForTesting", {
enumerable: true,
get: function () {
return _setup_for_testing.default;
}
});
Object.defineProperty(_exports, "QUnitAdapter", {
enumerable: true,
get: function () {
return _qunit.default;
}
});
});
define("ember-testing/lib/adapters/adapter", ["exports", "@ember/-internals/runtime"], function (_exports, _runtime) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function K() {
return this;
}
/**
@module @ember/test
*/
/**
The primary purpose of this class is to create hooks that can be implemented
by an adapter for various test frameworks.
@class TestAdapter
@public
*/
var _default = _runtime.Object.extend({
/**
This callback will be called whenever an async operation is about to start.
Override this to call your framework's methods that handle async
operations.
@public
@method asyncStart
*/
asyncStart: K,
/**
This callback will be called whenever an async operation has completed.
@public
@method asyncEnd
*/
asyncEnd: K,
/**
Override this method with your testing framework's false assertion.
This function is called whenever an exception occurs causing the testing
promise to fail.
QUnit example:
```javascript
exception: function(error) {
ok(false, error);
};
```
@public
@method exception
@param {String} error The exception to be raised.
*/
exception(error) {
throw error;
}
});
_exports.default = _default;
});
define("ember-testing/lib/adapters/qunit", ["exports", "@ember/-internals/utils", "ember-testing/lib/adapters/adapter"], function (_exports, _utils, _adapter) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/* globals QUnit */
/**
@module ember
*/
/**
This class implements the methods defined by TestAdapter for the
QUnit testing framework.
@class QUnitAdapter
@namespace Ember.Test
@extends TestAdapter
@public
*/
var _default = _adapter.default.extend({
init() {
this.doneCallbacks = [];
},
asyncStart() {
if (typeof QUnit.stop === 'function') {
// very old QUnit version
QUnit.stop();
} else {
this.doneCallbacks.push(QUnit.config.current ? QUnit.config.current.assert.async() : null);
}
},
asyncEnd() {
// checking for QUnit.stop here (even though we _need_ QUnit.start) because
// QUnit.start() still exists in QUnit 2.x (it just throws an error when calling
// inside a test context)
if (typeof QUnit.stop === 'function') {
QUnit.start();
} else {
var done = this.doneCallbacks.pop(); // This can be null if asyncStart() was called outside of a test
if (done) {
done();
}
}
},
exception(error) {
QUnit.config.current.assert.ok(false, (0, _utils.inspect)(error));
}
});
_exports.default = _default;
});
define("ember-testing/lib/events", ["exports", "@ember/runloop", "@ember/polyfills", "ember-testing/lib/helpers/-is-form-control"], function (_exports, _runloop, _polyfills, _isFormControl) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.focus = focus;
_exports.fireEvent = fireEvent;
var DEFAULT_EVENT_OPTIONS = {
canBubble: true,
cancelable: true
};
var KEYBOARD_EVENT_TYPES = ['keydown', 'keypress', 'keyup'];
var MOUSE_EVENT_TYPES = ['click', 'mousedown', 'mouseup', 'dblclick', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover'];
function focus(el) {
if (!el) {
return;
}
if (el.isContentEditable || (0, _isFormControl.default)(el)) {
var type = el.getAttribute('type');
if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') {
(0, _runloop.run)(null, function () {
var browserIsNotFocused = document.hasFocus && !document.hasFocus(); // makes `document.activeElement` be `element`. If the browser is focused, it also fires a focus event
el.focus(); // Firefox does not trigger the `focusin` event if the window
// does not have focus. If the document does not have focus then
// fire `focusin` event as well.
if (browserIsNotFocused) {
// if the browser is not focused the previous `el.focus()` didn't fire an event, so we simulate it
fireEvent(el, 'focus', {
bubbles: false
});
fireEvent(el, 'focusin');
}
});
}
}
}
function fireEvent(element, type, options = {}) {
if (!element) {
return;
}
var event;
if (KEYBOARD_EVENT_TYPES.indexOf(type) > -1) {
event = buildKeyboardEvent(type, options);
} else if (MOUSE_EVENT_TYPES.indexOf(type) > -1) {
var rect = element.getBoundingClientRect();
var x = rect.left + 1;
var y = rect.top + 1;
var simulatedCoordinates = {
screenX: x + 5,
screenY: y + 95,
clientX: x,
clientY: y
};
event = buildMouseEvent(type, (0, _polyfills.assign)(simulatedCoordinates, options));
} else {
event = buildBasicEvent(type, options);
}
element.dispatchEvent(event);
}
function buildBasicEvent(type, options = {}) {
var event = document.createEvent('Events'); // Event.bubbles is read only
var bubbles = options.bubbles !== undefined ? options.bubbles : true;
var cancelable = options.cancelable !== undefined ? options.cancelable : true;
delete options.bubbles;
delete options.cancelable;
event.initEvent(type, bubbles, cancelable);
(0, _polyfills.assign)(event, options);
return event;
}
function buildMouseEvent(type, options = {}) {
var event;
try {
event = document.createEvent('MouseEvents');
var eventOpts = (0, _polyfills.assign)({}, DEFAULT_EVENT_OPTIONS, options);
event.initMouseEvent(type, eventOpts.canBubble, eventOpts.cancelable, window, eventOpts.detail, eventOpts.screenX, eventOpts.screenY, eventOpts.clientX, eventOpts.clientY, eventOpts.ctrlKey, eventOpts.altKey, eventOpts.shiftKey, eventOpts.metaKey, eventOpts.button, eventOpts.relatedTarget);
} catch (e) {
event = buildBasicEvent(type, options);
}
return event;
}
function buildKeyboardEvent(type, options = {}) {
var event;
try {
event = document.createEvent('KeyEvents');
var eventOpts = (0, _polyfills.assign)({}, DEFAULT_EVENT_OPTIONS, options);
event.initKeyEvent(type, eventOpts.canBubble, eventOpts.cancelable, window, eventOpts.ctrlKey, eventOpts.altKey, eventOpts.shiftKey, eventOpts.metaKey, eventOpts.keyCode, eventOpts.charCode);
} catch (e) {
event = buildBasicEvent(type, options);
}
return event;
}
});
define("ember-testing/lib/ext/application", ["@ember/application", "ember-testing/lib/setup_for_testing", "ember-testing/lib/test/helpers", "ember-testing/lib/test/promise", "ember-testing/lib/test/run", "ember-testing/lib/test/on_inject_helpers", "ember-testing/lib/test/adapter"], function (_application, _setup_for_testing, _helpers, _promise, _run, _on_inject_helpers, _adapter) {
"use strict";
_application.default.reopen({
/**
This property contains the testing helpers for the current application. These
are created once you call `injectTestHelpers` on your `Application`
instance. The included helpers are also available on the `window` object by
default, but can be used from this object on the individual application also.
@property testHelpers
@type {Object}
@default {}
@public
*/
testHelpers: {},
/**
This property will contain the original methods that were registered
on the `helperContainer` before `injectTestHelpers` is called.
When `removeTestHelpers` is called, these methods are restored to the
`helperContainer`.
@property originalMethods
@type {Object}
@default {}
@private
@since 1.3.0
*/
originalMethods: {},
/**
This property indicates whether or not this application is currently in
testing mode. This is set when `setupForTesting` is called on the current
application.
@property testing
@type {Boolean}
@default false
@since 1.3.0
@public
*/
testing: false,
/**
This hook defers the readiness of the application, so that you can start
the app when your tests are ready to run. It also sets the router's
location to 'none', so that the window's location will not be modified
(preventing both accidental leaking of state between tests and interference
with your testing framework). `setupForTesting` should only be called after
setting a custom `router` class (for example `App.Router = Router.extend(`).
Example:
```
App.setupForTesting();
```
@method setupForTesting
@public
*/
setupForTesting() {
(0, _setup_for_testing.default)();
this.testing = true;
this.resolveRegistration('router:main').reopen({
location: 'none'
});
},
/**
This will be used as the container to inject the test helpers into. By
default the helpers are injected into `window`.
@property helperContainer
@type {Object} The object to be used for test helpers.
@default window
@since 1.2.0
@private
*/
helperContainer: null,
/**
This injects the test helpers into the `helperContainer` object. If an object is provided
it will be used as the helperContainer. If `helperContainer` is not set it will default
to `window`. If a function of the same name has already been defined it will be cached
(so that it can be reset if the helper is removed with `unregisterHelper` or
`removeTestHelpers`).
Any callbacks registered with `onInjectHelpers` will be called once the
helpers have been injected.
Example:
```
App.injectTestHelpers();
```
@method injectTestHelpers
@public
*/
injectTestHelpers(helperContainer) {
if (helperContainer) {
this.helperContainer = helperContainer;
} else {
this.helperContainer = window;
}
this.reopen({
willDestroy() {
this._super(...arguments);
this.removeTestHelpers();
}
});
this.testHelpers = {};
for (var name in _helpers.helpers) {
this.originalMethods[name] = this.helperContainer[name];
this.testHelpers[name] = this.helperContainer[name] = helper(this, name);
protoWrap(_promise.default.prototype, name, helper(this, name), _helpers.helpers[name].meta.wait);
}
(0, _on_inject_helpers.invokeInjectHelpersCallbacks)(this);
},
/**
This removes all helpers that have been registered, and resets and functions
that were overridden by the helpers.
Example:
```javascript
App.removeTestHelpers();
```
@public
@method removeTestHelpers
*/
removeTestHelpers() {
if (!this.helperContainer) {
return;
}
for (var name in _helpers.helpers) {
this.helperContainer[name] = this.originalMethods[name];
delete _promise.default.prototype[name];
delete this.testHelpers[name];
delete this.originalMethods[name];
}
}
}); // This method is no longer needed
// But still here for backwards compatibility
// of helper chaining
function protoWrap(proto, name, callback, isAsync) {
proto[name] = function (...args) {
if (isAsync) {
return callback.apply(this, args);
} else {
return this.then(function () {
return callback.apply(this, args);
});
}
};
}
function helper(app, name) {
var fn = _helpers.helpers[name].method;
var meta = _helpers.helpers[name].meta;
if (!meta.wait) {
return (...args) => fn.apply(app, [app, ...args]);
}
return (...args) => {
var lastPromise = (0, _run.default)(() => (0, _promise.resolve)((0, _promise.getLastPromise)())); // wait for last helper's promise to resolve and then
// execute. To be safe, we need to tell the adapter we're going
// asynchronous here, because fn may not be invoked before we
// return.
(0, _adapter.asyncStart)();
return lastPromise.then(() => fn.apply(app, [app, ...args])).finally(_adapter.asyncEnd);
};
}
});
define("ember-testing/lib/ext/rsvp", ["exports", "@ember/-internals/runtime", "@ember/runloop", "@ember/debug", "ember-testing/lib/test/adapter"], function (_exports, _runtime, _runloop, _debug, _adapter) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_runtime.RSVP.configure('async', function (callback, promise) {
// if schedule will cause autorun, we need to inform adapter
if ((0, _debug.isTesting)() && !_runloop.backburner.currentInstance) {
(0, _adapter.asyncStart)();
_runloop.backburner.schedule('actions', () => {
(0, _adapter.asyncEnd)();
callback(promise);
});
} else {
_runloop.backburner.schedule('actions', () => callback(promise));
}
});
var _default = _runtime.RSVP;
_exports.default = _default;
});
define("ember-testing/lib/helpers", ["ember-testing/lib/test/helpers", "ember-testing/lib/helpers/and_then", "ember-testing/lib/helpers/click", "ember-testing/lib/helpers/current_path", "ember-testing/lib/helpers/current_route_name", "ember-testing/lib/helpers/current_url", "ember-testing/lib/helpers/fill_in", "ember-testing/lib/helpers/find", "ember-testing/lib/helpers/find_with_assert", "ember-testing/lib/helpers/key_event", "ember-testing/lib/helpers/pause_test", "ember-testing/lib/helpers/trigger_event", "ember-testing/lib/helpers/visit", "ember-testing/lib/helpers/wait"], function (_helpers, _and_then, _click, _current_path, _current_route_name, _current_url, _fill_in, _find, _find_with_assert, _key_event, _pause_test, _trigger_event, _visit, _wait) {
"use strict";
(0, _helpers.registerAsyncHelper)('visit', _visit.default);
(0, _helpers.registerAsyncHelper)('click', _click.default);
(0, _helpers.registerAsyncHelper)('keyEvent', _key_event.default);
(0, _helpers.registerAsyncHelper)('fillIn', _fill_in.default);
(0, _helpers.registerAsyncHelper)('wait', _wait.default);
(0, _helpers.registerAsyncHelper)('andThen', _and_then.default);
(0, _helpers.registerAsyncHelper)('pauseTest', _pause_test.pauseTest);
(0, _helpers.registerAsyncHelper)('triggerEvent', _trigger_event.default);
(0, _helpers.registerHelper)('find', _find.default);
(0, _helpers.registerHelper)('findWithAssert', _find_with_assert.default);
(0, _helpers.registerHelper)('currentRouteName', _current_route_name.default);
(0, _helpers.registerHelper)('currentPath', _current_path.default);
(0, _helpers.registerHelper)('currentURL', _current_url.default);
(0, _helpers.registerHelper)('resumeTest', _pause_test.resumeTest);
});
define("ember-testing/lib/helpers/-is-form-control", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = isFormControl;
var FORM_CONTROL_TAGS = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'];
/**
@private
@param {Element} element the element to check
@returns {boolean} `true` when the element is a form control, `false` otherwise
*/
function isFormControl(element) {
var {
tagName,
type
} = element;
if (type === 'hidden') {
return false;
}
return FORM_CONTROL_TAGS.indexOf(tagName) > -1;
}
});
define("ember-testing/lib/helpers/and_then", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = andThen;
function andThen(app, callback) {
return app.testHelpers.wait(callback(app));
}
});
define("ember-testing/lib/helpers/click", ["exports", "ember-testing/lib/events"], function (_exports, _events) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = click;
/**
@module ember
*/
/**
Clicks an element and triggers any actions triggered by the element's `click`
event.
Example:
```javascript
click('.some-jQuery-selector').then(function() {
// assert something
});
```
@method click
@param {String} selector jQuery selector for finding element on the DOM
@param {Object} context A DOM Element, Document, or jQuery to use as context
@return {RSVP.Promise
}
@public
*/
function click(app, selector, context) {
var $el = app.testHelpers.findWithAssert(selector, context);
var el = $el[0];
(0, _events.fireEvent)(el, 'mousedown');
(0, _events.focus)(el);
(0, _events.fireEvent)(el, 'mouseup');
(0, _events.fireEvent)(el, 'click');
return app.testHelpers.wait();
}
});
define("ember-testing/lib/helpers/current_path", ["exports", "@ember/-internals/metal"], function (_exports, _metal) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = currentPath;
/**
@module ember
*/
/**
Returns the current path.
Example:
```javascript
function validateURL() {
equal(currentPath(), 'some.path.index', "correct path was transitioned into.");
}
click('#some-link-id').then(validateURL);
```
@method currentPath
@return {Object} The currently active path.
@since 1.5.0
@public
*/
function currentPath(app) {
var routingService = app.__container__.lookup('service:-routing');
return (0, _metal.get)(routingService, 'currentPath');
}
});
define("ember-testing/lib/helpers/current_route_name", ["exports", "@ember/-internals/metal"], function (_exports, _metal) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = currentRouteName;
/**
@module ember
*/
/**
Returns the currently active route name.
Example:
```javascript
function validateRouteName() {
equal(currentRouteName(), 'some.path', "correct route was transitioned into.");
}
visit('/some/path').then(validateRouteName)
```
@method currentRouteName
@return {Object} The name of the currently active route.
@since 1.5.0
@public
*/
function currentRouteName(app) {
var routingService = app.__container__.lookup('service:-routing');
return (0, _metal.get)(routingService, 'currentRouteName');
}
});
define("ember-testing/lib/helpers/current_url", ["exports", "@ember/-internals/metal"], function (_exports, _metal) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = currentURL;
/**
@module ember
*/
/**
Returns the current URL.
Example:
```javascript
function validateURL() {
equal(currentURL(), '/some/path', "correct URL was transitioned into.");
}
click('#some-link-id').then(validateURL);
```
@method currentURL
@return {Object} The currently active URL.
@since 1.5.0
@public
*/
function currentURL(app) {
var router = app.__container__.lookup('router:main');
return (0, _metal.get)(router, 'location').getURL();
}
});
define("ember-testing/lib/helpers/fill_in", ["exports", "ember-testing/lib/events", "ember-testing/lib/helpers/-is-form-control"], function (_exports, _events, _isFormControl) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = fillIn;
/**
@module ember
*/
/**
Fills in an input element with some text.
Example:
```javascript
fillIn('#email', 'you@example.com').then(function() {
// assert something
});
```
@method fillIn
@param {String} selector jQuery selector finding an input element on the DOM
to fill text with
@param {String} text text to place inside the input element
@return {RSVP.Promise}
@public
*/
function fillIn(app, selector, contextOrText, text) {
var $el, el, context;
if (text === undefined) {
text = contextOrText;
} else {
context = contextOrText;
}
$el = app.testHelpers.findWithAssert(selector, context);
el = $el[0];
(0, _events.focus)(el);
if ((0, _isFormControl.default)(el)) {
el.value = text;
} else {
el.innerHTML = text;
}
(0, _events.fireEvent)(el, 'input');
(0, _events.fireEvent)(el, 'change');
return app.testHelpers.wait();
}
});
define("ember-testing/lib/helpers/find", ["exports", "@ember/-internals/metal", "@ember/debug", "@ember/-internals/views"], function (_exports, _metal, _debug, _views) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = find;
/**
@module ember
*/
/**
Finds an element in the context of the app's container element. A simple alias
for `app.$(selector)`.
Example:
```javascript
var $el = find('.my-selector');
```
With the `context` param:
```javascript
var $el = find('.my-selector', '.parent-element-class');
```
@method find
@param {String} selector jQuery selector for element lookup
@param {String} [context] (optional) jQuery selector that will limit the selector
argument to find only within the context's children
@return {Object} DOM element representing the results of the query
@public
*/
function find(app, selector, context) {
if (_views.jQueryDisabled) {
(true && !(false) && (0, _debug.assert)('If jQuery is disabled, please import and use helpers from @ember/test-helpers [https://github.com/emberjs/ember-test-helpers]. Note: `find` is not an available helper.'));
}
var $el;
context = context || (0, _metal.get)(app, 'rootElement');
$el = app.$(selector, context);
return $el;
}
});
define("ember-testing/lib/helpers/find_with_assert", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = findWithAssert;
/**
@module ember
*/
/**
Like `find`, but throws an error if the element selector returns no results.
Example:
```javascript
var $el = findWithAssert('.doesnt-exist'); // throws error
```
With the `context` param:
```javascript
var $el = findWithAssert('.selector-id', '.parent-element-class'); // assert will pass
```
@method findWithAssert
@param {String} selector jQuery selector string for finding an element within
the DOM
@param {String} [context] (optional) jQuery selector that will limit the
selector argument to find only within the context's children
@return {Object} jQuery object representing the results of the query
@throws {Error} throws error if object returned has a length of 0
@public
*/
function findWithAssert(app, selector, context) {
var $el = app.testHelpers.find(selector, context);
if ($el.length === 0) {
throw new Error('Element ' + selector + ' not found.');
}
return $el;
}
});
define("ember-testing/lib/helpers/key_event", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = keyEvent;
/**
@module ember
*/
/**
Simulates a key event, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode
Example:
```javascript
keyEvent('.some-jQuery-selector', 'keypress', 13).then(function() {
// assert something
});
```
@method keyEvent
@param {String} selector jQuery selector for finding element on the DOM
@param {String} type the type of key event, e.g. `keypress`, `keydown`, `keyup`
@param {Number} keyCode the keyCode of the simulated key event
@return {RSVP.Promise}
@since 1.5.0
@public
*/
function keyEvent(app, selector, contextOrType, typeOrKeyCode, keyCode) {
var context, type;
if (keyCode === undefined) {
context = null;
keyCode = typeOrKeyCode;
type = contextOrType;
} else {
context = contextOrType;
type = typeOrKeyCode;
}
return app.testHelpers.triggerEvent(selector, context, type, {
keyCode,
which: keyCode
});
}
});
define("ember-testing/lib/helpers/pause_test", ["exports", "@ember/-internals/runtime", "@ember/debug"], function (_exports, _runtime, _debug) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.resumeTest = resumeTest;
_exports.pauseTest = pauseTest;
/**
@module ember
*/
var resume;
/**
Resumes a test paused by `pauseTest`.
@method resumeTest
@return {void}
@public
*/
function resumeTest() {
(true && !(resume) && (0, _debug.assert)('Testing has not been paused. There is nothing to resume.', resume));
resume();
resume = undefined;
}
/**
Pauses the current test - this is useful for debugging while testing or for test-driving.
It allows you to inspect the state of your application at any point.
Example (The test will pause before clicking the button):
```javascript
visit('/')
return pauseTest();
click('.btn');
```
You may want to turn off the timeout before pausing.
qunit (timeout available to use as of 2.4.0):
```
visit('/');
assert.timeout(0);
return pauseTest();
click('.btn');
```
mocha (timeout happens automatically as of ember-mocha v0.14.0):
```
visit('/');
this.timeout(0);
return pauseTest();
click('.btn');
```
@since 1.9.0
@method pauseTest
@return {Object} A promise that will never resolve
@public
*/
function pauseTest() {
(0, _debug.info)('Testing paused. Use `resumeTest()` to continue.');
return new _runtime.RSVP.Promise(resolve => {
resume = resolve;
}, 'TestAdapter paused promise');
}
});
define("ember-testing/lib/helpers/trigger_event", ["exports", "ember-testing/lib/events"], function (_exports, _events) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = triggerEvent;
/**
@module ember
*/
/**
Triggers the given DOM event on the element identified by the provided selector.
Example:
```javascript
triggerEvent('#some-elem-id', 'blur');
```
This is actually used internally by the `keyEvent` helper like so:
```javascript
triggerEvent('#some-elem-id', 'keypress', { keyCode: 13 });
```
@method triggerEvent
@param {String} selector jQuery selector for finding element on the DOM
@param {String} [context] jQuery selector that will limit the selector
argument to find only within the context's children
@param {String} type The event type to be triggered.
@param {Object} [options] The options to be passed to jQuery.Event.
@return {RSVP.Promise}
@since 1.5.0
@public
*/
function triggerEvent(app, selector, contextOrType, typeOrOptions, possibleOptions) {
var arity = arguments.length;
var context, type, options;
if (arity === 3) {
// context and options are optional, so this is
// app, selector, type
context = null;
type = contextOrType;
options = {};
} else if (arity === 4) {
// context and options are optional, so this is
if (typeof typeOrOptions === 'object') {
// either
// app, selector, type, options
context = null;
type = contextOrType;
options = typeOrOptions;
} else {
// or
// app, selector, context, type
context = contextOrType;
type = typeOrOptions;
options = {};
}
} else {
context = contextOrType;
type = typeOrOptions;
options = possibleOptions;
}
var $el = app.testHelpers.findWithAssert(selector, context);
var el = $el[0];
(0, _events.fireEvent)(el, type, options);
return app.testHelpers.wait();
}
});
define("ember-testing/lib/helpers/visit", ["exports", "@ember/runloop"], function (_exports, _runloop) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = visit;
/**
Loads a route, sets up any controllers, and renders any templates associated
with the route as though a real user had triggered the route change while
using your app.
Example:
```javascript
visit('posts/index').then(function() {
// assert something
});
```
@method visit
@param {String} url the name of the route
@return {RSVP.Promise}
@public
*/
function visit(app, url) {
var router = app.__container__.lookup('router:main');
var shouldHandleURL = false;
app.boot().then(() => {
router.location.setURL(url);
if (shouldHandleURL) {
(0, _runloop.run)(app.__deprecatedInstance__, 'handleURL', url);
}
});
if (app._readinessDeferrals > 0) {
router.initialURL = url;
(0, _runloop.run)(app, 'advanceReadiness');
delete router.initialURL;
} else {
shouldHandleURL = true;
}
return app.testHelpers.wait();
}
});
define("ember-testing/lib/helpers/wait", ["exports", "ember-testing/lib/test/waiters", "@ember/-internals/runtime", "@ember/runloop", "ember-testing/lib/test/pending_requests"], function (_exports, _waiters, _runtime, _runloop, _pending_requests) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = wait;
/**
@module ember
*/
/**
Causes the run loop to process any pending events. This is used to ensure that
any async operations from other helpers (or your assertions) have been processed.
This is most often used as the return value for the helper functions (see 'click',
'fillIn','visit',etc). However, there is a method to register a test helper which
utilizes this method without the need to actually call `wait()` in your helpers.
The `wait` helper is built into `registerAsyncHelper` by default. You will not need
to `return app.testHelpers.wait();` - the wait behavior is provided for you.
Example:
```javascript
import { registerAsyncHelper } from '@ember/test';
registerAsyncHelper('loginUser', function(app, username, password) {
visit('secured/path/here')
.fillIn('#username', username)
.fillIn('#password', password)
.click('.submit');
});
```
@method wait
@param {Object} value The value to be returned.
@return {RSVP.Promise} Promise that resolves to the passed value.
@public
@since 1.0.0
*/
function wait(app, value) {
return new _runtime.RSVP.Promise(function (resolve) {
var router = app.__container__.lookup('router:main'); // Every 10ms, poll for the async thing to have finished
var watcher = setInterval(() => {
// 1. If the router is loading, keep polling
var routerIsLoading = router._routerMicrolib && Boolean(router._routerMicrolib.activeTransition);
if (routerIsLoading) {
return;
} // 2. If there are pending Ajax requests, keep polling
if ((0, _pending_requests.pendingRequests)()) {
return;
} // 3. If there are scheduled timers or we are inside of a run loop, keep polling
if ((0, _runloop.hasScheduledTimers)() || (0, _runloop.getCurrentRunLoop)()) {
return;
}
if ((0, _waiters.checkWaiters)()) {
return;
} // Stop polling
clearInterval(watcher); // Synchronously resolve the promise
(0, _runloop.run)(null, resolve, value);
}, 10);
});
}
});
define("ember-testing/lib/initializers", ["@ember/application"], function (_application) {
"use strict";
var name = 'deferReadiness in `testing` mode';
(0, _application.onLoad)('Ember.Application', function (Application) {
if (!Application.initializers[name]) {
Application.initializer({
name: name,
initialize(application) {
if (application.testing) {
application.deferReadiness();
}
}
});
}
});
});
define("ember-testing/lib/setup_for_testing", ["exports", "@ember/debug", "@ember/-internals/views", "ember-testing/lib/test/adapter", "ember-testing/lib/test/pending_requests", "ember-testing/lib/adapters/adapter", "ember-testing/lib/adapters/qunit"], function (_exports, _debug, _views, _adapter, _pending_requests, _adapter2, _qunit) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = setupForTesting;
/* global self */
/**
Sets Ember up for testing. This is useful to perform
basic setup steps in order to unit test.
Use `App.setupForTesting` to perform integration tests (full
application testing).
@method setupForTesting
@namespace Ember
@since 1.5.0
@private
*/
function setupForTesting() {
(0, _debug.setTesting)(true);
var adapter = (0, _adapter.getAdapter)(); // if adapter is not manually set default to QUnit
if (!adapter) {
(0, _adapter.setAdapter)(typeof self.QUnit === 'undefined' ? _adapter2.default.create() : _qunit.default.create());
}
if (!_views.jQueryDisabled) {
(0, _views.jQuery)(document).off('ajaxSend', _pending_requests.incrementPendingRequests);
(0, _views.jQuery)(document).off('ajaxComplete', _pending_requests.decrementPendingRequests);
(0, _pending_requests.clearPendingRequests)();
(0, _views.jQuery)(document).on('ajaxSend', _pending_requests.incrementPendingRequests);
(0, _views.jQuery)(document).on('ajaxComplete', _pending_requests.decrementPendingRequests);
}
}
});
define("ember-testing/lib/support", ["@ember/debug", "@ember/-internals/views", "@ember/-internals/browser-environment"], function (_debug, _views, _browserEnvironment) {
"use strict";
/**
@module ember
*/
var $ = _views.jQuery;
/**
This method creates a checkbox and triggers the click event to fire the
passed in handler. It is used to correct for a bug in older versions
of jQuery (e.g 1.8.3).
@private
@method testCheckboxClick
*/
function testCheckboxClick(handler) {
var input = document.createElement('input');
$(input).attr('type', 'checkbox').css({
position: 'absolute',
left: '-1000px',
top: '-1000px'
}).appendTo('body').on('click', handler).trigger('click').remove();
}
if (_browserEnvironment.hasDOM && !_views.jQueryDisabled) {
$(function () {
/*
Determine whether a checkbox checked using jQuery's "click" method will have
the correct value for its checked property.
If we determine that the current jQuery version exhibits this behavior,
patch it to work correctly as in the commit for the actual fix:
https://github.com/jquery/jquery/commit/1fb2f92.
*/
testCheckboxClick(function () {
if (!this.checked && !$.event.special.click) {
$.event.special.click = {
// For checkbox, fire native event so checked state will be right
trigger() {
if (this.nodeName === 'INPUT' && this.type === 'checkbox' && this.click) {
this.click();
return false;
}
}
};
}
}); // Try again to verify that the patch took effect or blow up.
testCheckboxClick(function () {
(true && (0, _debug.warn)("clicked checkboxes should be checked! the jQuery patch didn't work", this.checked, {
id: 'ember-testing.test-checkbox-click'
}));
});
});
}
});
define("ember-testing/lib/test", ["exports", "ember-testing/lib/test/helpers", "ember-testing/lib/test/on_inject_helpers", "ember-testing/lib/test/promise", "ember-testing/lib/test/waiters", "ember-testing/lib/test/adapter"], function (_exports, _helpers, _on_inject_helpers, _promise, _waiters, _adapter) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module ember
*/
/**
This is a container for an assortment of testing related functionality:
* Choose your default test adapter (for your framework of choice).
* Register/Unregister additional test helpers.
* Setup callbacks to be fired when the test helpers are injected into
your application.
@class Test
@namespace Ember
@public
*/
var Test = {
/**
Hash containing all known test helpers.
@property _helpers
@private
@since 1.7.0
*/
_helpers: _helpers.helpers,
registerHelper: _helpers.registerHelper,
registerAsyncHelper: _helpers.registerAsyncHelper,
unregisterHelper: _helpers.unregisterHelper,
onInjectHelpers: _on_inject_helpers.onInjectHelpers,
Promise: _promise.default,
promise: _promise.promise,
resolve: _promise.resolve,
registerWaiter: _waiters.registerWaiter,
unregisterWaiter: _waiters.unregisterWaiter,
checkWaiters: _waiters.checkWaiters
};
/**
Used to allow ember-testing to communicate with a specific testing
framework.
You can manually set it before calling `App.setupForTesting()`.
Example:
```javascript
Ember.Test.adapter = MyCustomAdapter.create()
```
If you do not set it, ember-testing will default to `Ember.Test.QUnitAdapter`.
@public
@for Ember.Test
@property adapter
@type {Class} The adapter to be used.
@default Ember.Test.QUnitAdapter
*/
Object.defineProperty(Test, 'adapter', {
get: _adapter.getAdapter,
set: _adapter.setAdapter
});
var _default = Test;
_exports.default = _default;
});
define("ember-testing/lib/test/adapter", ["exports", "@ember/-internals/error-handling"], function (_exports, _errorHandling) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.getAdapter = getAdapter;
_exports.setAdapter = setAdapter;
_exports.asyncStart = asyncStart;
_exports.asyncEnd = asyncEnd;
var adapter;
function getAdapter() {
return adapter;
}
function setAdapter(value) {
adapter = value;
if (value && typeof value.exception === 'function') {
(0, _errorHandling.setDispatchOverride)(adapterDispatch);
} else {
(0, _errorHandling.setDispatchOverride)(null);
}
}
function asyncStart() {
if (adapter) {
adapter.asyncStart();
}
}
function asyncEnd() {
if (adapter) {
adapter.asyncEnd();
}
}
function adapterDispatch(error) {
adapter.exception(error);
console.error(error.stack); // eslint-disable-line no-console
}
});
define("ember-testing/lib/test/helpers", ["exports", "ember-testing/lib/test/promise"], function (_exports, _promise) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.registerHelper = registerHelper;
_exports.registerAsyncHelper = registerAsyncHelper;
_exports.unregisterHelper = unregisterHelper;
_exports.helpers = void 0;
var helpers = {};
/**
@module @ember/test
*/
/**
`registerHelper` is used to register a test helper that will be injected
when `App.injectTestHelpers` is called.
The helper method will always be called with the current Application as
the first parameter.
For example:
```javascript
import { registerHelper } from '@ember/test';
import { run } from '@ember/runloop';
registerHelper('boot', function(app) {
run(app, app.advanceReadiness);
});
```
This helper can later be called without arguments because it will be
called with `app` as the first parameter.
```javascript
import Application from '@ember/application';
App = Application.create();
App.injectTestHelpers();
boot();
```
@public
@for @ember/test
@static
@method registerHelper
@param {String} name The name of the helper method to add.
@param {Function} helperMethod
@param options {Object}
*/
_exports.helpers = helpers;
function registerHelper(name, helperMethod) {
helpers[name] = {
method: helperMethod,
meta: {
wait: false
}
};
}
/**
`registerAsyncHelper` is used to register an async test helper that will be injected
when `App.injectTestHelpers` is called.
The helper method will always be called with the current Application as
the first parameter.
For example:
```javascript
import { registerAsyncHelper } from '@ember/test';
import { run } from '@ember/runloop';
registerAsyncHelper('boot', function(app) {
run(app, app.advanceReadiness);
});
```
The advantage of an async helper is that it will not run
until the last async helper has completed. All async helpers
after it will wait for it complete before running.
For example:
```javascript
import { registerAsyncHelper } from '@ember/test';
registerAsyncHelper('deletePost', function(app, postId) {
click('.delete-' + postId);
});
// ... in your test
visit('/post/2');
deletePost(2);
visit('/post/3');
deletePost(3);
```
@public
@for @ember/test
@method registerAsyncHelper
@param {String} name The name of the helper method to add.
@param {Function} helperMethod
@since 1.2.0
*/
function registerAsyncHelper(name, helperMethod) {
helpers[name] = {
method: helperMethod,
meta: {
wait: true
}
};
}
/**
Remove a previously added helper method.
Example:
```javascript
import { unregisterHelper } from '@ember/test';
unregisterHelper('wait');
```
@public
@method unregisterHelper
@static
@for @ember/test
@param {String} name The helper to remove.
*/
function unregisterHelper(name) {
delete helpers[name];
delete _promise.default.prototype[name];
}
});
define("ember-testing/lib/test/on_inject_helpers", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.onInjectHelpers = onInjectHelpers;
_exports.invokeInjectHelpersCallbacks = invokeInjectHelpersCallbacks;
_exports.callbacks = void 0;
var callbacks = [];
/**
Used to register callbacks to be fired whenever `App.injectTestHelpers`
is called.
The callback will receive the current application as an argument.
Example:
```javascript
import $ from 'jquery';
Ember.Test.onInjectHelpers(function() {
$(document).ajaxSend(function() {
Test.pendingRequests++;
});
$(document).ajaxComplete(function() {
Test.pendingRequests--;
});
});
```
@public
@for Ember.Test
@method onInjectHelpers
@param {Function} callback The function to be called.
*/
_exports.callbacks = callbacks;
function onInjectHelpers(callback) {
callbacks.push(callback);
}
function invokeInjectHelpersCallbacks(app) {
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](app);
}
}
});
define("ember-testing/lib/test/pending_requests", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.pendingRequests = pendingRequests;
_exports.clearPendingRequests = clearPendingRequests;
_exports.incrementPendingRequests = incrementPendingRequests;
_exports.decrementPendingRequests = decrementPendingRequests;
var requests = [];
function pendingRequests() {
return requests.length;
}
function clearPendingRequests() {
requests.length = 0;
}
function incrementPendingRequests(_, xhr) {
requests.push(xhr);
}
function decrementPendingRequests(_, xhr) {
setTimeout(function () {
for (var i = 0; i < requests.length; i++) {
if (xhr === requests[i]) {
requests.splice(i, 1);
break;
}
}
}, 0);
}
});
define("ember-testing/lib/test/promise", ["exports", "@ember/-internals/runtime", "ember-testing/lib/test/run"], function (_exports, _runtime, _run) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.promise = promise;
_exports.resolve = resolve;
_exports.getLastPromise = getLastPromise;
_exports.default = void 0;
var lastPromise;
class TestPromise extends _runtime.RSVP.Promise {
constructor() {
super(...arguments);
lastPromise = this;
}
then(_onFulfillment, ...args) {
var onFulfillment = typeof _onFulfillment === 'function' ? result => isolate(_onFulfillment, result) : undefined;
return super.then(onFulfillment, ...args);
}
}
/**
This returns a thenable tailored for testing. It catches failed
`onSuccess` callbacks and invokes the `Ember.Test.adapter.exception`
callback in the last chained then.
This method should be returned by async helpers such as `wait`.
@public
@for Ember.Test
@method promise
@param {Function} resolver The function used to resolve the promise.
@param {String} label An optional string for identifying the promise.
*/
_exports.default = TestPromise;
function promise(resolver, label) {
var fullLabel = `Ember.Test.promise: ${label || ''}`;
return new TestPromise(resolver, fullLabel);
}
/**
Replacement for `Ember.RSVP.resolve`
The only difference is this uses
an instance of `Ember.Test.Promise`
@public
@for Ember.Test
@method resolve
@param {Mixed} The value to resolve
@since 1.2.0
*/
function resolve(result, label) {
return TestPromise.resolve(result, label);
}
function getLastPromise() {
return lastPromise;
} // This method isolates nested async methods
// so that they don't conflict with other last promises.
//
// 1. Set `Ember.Test.lastPromise` to null
// 2. Invoke method
// 3. Return the last promise created during method
function isolate(onFulfillment, result) {
// Reset lastPromise for nested helpers
lastPromise = null;
var value = onFulfillment(result);
var promise = lastPromise;
lastPromise = null; // If the method returned a promise
// return that promise. If not,
// return the last async helper's promise
if (value && value instanceof TestPromise || !promise) {
return value;
} else {
return (0, _run.default)(() => resolve(promise).then(() => value));
}
}
});
define("ember-testing/lib/test/run", ["exports", "@ember/runloop"], function (_exports, _runloop) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = run;
function run(fn) {
if (!(0, _runloop.getCurrentRunLoop)()) {
return (0, _runloop.run)(fn);
} else {
return fn();
}
}
});
define("ember-testing/lib/test/waiters", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.registerWaiter = registerWaiter;
_exports.unregisterWaiter = unregisterWaiter;
_exports.checkWaiters = checkWaiters;
/**
@module @ember/test
*/
var contexts = [];
var callbacks = [];
/**
This allows ember-testing to play nicely with other asynchronous
events, such as an application that is waiting for a CSS3
transition or an IndexDB transaction. The waiter runs periodically
after each async helper (i.e. `click`, `andThen`, `visit`, etc) has executed,
until the returning result is truthy. After the waiters finish, the next async helper
is executed and the process repeats.
For example:
```javascript
import { registerWaiter } from '@ember/test';
registerWaiter(function() {
return myPendingTransactions() === 0;
});
```
The `context` argument allows you to optionally specify the `this`
with which your callback will be invoked.
For example:
```javascript
import { registerWaiter } from '@ember/test';
registerWaiter(MyDB, MyDB.hasPendingTransactions);
```
@public
@for @ember/test
@static
@method registerWaiter
@param {Object} context (optional)
@param {Function} callback
@since 1.2.0
*/
function registerWaiter(context, callback) {
if (arguments.length === 1) {
callback = context;
context = null;
}
if (indexOf(context, callback) > -1) {
return;
}
contexts.push(context);
callbacks.push(callback);
}
/**
`unregisterWaiter` is used to unregister a callback that was
registered with `registerWaiter`.
@public
@for @ember/test
@static
@method unregisterWaiter
@param {Object} context (optional)
@param {Function} callback
@since 1.2.0
*/
function unregisterWaiter(context, callback) {
if (!callbacks.length) {
return;
}
if (arguments.length === 1) {
callback = context;
context = null;
}
var i = indexOf(context, callback);
if (i === -1) {
return;
}
contexts.splice(i, 1);
callbacks.splice(i, 1);
}
/**
Iterates through each registered test waiter, and invokes
its callback. If any waiter returns false, this method will return
true indicating that the waiters have not settled yet.
This is generally used internally from the acceptance/integration test
infrastructure.
@public
@for @ember/test
@static
@method checkWaiters
*/
function checkWaiters() {
if (!callbacks.length) {
return false;
}
for (var i = 0; i < callbacks.length; i++) {
var context = contexts[i];
var callback = callbacks[i];
if (!callback.call(context)) {
return true;
}
}
return false;
}
function indexOf(context, callback) {
for (var i = 0; i < callbacks.length; i++) {
if (callbacks[i] === callback && contexts[i] === context) {
return i;
}
}
return -1;
}
});
define("ember/index", ["exports", "require", "@ember/-internals/environment", "node-module", "@ember/-internals/utils", "@ember/-internals/container", "@ember/instrumentation", "@ember/-internals/meta", "@ember/-internals/metal", "@ember/canary-features", "@ember/debug", "backburner", "@ember/-internals/console", "@ember/controller", "@ember/controller/lib/controller_mixin", "@ember/string", "@ember/service", "@ember/object", "@ember/object/compat", "@ember/object/computed", "@ember/-internals/runtime", "@ember/-internals/glimmer", "ember/version", "@ember/-internals/views", "@ember/-internals/routing", "@ember/-internals/extension-support", "@ember/error", "@ember/runloop", "@ember/-internals/error-handling", "@ember/-internals/owner", "@ember/application", "@ember/application/globals-resolver", "@ember/application/instance", "@ember/engine", "@ember/engine/instance", "@ember/polyfills", "@ember/deprecated-features", "@ember/component/template-only"], function (_exports, _require, _environment, _nodeModule, utils, _container, instrumentation, _meta, metal, _canaryFeatures, EmberDebug, _backburner, _console, _controller, _controller_mixin, _string, _service, _object, _compat, _computed, _runtime, _glimmer, _version, views, routing, extensionSupport, _error, runloop, _errorHandling, _owner, _application, _globalsResolver, _instance, _engine, _instance2, _polyfills, _deprecatedFeatures, _templateOnly) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
// eslint-disable-next-line import/no-unresolved
// ****@ember/-internals/environment****
var Ember = typeof _environment.context.imports.Ember === 'object' && _environment.context.imports.Ember || {};
Ember.isNamespace = true;
Ember.toString = function () {
return 'Ember';
};
Object.defineProperty(Ember, 'ENV', {
get: _environment.getENV,
enumerable: false
});
Object.defineProperty(Ember, 'lookup', {
get: _environment.getLookup,
set: _environment.setLookup,
enumerable: false
});
if (_deprecatedFeatures.EMBER_EXTEND_PROTOTYPES) {
Object.defineProperty(Ember, 'EXTEND_PROTOTYPES', {
enumerable: false,
get() {
(true && !(false) && (0, EmberDebug.deprecate)('Accessing Ember.EXTEND_PROTOTYPES is deprecated, please migrate to Ember.ENV.EXTEND_PROTOTYPES', false, {
id: 'ember-env.old-extend-prototypes',
until: '4.0.0'
}));
return _environment.ENV.EXTEND_PROTOTYPES;
}
});
} // ****@ember/application****
Ember.getOwner = _owner.getOwner;
Ember.setOwner = _owner.setOwner;
Ember.Application = _application.default;
Ember.DefaultResolver = Ember.Resolver = _globalsResolver.default;
Ember.ApplicationInstance = _instance.default; // ****@ember/engine****
Ember.Engine = _engine.default;
Ember.EngineInstance = _instance2.default; // ****@ember/polyfills****
Ember.assign = _polyfills.assign;
Ember.merge = _polyfills.merge; // ****@ember/-internals/utils****
Ember.generateGuid = utils.generateGuid;
Ember.GUID_KEY = utils.GUID_KEY;
Ember.guidFor = utils.guidFor;
Ember.inspect = utils.inspect;
Ember.makeArray = utils.makeArray;
Ember.canInvoke = utils.canInvoke;
Ember.tryInvoke = utils.tryInvoke;
Ember.wrap = utils.wrap;
Ember.uuid = utils.uuid; // ****@ember/-internals/container****
Ember.Container = _container.Container;
Ember.Registry = _container.Registry; // ****@ember/debug****
Ember.assert = EmberDebug.assert;
Ember.warn = EmberDebug.warn;
Ember.debug = EmberDebug.debug;
Ember.deprecate = EmberDebug.deprecate;
Ember.deprecateFunc = EmberDebug.deprecateFunc;
Ember.runInDebug = EmberDebug.runInDebug; // ****@ember/error****
Ember.Error = _error.default;
/**
@public
@class Ember.Debug
*/
Ember.Debug = {
registerDeprecationHandler: EmberDebug.registerDeprecationHandler,
registerWarnHandler: EmberDebug.registerWarnHandler,
isComputed: metal.isComputed
}; // ****@ember/instrumentation****
Ember.instrument = instrumentation.instrument;
Ember.subscribe = instrumentation.subscribe;
Ember.Instrumentation = {
instrument: instrumentation.instrument,
subscribe: instrumentation.subscribe,
unsubscribe: instrumentation.unsubscribe,
reset: instrumentation.reset
}; // ****@ember/runloop****
// Using _globalsRun here so that mutating the function (adding
// `next`, `later`, etc to it) is only available in globals builds
Ember.run = runloop._globalsRun;
Ember.run.backburner = runloop.backburner;
Ember.run.begin = runloop.begin;
Ember.run.bind = runloop.bind;
Ember.run.cancel = runloop.cancel;
Ember.run.debounce = runloop.debounce;
Ember.run.end = runloop.end;
Ember.run.hasScheduledTimers = runloop.hasScheduledTimers;
Ember.run.join = runloop.join;
Ember.run.later = runloop.later;
Ember.run.next = runloop.next;
Ember.run.once = runloop.once;
Ember.run.schedule = runloop.schedule;
Ember.run.scheduleOnce = runloop.scheduleOnce;
Ember.run.throttle = runloop.throttle;
Ember.run.cancelTimers = runloop.cancelTimers;
Object.defineProperty(Ember.run, 'currentRunLoop', {
get: runloop.getCurrentRunLoop,
enumerable: false
}); // ****@ember/-internals/metal****
// Using _globalsComputed here so that mutating the function is only available
// in globals builds
var computed = metal._globalsComputed;
Ember.computed = computed;
Ember._descriptor = metal.nativeDescDecorator;
Ember._tracked = metal.tracked;
computed.alias = metal.alias;
Ember.cacheFor = metal.getCachedValueFor;
Ember.ComputedProperty = metal.ComputedProperty;
Object.defineProperty(Ember, '_setComputedDecorator', {
get() {
(true && !(false) && (0, EmberDebug.deprecate)('Please migrate from Ember._setComputedDecorator to Ember._setClassicDecorator', false, {
id: 'ember._setComputedDecorator',
until: '3.13.0'
}));
return metal.setClassicDecorator;
}
});
Ember._setClassicDecorator = metal.setClassicDecorator;
Ember.meta = _meta.meta;
Ember.get = metal.get;
Ember.getWithDefault = metal.getWithDefault;
Ember._getPath = metal._getPath;
Ember.set = metal.set;
Ember.trySet = metal.trySet;
Ember.FEATURES = (0, _polyfills.assign)({
isEnabled: _canaryFeatures.isEnabled
}, _canaryFeatures.FEATURES);
Ember._Cache = utils.Cache;
Ember.on = metal.on;
Ember.addListener = metal.addListener;
Ember.removeListener = metal.removeListener;
Ember.sendEvent = metal.sendEvent;
Ember.hasListeners = metal.hasListeners;
Ember.isNone = metal.isNone;
Ember.isEmpty = metal.isEmpty;
Ember.isBlank = metal.isBlank;
Ember.isPresent = metal.isPresent;
Ember.notifyPropertyChange = metal.notifyPropertyChange;
Ember.overrideChains = metal.overrideChains;
Ember.beginPropertyChanges = metal.beginPropertyChanges;
Ember.endPropertyChanges = metal.endPropertyChanges;
Ember.changeProperties = metal.changeProperties;
Ember.platform = {
defineProperty: true,
hasPropertyAccessors: true
};
Ember.defineProperty = metal.defineProperty;
Ember.watchKey = metal.watchKey;
Ember.unwatchKey = metal.unwatchKey;
Ember.removeChainWatcher = metal.removeChainWatcher;
Ember._ChainNode = metal.ChainNode;
Ember.finishChains = metal.finishChains;
Ember.watchPath = metal.watchPath;
Ember.unwatchPath = metal.unwatchPath;
Ember.watch = metal.watch;
Ember.isWatching = metal.isWatching;
Ember.unwatch = metal.unwatch;
Ember.destroy = _meta.deleteMeta;
Ember.libraries = metal.libraries;
Ember.getProperties = metal.getProperties;
Ember.setProperties = metal.setProperties;
Ember.expandProperties = metal.expandProperties;
Ember.addObserver = metal.addObserver;
Ember.removeObserver = metal.removeObserver;
Ember.aliasMethod = metal.aliasMethod;
Ember.observer = metal.observer;
Ember.mixin = metal.mixin;
Ember.Mixin = metal.Mixin;
/**
A function may be assigned to `Ember.onerror` to be called when Ember
internals encounter an error. This is useful for specialized error handling
and reporting code.
```javascript
import $ from 'jquery';
Ember.onerror = function(error) {
$.ajax('/report-error', 'POST', {
stack: error.stack,
otherInformation: 'whatever app state you want to provide'
});
};
```
Internally, `Ember.onerror` is used as Backburner's error handler.
@event onerror
@for Ember
@param {Exception} error the error object
@public
*/
Object.defineProperty(Ember, 'onerror', {
get: _errorHandling.getOnerror,
set: _errorHandling.setOnerror,
enumerable: false
});
Object.defineProperty(Ember, 'testing', {
get: EmberDebug.isTesting,
set: EmberDebug.setTesting,
enumerable: false
});
Ember._Backburner = _backburner.default; // ****@ember/-internals/console****
if (_deprecatedFeatures.LOGGER) {
Ember.Logger = _console.default;
} // ****@ember/-internals/runtime****
Ember.A = _runtime.A;
Ember.String = {
loc: _string.loc,
w: _string.w,
dasherize: _string.dasherize,
decamelize: _string.decamelize,
camelize: _string.camelize,
classify: _string.classify,
underscore: _string.underscore,
capitalize: _string.capitalize
};
Ember.Object = _runtime.Object;
Ember._RegistryProxyMixin = _runtime.RegistryProxyMixin;
Ember._ContainerProxyMixin = _runtime.ContainerProxyMixin;
Ember.compare = _runtime.compare;
Ember.copy = _runtime.copy;
Ember.isEqual = _runtime.isEqual;
if (true
/* EMBER_FRAMEWORK_OBJECT_OWNER_ARGUMENT */
) {
Ember._setFrameworkClass = _runtime.setFrameworkClass;
}
/**
@module ember
*/
/**
Namespace for injection helper methods.
@class inject
@namespace Ember
@static
@public
*/
Ember.inject = function inject() {
(true && !(false) && (0, EmberDebug.assert)(`Injected properties must be created through helpers, see '${Object.keys(inject).map(k => `'inject.${k}'`).join(' or ')}'`));
};
Ember.inject.service = _service.inject;
Ember.inject.controller = _controller.inject;
Ember.Array = _runtime.Array;
Ember.Comparable = _runtime.Comparable;
Ember.Enumerable = _runtime.Enumerable;
Ember.ArrayProxy = _runtime.ArrayProxy;
Ember.ObjectProxy = _runtime.ObjectProxy;
Ember.ActionHandler = _runtime.ActionHandler;
Ember.CoreObject = _runtime.CoreObject;
Ember.NativeArray = _runtime.NativeArray;
Ember.Copyable = _runtime.Copyable;
Ember.MutableEnumerable = _runtime.MutableEnumerable;
Ember.MutableArray = _runtime.MutableArray;
Ember.TargetActionSupport = _runtime.TargetActionSupport;
Ember.Evented = _runtime.Evented;
Ember.PromiseProxyMixin = _runtime.PromiseProxyMixin;
Ember.Observable = _runtime.Observable;
Ember.typeOf = _runtime.typeOf;
Ember.isArray = _runtime.isArray;
Ember.Object = _runtime.Object;
Ember.onLoad = _application.onLoad;
Ember.runLoadHooks = _application.runLoadHooks;
Ember.Controller = _controller.default;
Ember.ControllerMixin = _controller_mixin.default;
Ember.Service = _service.default;
Ember._ProxyMixin = _runtime._ProxyMixin;
Ember.RSVP = _runtime.RSVP;
Ember.Namespace = _runtime.Namespace;
Ember._action = _object.action;
Ember._dependentKeyCompat = _compat.dependentKeyCompat;
computed.empty = _computed.empty;
computed.notEmpty = _computed.notEmpty;
computed.none = _computed.none;
computed.not = _computed.not;
computed.bool = _computed.bool;
computed.match = _computed.match;
computed.equal = _computed.equal;
computed.gt = _computed.gt;
computed.gte = _computed.gte;
computed.lt = _computed.lt;
computed.lte = _computed.lte;
computed.oneWay = _computed.oneWay;
computed.reads = _computed.oneWay;
computed.readOnly = _computed.readOnly;
computed.deprecatingAlias = _computed.deprecatingAlias;
computed.and = _computed.and;
computed.or = _computed.or;
computed.sum = _computed.sum;
computed.min = _computed.min;
computed.max = _computed.max;
computed.map = _computed.map;
computed.sort = _computed.sort;
computed.setDiff = _computed.setDiff;
computed.mapBy = _computed.mapBy;
computed.filter = _computed.filter;
computed.filterBy = _computed.filterBy;
computed.uniq = _computed.uniq;
computed.uniqBy = _computed.uniqBy;
computed.union = _computed.union;
computed.intersect = _computed.intersect;
computed.collect = _computed.collect;
/**
Defines the hash of localized strings for the current language. Used by
the `String.loc` helper. To localize, add string values to this
hash.
@property STRINGS
@for Ember
@type Object
@private
*/
Object.defineProperty(Ember, 'STRINGS', {
configurable: false,
get: _string._getStrings,
set: _string._setStrings
});
/**
Whether searching on the global for new Namespace instances is enabled.
This is only exported here as to not break any addons. Given the new
visit API, you will have issues if you treat this as a indicator of
booted.
Internally this is only exposing a flag in Namespace.
@property BOOTED
@for Ember
@type Boolean
@private
*/
Object.defineProperty(Ember, 'BOOTED', {
configurable: false,
enumerable: false,
get: metal.isNamespaceSearchDisabled,
set: metal.setNamespaceSearchDisabled
}); // ****@ember/-internals/glimmer****
Ember.Component = _glimmer.Component;
_glimmer.Helper.helper = _glimmer.helper;
Ember.Helper = _glimmer.Helper;
Ember.Checkbox = _glimmer.Checkbox;
Ember.TextField = _glimmer.TextField;
Ember.TextArea = _glimmer.TextArea;
Ember.LinkComponent = _glimmer.LinkComponent;
Ember._setComponentManager = _glimmer.setComponentManager;
Ember._componentManagerCapabilities = _glimmer.capabilities;
Ember._setModifierManager = _glimmer.setModifierManager;
Ember._modifierManagerCapabilities = _glimmer.modifierCapabilities;
if (true
/* EMBER_GLIMMER_SET_COMPONENT_TEMPLATE */
) {
Ember._getComponentTemplate = _glimmer.getComponentTemplate;
Ember._setComponentTemplate = _glimmer.setComponentTemplate;
Ember._templateOnlyComponent = _templateOnly.default;
}
Ember.Handlebars = {
template: _glimmer.template,
Utils: {
escapeExpression: _glimmer.escapeExpression
}
};
Ember.HTMLBars = {
template: _glimmer.template
};
if (_environment.ENV.EXTEND_PROTOTYPES.String) {
String.prototype.htmlSafe = function () {
return (0, _glimmer.htmlSafe)(this);
};
}
Ember.String.htmlSafe = _glimmer.htmlSafe;
Ember.String.isHTMLSafe = _glimmer.isHTMLSafe;
/**
Global hash of shared templates. This will automatically be populated
by the build tools so that you can store your Handlebars templates in
separate files that get loaded into JavaScript at buildtime.
@property TEMPLATES
@for Ember
@type Object
@private
*/
Object.defineProperty(Ember, 'TEMPLATES', {
get: _glimmer.getTemplates,
set: _glimmer.setTemplates,
configurable: false,
enumerable: false
});
/**
The semantic version
@property VERSION
@type String
@public
*/
Ember.VERSION = _version.default; // ****@ember/-internals/views****
if (_deprecatedFeatures.JQUERY_INTEGRATION && !views.jQueryDisabled) {
Object.defineProperty(Ember, '$', {
get() {
(true && !(false) && (0, EmberDebug.deprecate)("Using Ember.$() has been deprecated, use `import jQuery from 'jquery';` instead", false, {
id: 'ember-views.curly-components.jquery-element',
until: '4.0.0',
url: 'https://emberjs.com/deprecations/v3.x#toc_jquery-apis'
}));
return views.jQuery;
},
configurable: true,
enumerable: true
});
}
Ember.ViewUtils = {
isSimpleClick: views.isSimpleClick,
getElementView: views.getElementView,
getViewElement: views.getViewElement,
getViewBounds: views.getViewBounds,
getViewClientRects: views.getViewClientRects,
getViewBoundingClientRect: views.getViewBoundingClientRect,
getRootViews: views.getRootViews,
getChildViews: views.getChildViews,
isSerializationFirstNode: _glimmer.isSerializationFirstNode
};
Ember.TextSupport = views.TextSupport;
Ember.ComponentLookup = views.ComponentLookup;
Ember.EventDispatcher = views.EventDispatcher; // ****@ember/-internals/routing****
Ember.Location = routing.Location;
Ember.AutoLocation = routing.AutoLocation;
Ember.HashLocation = routing.HashLocation;
Ember.HistoryLocation = routing.HistoryLocation;
Ember.NoneLocation = routing.NoneLocation;
Ember.controllerFor = routing.controllerFor;
Ember.generateControllerFactory = routing.generateControllerFactory;
Ember.generateController = routing.generateController;
Ember.RouterDSL = routing.RouterDSL;
Ember.Router = routing.Router;
Ember.Route = routing.Route;
(0, _application.runLoadHooks)('Ember.Application', _application.default);
Ember.DataAdapter = extensionSupport.DataAdapter;
Ember.ContainerDebugAdapter = extensionSupport.ContainerDebugAdapter;
if ((0, _require.has)('ember-template-compiler')) {
(0, _require.default)("ember-template-compiler");
} // do this to ensure that Ember.Test is defined properly on the global
// if it is present.
if ((0, _require.has)('ember-testing')) {
var testing = (0, _require.default)("ember-testing");
Ember.Test = testing.Test;
Ember.Test.Adapter = testing.Adapter;
Ember.Test.QUnitAdapter = testing.QUnitAdapter;
Ember.setupForTesting = testing.setupForTesting;
}
(0, _application.runLoadHooks)('Ember');
var _default = Ember;
_exports.default = _default;
if (_nodeModule.IS_NODE) {
_nodeModule.module.exports = Ember;
} else {
_environment.context.exports.Ember = _environment.context.exports.Em = Ember;
}
/**
@module jquery
@public
*/
/**
@class jquery
@public
@static
*/
/**
Alias for jQuery
@for jquery
@method $
@static
@public
*/
});
define("ember/version", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
var _default = "3.13.2";
_exports.default = _default;
});
define("node-module/index", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.require = _exports.module = _exports.IS_NODE = void 0;
/*global module */
var IS_NODE = typeof module === 'object' && typeof module.require === 'function';
_exports.IS_NODE = IS_NODE;
var exportModule;
_exports.module = exportModule;
var exportRequire;
_exports.require = exportRequire;
if (IS_NODE) {
_exports.module = exportModule = module;
_exports.require = exportRequire = module.require;
} else {
_exports.module = exportModule = null;
_exports.require = exportRequire = null;
}
});
define("route-recognizer", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
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;
for (var i = 0; i < handlers.length; i++) {
var handler = handlers[i];
var names = handler.names;
var shouldDecodes = handler.shouldDecodes;
var params = EmptyObject;
var isDynamic = false;
if (names !== EmptyArray && shouldDecodes !== EmptyArray) {
for (var j = 0; j < names.length; j++) {
isDynamic = true;
var name = names[j];
var capture = captures && captures[currentCapture++];
if (params === EmptyObject) {
params = {};
}
if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS && shouldDecodes[j]) {
params[name] = capture && decodeURIComponent(capture);
} else {
params[name] = capture;
}
}
}
result[i] = {
handler: handler.handler,
params: params,
isDynamic: isDynamic
};
}
return result;
}
function decodeQueryParamPart(part) {
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
part = part.replace(/\+/gm, "%20");
var result;
try {
result = decodeURIComponent(part);
} catch (error) {
result = "";
}
return result;
}
var RouteRecognizer = function RouteRecognizer() {
this.names = createMap();
var states = [];
var state = new State(states, 0, -1
/* ANY */
, true, false);
states[0] = state;
this.states = states;
this.rootState = state;
};
RouteRecognizer.prototype.add = function add(routes, options) {
var currentState = this.rootState;
var pattern = "^";
var types = [0, 0, 0];
var handlers = new Array(routes.length);
var allSegments = [];
var isEmpty = true;
var j = 0;
for (var i = 0; i < routes.length; i++) {
var route = routes[i];
var ref = parse(allSegments, route.path, types);
var names = ref.names;
var shouldDecodes = ref.shouldDecodes; // preserve j so it points to the start of newly added segments
for (; j < allSegments.length; j++) {
var segment = allSegments[j];
if (segment.type === 4
/* Epsilon */
) {
continue;
}
isEmpty = false; // Add a "/" for the new segment
currentState = currentState.put(47
/* SLASH */
, false, false);
pattern += "/"; // Add a representation of the segment to the NFA and regex
currentState = eachChar[segment.type](segment, currentState);
pattern += regex[segment.type](segment);
}
handlers[i] = {
handler: route.handler,
names: names,
shouldDecodes: shouldDecodes
};
}
if (isEmpty) {
currentState = currentState.put(47
/* SLASH */
, false, false);
pattern += "/";
}
currentState.handlers = handlers;
currentState.pattern = pattern + "$";
currentState.types = types;
var name;
if (typeof options === "object" && options !== null && options.as) {
name = options.as;
}
if (name) {
// if (this.names[name]) {
// throw new Error("You may not add a duplicate route named `" + name + "`.");
// }
this.names[name] = {
segments: allSegments,
handlers: handlers
};
}
};
RouteRecognizer.prototype.handlersFor = function handlersFor(name) {
var route = this.names[name];
if (!route) {
throw new Error("There is no route named " + name);
}
var result = new Array(route.handlers.length);
for (var i = 0; i < route.handlers.length; i++) {
var handler = route.handlers[i];
result[i] = handler;
}
return result;
};
RouteRecognizer.prototype.hasRoute = function hasRoute(name) {
return !!this.names[name];
};
RouteRecognizer.prototype.generate = function generate$1(name, params) {
var route = this.names[name];
var output = "";
if (!route) {
throw new Error("There is no route named " + name);
}
var segments = route.segments;
for (var i = 0; i < segments.length; i++) {
var segment = segments[i];
if (segment.type === 4
/* Epsilon */
) {
continue;
}
output += "/";
output += generate[segment.type](segment, params);
}
if (output.charAt(0) !== "/") {
output = "/" + output;
}
if (params && params.queryParams) {
output += this.generateQueryString(params.queryParams);
}
return output;
};
RouteRecognizer.prototype.generateQueryString = function generateQueryString(params) {
var pairs = [];
var keys = Object.keys(params);
keys.sort();
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = params[key];
if (value == null) {
continue;
}
var pair = encodeURIComponent(key);
if (isArray(value)) {
for (var j = 0; j < value.length; j++) {
var arrayPair = key + "[]" + "=" + encodeURIComponent(value[j]);
pairs.push(arrayPair);
}
} else {
pair += "=" + encodeURIComponent(value);
pairs.push(pair);
}
}
if (pairs.length === 0) {
return "";
}
return "?" + pairs.join("&");
};
RouteRecognizer.prototype.parseQueryString = function parseQueryString(queryString) {
var pairs = queryString.split("&");
var queryParams = {};
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split("="),
key = decodeQueryParamPart(pair[0]),
keyLength = key.length,
isArray = false,
value = void 0;
if (pair.length === 1) {
value = "true";
} else {
// Handle arrays
if (keyLength > 2 && key.slice(keyLength - 2) === "[]") {
isArray = true;
key = key.slice(0, keyLength - 2);
if (!queryParams[key]) {
queryParams[key] = [];
}
}
value = pair[1] ? decodeQueryParamPart(pair[1]) : "";
}
if (isArray) {
queryParams[key].push(value);
} else {
queryParams[key] = value;
}
}
return queryParams;
};
RouteRecognizer.prototype.recognize = function recognize(path) {
var results;
var states = [this.rootState];
var queryParams = {};
var isSlashDropped = false;
var hashStart = path.indexOf("#");
if (hashStart !== -1) {
path = path.substr(0, hashStart);
}
var queryStart = path.indexOf("?");
if (queryStart !== -1) {
var queryString = path.substr(queryStart + 1, path.length);
path = path.substr(0, queryStart);
queryParams = this.parseQueryString(queryString);
}
if (path.charAt(0) !== "/") {
path = "/" + path;
}
var originalPath = path;
if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {
path = normalizePath(path);
} else {
path = decodeURI(path);
originalPath = decodeURI(originalPath);
}
var pathLen = path.length;
if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
path = path.substr(0, pathLen - 1);
originalPath = originalPath.substr(0, originalPath.length - 1);
isSlashDropped = true;
}
for (var i = 0; i < path.length; i++) {
states = recognizeChar(states, path.charCodeAt(i));
if (!states.length) {
break;
}
}
var solutions = [];
for (var i$1 = 0; i$1 < states.length; i$1++) {
if (states[i$1].handlers) {
solutions.push(states[i$1]);
}
}
states = sortSolutions(solutions);
var state = solutions[0];
if (state && state.handlers) {
// if a trailing slash was dropped and a star segment is the last segment
// specified, put the trailing slash back
if (isSlashDropped && state.pattern && state.pattern.slice(-5) === "(.+)$") {
originalPath = originalPath + "/";
}
results = findHandler(state, originalPath, queryParams);
}
return results;
};
RouteRecognizer.VERSION = "0.3.4"; // Set to false to opt-out of encoding and decoding path segments.
// See https://github.com/tildeio/route-recognizer/pull/55
RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS = true;
RouteRecognizer.Normalizer = {
normalizeSegment: normalizeSegment,
normalizePath: normalizePath,
encodePathSegment: encodePathSegment
};
RouteRecognizer.prototype.map = map;
var _default = RouteRecognizer;
_exports.default = _default;
});
define("router_js", ["exports", "@ember/polyfills", "rsvp", "route-recognizer"], function (_exports, _polyfills, _rsvp, _routeRecognizer) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.logAbort = logAbort;
_exports.InternalRouteInfo = _exports.TransitionError = _exports.TransitionState = _exports.QUERY_PARAMS_SYMBOL = _exports.PARAMS_SYMBOL = _exports.STATE_SYMBOL = _exports.InternalTransition = _exports.default = void 0;
var TransitionAbortedError = function () {
TransitionAbortedError.prototype = Object.create(Error.prototype);
TransitionAbortedError.prototype.constructor = TransitionAbortedError;
function TransitionAbortedError(message) {
var error = Error.call(this, message);
this.name = 'TransitionAborted';
this.message = message || 'TransitionAborted';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TransitionAbortedError);
} else {
this.stack = error.stack;
}
}
return TransitionAbortedError;
}();
var slice = Array.prototype.slice;
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
Determines if an object is Promise by checking if it is "thenable".
**/
function isPromise(p) {
return p !== null && typeof p === 'object' && typeof p.then === 'function';
}
function merge(hash, other) {
for (var prop in other) {
if (hasOwnProperty.call(other, prop)) {
hash[prop] = other[prop];
}
}
}
/**
@private
Extracts query params from the end of an array
**/
function extractQueryParams(array) {
var len = array && array.length,
head,
queryParams;
if (len && len > 0) {
var obj = array[len - 1];
if (isQueryParams(obj)) {
queryParams = obj.queryParams;
head = slice.call(array, 0, len - 1);
return [head, queryParams];
}
}
return [array, null];
}
function isQueryParams(obj) {
return obj && hasOwnProperty.call(obj, 'queryParams');
}
/**
@private
Coerces query param properties and array elements into strings.
**/
function coerceQueryParamsToString(queryParams) {
for (var key in queryParams) {
var val = queryParams[key];
if (typeof val === 'number') {
queryParams[key] = '' + val;
} else if (Array.isArray(val)) {
for (var i = 0, l = val.length; i < l; i++) {
val[i] = '' + val[i];
}
}
}
}
/**
@private
*/
function log(router, ...args) {
if (!router.log) {
return;
}
if (args.length === 2) {
var [sequence, msg] = args;
router.log('Transition #' + sequence + ': ' + msg);
} else {
var [_msg] = args;
router.log(_msg);
}
}
function isParam(object) {
return typeof object === 'string' || object instanceof String || typeof object === 'number' || object instanceof Number;
}
function forEach(array, callback) {
for (var i = 0, l = array.length; i < l && callback(array[i]) !== false; i++) {// empty intentionally
}
}
function getChangelist(oldObject, newObject) {
var key;
var results = {
all: {},
changed: {},
removed: {}
};
merge(results.all, newObject);
var didChange = false;
coerceQueryParamsToString(oldObject);
coerceQueryParamsToString(newObject); // Calculate removals
for (key in oldObject) {
if (hasOwnProperty.call(oldObject, key)) {
if (!hasOwnProperty.call(newObject, key)) {
didChange = true;
results.removed[key] = oldObject[key];
}
}
} // Calculate changes
for (key in newObject) {
if (hasOwnProperty.call(newObject, key)) {
var oldElement = oldObject[key];
var newElement = newObject[key];
if (isArray(oldElement) && isArray(newElement)) {
if (oldElement.length !== newElement.length) {
results.changed[key] = newObject[key];
didChange = true;
} else {
for (var i = 0, l = oldElement.length; i < l; i++) {
if (oldElement[i] !== newElement[i]) {
results.changed[key] = newObject[key];
didChange = true;
}
}
}
} else if (oldObject[key] !== newObject[key]) {
results.changed[key] = newObject[key];
didChange = true;
}
}
}
return didChange ? results : undefined;
}
function isArray(obj) {
return Array.isArray(obj);
}
function promiseLabel(label) {
return 'Router: ' + label;
}
var STATE_SYMBOL = `__STATE__-2619860001345920-3322w3`;
_exports.STATE_SYMBOL = STATE_SYMBOL;
var PARAMS_SYMBOL = `__PARAMS__-261986232992830203-23323`;
_exports.PARAMS_SYMBOL = PARAMS_SYMBOL;
var QUERY_PARAMS_SYMBOL = `__QPS__-2619863929824844-32323`;
/**
A Transition is a thennable (a promise-like object) that represents
an attempt to transition to another route. It can be aborted, either
explicitly via `abort` or by attempting another transition while a
previous one is still underway. An aborted transition can also
be `retry()`d later.
@class Transition
@constructor
@param {Object} router
@param {Object} intent
@param {Object} state
@param {Object} error
@private
*/
_exports.QUERY_PARAMS_SYMBOL = QUERY_PARAMS_SYMBOL;
class Transition {
constructor(router, intent, state, error = undefined, previousTransition = undefined) {
this.from = null;
this.to = undefined;
this.isAborted = false;
this.isActive = true;
this.urlMethod = 'update';
this.resolveIndex = 0;
this.queryParamsOnly = false;
this.isTransition = true;
this.isCausedByAbortingTransition = false;
this.isCausedByInitialTransition = false;
this.isCausedByAbortingReplaceTransition = false;
this._visibleQueryParams = {};
this[STATE_SYMBOL] = state || router.state;
this.intent = intent;
this.router = router;
this.data = intent && intent.data || {};
this.resolvedModels = {};
this[QUERY_PARAMS_SYMBOL] = {};
this.promise = undefined;
this.error = undefined;
this[PARAMS_SYMBOL] = {};
this.routeInfos = [];
this.targetName = undefined;
this.pivotHandler = undefined;
this.sequence = -1;
if (error) {
this.promise = _rsvp.Promise.reject(error);
this.error = error;
return;
} // if you're doing multiple redirects, need the new transition to know if it
// is actually part of the first transition or not. Any further redirects
// in the initial transition also need to know if they are part of the
// initial transition
this.isCausedByAbortingTransition = !!previousTransition;
this.isCausedByInitialTransition = !!previousTransition && (previousTransition.isCausedByInitialTransition || previousTransition.sequence === 0); // Every transition in the chain is a replace
this.isCausedByAbortingReplaceTransition = !!previousTransition && previousTransition.urlMethod === 'replace' && (!previousTransition.isCausedByAbortingTransition || previousTransition.isCausedByAbortingReplaceTransition);
if (state) {
this[PARAMS_SYMBOL] = state.params;
this[QUERY_PARAMS_SYMBOL] = state.queryParams;
this.routeInfos = state.routeInfos;
var len = state.routeInfos.length;
if (len) {
this.targetName = state.routeInfos[len - 1].name;
}
for (var i = 0; i < len; ++i) {
var handlerInfo = state.routeInfos[i]; // TODO: this all seems hacky
if (!handlerInfo.isResolved) {
break;
}
this.pivotHandler = handlerInfo.route;
}
this.sequence = router.currentSequence++;
this.promise = state.resolve(() => {
if (this.isAborted) {
return _rsvp.Promise.reject(false, promiseLabel('Transition aborted - reject'));
}
return _rsvp.Promise.resolve(true);
}, this).catch(result => {
return _rsvp.Promise.reject(this.router.transitionDidError(result, this));
}, promiseLabel('Handle Abort'));
} else {
this.promise = _rsvp.Promise.resolve(this[STATE_SYMBOL]);
this[PARAMS_SYMBOL] = {};
}
}
/**
The Transition's internal promise. Calling `.then` on this property
is that same as calling `.then` on the Transition object itself, but
this property is exposed for when you want to pass around a
Transition's promise, but not the Transition object itself, since
Transition object can be externally `abort`ed, while the promise
cannot.
@property promise
@type {Object}
@public
*/
/**
Custom state can be stored on a Transition's `data` object.
This can be useful for decorating a Transition within an earlier
hook and shared with a later hook. Properties set on `data` will
be copied to new transitions generated by calling `retry` on this
transition.
@property data
@type {Object}
@public
*/
/**
A standard promise hook that resolves if the transition
succeeds and rejects if it fails/redirects/aborts.
Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable,
but not the Transition itself.
@method then
@param {Function} onFulfilled
@param {Function} onRejected
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
@public
*/
then(onFulfilled, onRejected, label) {
return this.promise.then(onFulfilled, onRejected, label);
}
/**
Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable,
but not the Transition itself.
@method catch
@param {Function} onRejection
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
@public
*/
catch(onRejection, label) {
return this.promise.catch(onRejection, label);
}
/**
Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable,
but not the Transition itself.
@method finally
@param {Function} callback
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}
@public
*/
finally(callback, label) {
return this.promise.finally(callback, label);
}
/**
Aborts the Transition. Note you can also implicitly abort a transition
by initiating another transition while a previous one is underway.
@method abort
@return {Transition} this transition
@public
*/
abort() {
this.rollback();
var transition = new Transition(this.router, undefined, undefined, undefined);
transition.to = this.from;
transition.from = this.from;
transition.isAborted = true;
this.router.routeWillChange(transition);
this.router.routeDidChange(transition);
return this;
}
rollback() {
if (!this.isAborted) {
log(this.router, this.sequence, this.targetName + ': transition was aborted');
if (this.intent !== undefined && this.intent !== null) {
this.intent.preTransitionState = this.router.state;
}
this.isAborted = true;
this.isActive = false;
this.router.activeTransition = undefined;
}
}
redirect(newTransition) {
this.rollback();
this.router.routeWillChange(newTransition);
}
/**
Retries a previously-aborted transition (making sure to abort the
transition if it's still active). Returns a new transition that
represents the new attempt to transition.
@method retry
@return {Transition} new transition
@public
*/
retry() {
// TODO: add tests for merged state retry()s
this.abort();
var newTransition = this.router.transitionByIntent(this.intent, false); // inheriting a `null` urlMethod is not valid
// the urlMethod is only set to `null` when
// the transition is initiated *after* the url
// has been updated (i.e. `router.handleURL`)
//
// in that scenario, the url method cannot be
// inherited for a new transition because then
// the url would not update even though it should
if (this.urlMethod !== null) {
newTransition.method(this.urlMethod);
}
return newTransition;
}
/**
Sets the URL-changing method to be employed at the end of a
successful transition. By default, a new Transition will just
use `updateURL`, but passing 'replace' to this method will
cause the URL to update using 'replaceWith' instead. Omitting
a parameter will disable the URL change, allowing for transitions
that don't update the URL at completion (this is also used for
handleURL, since the URL has already changed before the
transition took place).
@method method
@param {String} method the type of URL-changing method to use
at the end of a transition. Accepted values are 'replace',
falsy values, or any other non-falsy value (which is
interpreted as an updateURL transition).
@return {Transition} this transition
@public
*/
method(method) {
this.urlMethod = method;
return this;
} // Alias 'trigger' as 'send'
send(ignoreFailure = false, _name, err, transition, handler) {
this.trigger(ignoreFailure, _name, err, transition, handler);
}
/**
Fires an event on the current list of resolved/resolving
handlers within this transition. Useful for firing events
on route hierarchies that haven't fully been entered yet.
Note: This method is also aliased as `send`
@method trigger
@param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error
@param {String} name the name of the event to fire
@public
*/
trigger(ignoreFailure = false, name, ...args) {
// TODO: Deprecate the current signature
if (typeof ignoreFailure === 'string') {
name = ignoreFailure;
ignoreFailure = false;
}
this.router.triggerEvent(this[STATE_SYMBOL].routeInfos.slice(0, this.resolveIndex + 1), ignoreFailure, name, args);
}
/**
Transitions are aborted and their promises rejected
when redirects occur; this method returns a promise
that will follow any redirects that occur and fulfill
with the value fulfilled by any redirecting transitions
that occur.
@method followRedirects
@return {Promise} a promise that fulfills with the same
value that the final redirecting transition fulfills with
@public
*/
followRedirects() {
var router = this.router;
return this.promise.catch(function (reason) {
if (router.activeTransition) {
return router.activeTransition.followRedirects();
}
return _rsvp.Promise.reject(reason);
});
}
toString() {
return 'Transition (sequence ' + this.sequence + ')';
}
/**
@private
*/
log(message) {
log(this.router, this.sequence, message);
}
}
/**
@private
Logs and returns an instance of TransitionAborted.
*/
_exports.InternalTransition = Transition;
function logAbort(transition) {
log(transition.router, transition.sequence, 'detected abort.');
return new TransitionAbortedError();
}
function isTransition(obj) {
return typeof obj === 'object' && obj instanceof Transition && obj.isTransition;
}
function prepareResult(obj) {
if (isTransition(obj)) {
return null;
}
return obj;
}
var ROUTE_INFOS = new WeakMap();
function toReadOnlyRouteInfo(routeInfos, queryParams = {}, includeAttributes = false) {
return routeInfos.map((info, i) => {
var {
name,
params,
paramNames,
context,
route
} = info;
if (ROUTE_INFOS.has(info) && includeAttributes) {
var _routeInfo = ROUTE_INFOS.get(info);
_routeInfo = attachMetadata(route, _routeInfo);
var routeInfoWithAttribute = createRouteInfoWithAttributes(_routeInfo, context);
ROUTE_INFOS.set(info, routeInfoWithAttribute);
return routeInfoWithAttribute;
}
var routeInfo = {
find(predicate, thisArg) {
var publicInfo;
var arr = [];
if (predicate.length === 3) {
arr = routeInfos.map(info => ROUTE_INFOS.get(info));
}
for (var _i = 0; routeInfos.length > _i; _i++) {
publicInfo = ROUTE_INFOS.get(routeInfos[_i]);
if (predicate.call(thisArg, publicInfo, _i, arr)) {
return publicInfo;
}
}
return undefined;
},
get name() {
return name;
},
get paramNames() {
return paramNames;
},
get metadata() {
return buildRouteInfoMetadata(info.route);
},
get parent() {
var parent = routeInfos[i - 1];
if (parent === undefined) {
return null;
}
return ROUTE_INFOS.get(parent);
},
get child() {
var child = routeInfos[i + 1];
if (child === undefined) {
return null;
}
return ROUTE_INFOS.get(child);
},
get localName() {
var parts = this.name.split('.');
return parts[parts.length - 1];
},
get params() {
return params;
},
get queryParams() {
return queryParams;
}
};
if (includeAttributes) {
routeInfo = createRouteInfoWithAttributes(routeInfo, context);
}
ROUTE_INFOS.set(info, routeInfo);
return routeInfo;
});
}
function createRouteInfoWithAttributes(routeInfo, context) {
var attributes = {
get attributes() {
return context;
}
};
if (Object.isFrozen(routeInfo) || routeInfo.hasOwnProperty('attributes')) {
return Object.freeze((0, _polyfills.assign)({}, routeInfo, attributes));
}
return (0, _polyfills.assign)(routeInfo, attributes);
}
function buildRouteInfoMetadata(route) {
if (route !== undefined && route !== null && route.buildRouteInfoMetadata !== undefined) {
return route.buildRouteInfoMetadata();
}
return null;
}
function attachMetadata(route, routeInfo) {
var metadata = {
get metadata() {
return buildRouteInfoMetadata(route);
}
};
if (Object.isFrozen(routeInfo) || routeInfo.hasOwnProperty('metadata')) {
return Object.freeze((0, _polyfills.assign)({}, routeInfo, metadata));
}
return (0, _polyfills.assign)(routeInfo, metadata);
}
class InternalRouteInfo {
constructor(router, name, paramNames, route) {
this._routePromise = undefined;
this._route = null;
this.params = {};
this.isResolved = false;
this.name = name;
this.paramNames = paramNames;
this.router = router;
if (route) {
this._processRoute(route);
}
}
getModel(_transition) {
return _rsvp.Promise.resolve(this.context);
}
serialize(_context) {
return this.params || {};
}
resolve(shouldContinue, transition) {
return _rsvp.Promise.resolve(this.routePromise).then(route => this.checkForAbort(shouldContinue, route)).then(() => this.runBeforeModelHook(transition)).then(() => this.checkForAbort(shouldContinue, null)).then(() => this.getModel(transition)).then(resolvedModel => this.checkForAbort(shouldContinue, resolvedModel)).then(resolvedModel => this.runAfterModelHook(transition, resolvedModel)).then(resolvedModel => this.becomeResolved(transition, resolvedModel));
}
becomeResolved(transition, resolvedContext) {
var params = this.serialize(resolvedContext);
if (transition) {
this.stashResolvedModel(transition, resolvedContext);
transition[PARAMS_SYMBOL] = transition[PARAMS_SYMBOL] || {};
transition[PARAMS_SYMBOL][this.name] = params;
}
var context;
var contextsMatch = resolvedContext === this.context;
if ('context' in this || !contextsMatch) {
context = resolvedContext;
}
var cached = ROUTE_INFOS.get(this);
var resolved = new ResolvedRouteInfo(this.router, this.name, this.paramNames, params, this.route, context);
if (cached !== undefined) {
ROUTE_INFOS.set(resolved, cached);
}
return resolved;
}
shouldSupercede(routeInfo) {
// Prefer this newer routeInfo over `other` if:
// 1) The other one doesn't exist
// 2) The names don't match
// 3) This route has a context that doesn't match
// the other one (or the other one doesn't have one).
// 4) This route has parameters that don't match the other.
if (!routeInfo) {
return true;
}
var contextsMatch = routeInfo.context === this.context;
return routeInfo.name !== this.name || 'context' in this && !contextsMatch || this.hasOwnProperty('params') && !paramsMatch(this.params, routeInfo.params);
}
get route() {
// _route could be set to either a route object or undefined, so we
// compare against null to know when it's been set
if (this._route !== null) {
return this._route;
}
return this.fetchRoute();
}
set route(route) {
this._route = route;
}
get routePromise() {
if (this._routePromise) {
return this._routePromise;
}
this.fetchRoute();
return this._routePromise;
}
set routePromise(routePromise) {
this._routePromise = routePromise;
}
log(transition, message) {
if (transition.log) {
transition.log(this.name + ': ' + message);
}
}
updateRoute(route) {
route._internalName = this.name;
return this.route = route;
}
runBeforeModelHook(transition) {
if (transition.trigger) {
transition.trigger(true, 'willResolveModel', transition, this.route);
}
var result;
if (this.route) {
if (this.route.beforeModel !== undefined) {
result = this.route.beforeModel(transition);
}
}
if (isTransition(result)) {
result = null;
}
return _rsvp.Promise.resolve(result);
}
runAfterModelHook(transition, resolvedModel) {
// Stash the resolved model on the payload.
// This makes it possible for users to swap out
// the resolved model in afterModel.
var name = this.name;
this.stashResolvedModel(transition, resolvedModel);
var result;
if (this.route !== undefined) {
if (this.route.afterModel !== undefined) {
result = this.route.afterModel(resolvedModel, transition);
}
}
result = prepareResult(result);
return _rsvp.Promise.resolve(result).then(() => {
// Ignore the fulfilled value returned from afterModel.
// Return the value stashed in resolvedModels, which
// might have been swapped out in afterModel.
return transition.resolvedModels[name];
});
}
checkForAbort(shouldContinue, value) {
return _rsvp.Promise.resolve(shouldContinue()).then(function () {
// We don't care about shouldContinue's resolve value;
// pass along the original value passed to this fn.
return value;
}, null);
}
stashResolvedModel(transition, resolvedModel) {
transition.resolvedModels = transition.resolvedModels || {};
transition.resolvedModels[this.name] = resolvedModel;
}
fetchRoute() {
var route = this.router.getRoute(this.name);
return this._processRoute(route);
}
_processRoute(route) {
// Setup a routePromise so that we can wait for asynchronously loaded routes
this.routePromise = _rsvp.Promise.resolve(route); // Wait until the 'route' property has been updated when chaining to a route
// that is a promise
if (isPromise(route)) {
this.routePromise = this.routePromise.then(r => {
return this.updateRoute(r);
}); // set to undefined to avoid recursive loop in the route getter
return this.route = undefined;
} else if (route) {
return this.updateRoute(route);
}
return undefined;
}
}
_exports.InternalRouteInfo = InternalRouteInfo;
class ResolvedRouteInfo extends InternalRouteInfo {
constructor(router, name, paramNames, params, route, context) {
super(router, name, paramNames, route);
this.params = params;
this.isResolved = true;
this.context = context;
}
resolve(_shouldContinue, transition) {
// A ResolvedRouteInfo just resolved with itself.
if (transition && transition.resolvedModels) {
transition.resolvedModels[this.name] = this.context;
}
return _rsvp.Promise.resolve(this);
}
}
class UnresolvedRouteInfoByParam extends InternalRouteInfo {
constructor(router, name, paramNames, params, route) {
super(router, name, paramNames, route);
this.params = {};
this.params = params;
}
getModel(transition) {
var fullParams = this.params;
if (transition && transition[QUERY_PARAMS_SYMBOL]) {
fullParams = {};
merge(fullParams, this.params);
fullParams.queryParams = transition[QUERY_PARAMS_SYMBOL];
}
var route = this.route;
var result = undefined;
if (route.deserialize) {
result = route.deserialize(fullParams, transition);
} else if (route.model) {
result = route.model(fullParams, transition);
}
if (result && isTransition(result)) {
result = undefined;
}
return _rsvp.Promise.resolve(result);
}
}
class UnresolvedRouteInfoByObject extends InternalRouteInfo {
constructor(router, name, paramNames, context) {
super(router, name, paramNames);
this.context = context;
this.serializer = this.router.getSerializer(name);
}
getModel(transition) {
if (this.router.log !== undefined) {
this.router.log(this.name + ': resolving provided model');
}
return super.getModel(transition);
}
/**
@private
Serializes a route using its custom `serialize` method or
by a default that looks up the expected property name from
the dynamic segment.
@param {Object} model the model to be serialized for this route
*/
serialize(model) {
var {
paramNames,
context
} = this;
if (!model) {
model = context;
}
var object = {};
if (isParam(model)) {
object[paramNames[0]] = model;
return object;
} // Use custom serialize if it exists.
if (this.serializer) {
// invoke this.serializer unbound (getSerializer returns a stateless function)
return this.serializer.call(null, model, paramNames);
} else if (this.route !== undefined) {
if (this.route.serialize) {
return this.route.serialize(model, paramNames);
}
}
if (paramNames.length !== 1) {
return;
}
var name = paramNames[0];
if (/_id$/.test(name)) {
object[name] = model.id;
} else {
object[name] = model;
}
return object;
}
}
function paramsMatch(a, b) {
if (!a !== !b) {
// Only one is null.
return false;
}
if (!a) {
// Both must be null.
return true;
} // Note: this assumes that both params have the same
// number of keys, but since we're comparing the
// same routes, they should.
for (var k in a) {
if (a.hasOwnProperty(k) && a[k] !== b[k]) {
return false;
}
}
return true;
}
class TransitionIntent {
constructor(router, data = {}) {
this.router = router;
this.data = data;
}
}
class TransitionState {
constructor() {
this.routeInfos = [];
this.queryParams = {};
this.params = {};
}
promiseLabel(label) {
var targetName = '';
forEach(this.routeInfos, function (routeInfo) {
if (targetName !== '') {
targetName += '.';
}
targetName += routeInfo.name;
return true;
});
return promiseLabel("'" + targetName + "': " + label);
}
resolve(shouldContinue, transition) {
// First, calculate params for this state. This is useful
// information to provide to the various route hooks.
var params = this.params;
forEach(this.routeInfos, routeInfo => {
params[routeInfo.name] = routeInfo.params || {};
return true;
});
transition.resolveIndex = 0;
var currentState = this;
var wasAborted = false; // The prelude RSVP.resolve() asyncs us into the promise land.
return _rsvp.Promise.resolve(null, this.promiseLabel('Start transition')).then(resolveOneRouteInfo, null, this.promiseLabel('Resolve route')).catch(handleError, this.promiseLabel('Handle error'));
function innerShouldContinue() {
return _rsvp.Promise.resolve(shouldContinue(), currentState.promiseLabel('Check if should continue')).catch(function (reason) {
// We distinguish between errors that occurred
// during resolution (e.g. before"Model/model/afterModel),
// and aborts due to a rejecting promise from shouldContinue().
wasAborted = true;
return _rsvp.Promise.reject(reason);
}, currentState.promiseLabel('Handle abort'));
}
function handleError(error) {
// This is the only possible
// reject value of TransitionState#resolve
var routeInfos = currentState.routeInfos;
var errorHandlerIndex = transition.resolveIndex >= routeInfos.length ? routeInfos.length - 1 : transition.resolveIndex;
return _rsvp.Promise.reject(new TransitionError(error, currentState.routeInfos[errorHandlerIndex].route, wasAborted, currentState));
}
function proceed(resolvedRouteInfo) {
var wasAlreadyResolved = currentState.routeInfos[transition.resolveIndex].isResolved; // Swap the previously unresolved routeInfo with
// the resolved routeInfo
currentState.routeInfos[transition.resolveIndex++] = resolvedRouteInfo;
if (!wasAlreadyResolved) {
// Call the redirect hook. The reason we call it here
// vs. afterModel is so that redirects into child
// routes don't re-run the model hooks for this
// already-resolved route.
var {
route
} = resolvedRouteInfo;
if (route !== undefined) {
if (route.redirect) {
route.redirect(resolvedRouteInfo.context, transition);
}
}
} // Proceed after ensuring that the redirect hook
// didn't abort this transition by transitioning elsewhere.
return innerShouldContinue().then(resolveOneRouteInfo, null, currentState.promiseLabel('Resolve route'));
}
function resolveOneRouteInfo() {
if (transition.resolveIndex === currentState.routeInfos.length) {
// This is is the only possible
// fulfill value of TransitionState#resolve
return currentState;
}
var routeInfo = currentState.routeInfos[transition.resolveIndex];
return routeInfo.resolve(innerShouldContinue, transition).then(proceed, null, currentState.promiseLabel('Proceed'));
}
}
}
_exports.TransitionState = TransitionState;
class TransitionError {
constructor(error, route, wasAborted, state) {
this.error = error;
this.route = route;
this.wasAborted = wasAborted;
this.state = state;
}
}
_exports.TransitionError = TransitionError;
class NamedTransitionIntent extends TransitionIntent {
constructor(router, name, pivotHandler, contexts = [], queryParams = {}, data) {
super(router, data);
this.preTransitionState = undefined;
this.name = name;
this.pivotHandler = pivotHandler;
this.contexts = contexts;
this.queryParams = queryParams;
}
applyToState(oldState, isIntermediate) {
// TODO: WTF fix me
var partitionedArgs = extractQueryParams([this.name].concat(this.contexts)),
pureArgs = partitionedArgs[0],
handlers = this.router.recognizer.handlersFor(pureArgs[0]);
var targetRouteName = handlers[handlers.length - 1].handler;
return this.applyToHandlers(oldState, handlers, targetRouteName, isIntermediate, false);
}
applyToHandlers(oldState, parsedHandlers, targetRouteName, isIntermediate, checkingIfActive) {
var i, len;
var newState = new TransitionState();
var objects = this.contexts.slice(0);
var invalidateIndex = parsedHandlers.length; // Pivot handlers are provided for refresh transitions
if (this.pivotHandler) {
for (i = 0, len = parsedHandlers.length; i < len; ++i) {
if (parsedHandlers[i].handler === this.pivotHandler._internalName) {
invalidateIndex = i;
break;
}
}
}
for (i = parsedHandlers.length - 1; i >= 0; --i) {
var result = parsedHandlers[i];
var name = result.handler;
var oldHandlerInfo = oldState.routeInfos[i];
var newHandlerInfo = null;
if (result.names.length > 0) {
if (i >= invalidateIndex) {
newHandlerInfo = this.createParamHandlerInfo(name, result.names, objects, oldHandlerInfo);
} else {
newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, result.names, objects, oldHandlerInfo, targetRouteName, i);
}
} else {
// This route has no dynamic segment.
// Therefore treat as a param-based handlerInfo
// with empty params. This will cause the `model`
// hook to be called with empty params, which is desirable.
newHandlerInfo = this.createParamHandlerInfo(name, result.names, objects, oldHandlerInfo);
}
if (checkingIfActive) {
// If we're performing an isActive check, we want to
// serialize URL params with the provided context, but
// ignore mismatches between old and new context.
newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context);
var oldContext = oldHandlerInfo && oldHandlerInfo.context;
if (result.names.length > 0 && oldHandlerInfo.context !== undefined && newHandlerInfo.context === oldContext) {
// If contexts match in isActive test, assume params also match.
// This allows for flexibility in not requiring that every last
// handler provide a `serialize` method
newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params;
}
newHandlerInfo.context = oldContext;
}
var handlerToUse = oldHandlerInfo;
if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) {
invalidateIndex = Math.min(i, invalidateIndex);
handlerToUse = newHandlerInfo;
}
if (isIntermediate && !checkingIfActive) {
handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context);
}
newState.routeInfos.unshift(handlerToUse);
}
if (objects.length > 0) {
throw new Error('More context objects were passed than there are dynamic segments for the route: ' + targetRouteName);
}
if (!isIntermediate) {
this.invalidateChildren(newState.routeInfos, invalidateIndex);
}
merge(newState.queryParams, this.queryParams || {});
return newState;
}
invalidateChildren(handlerInfos, invalidateIndex) {
for (var i = invalidateIndex, l = handlerInfos.length; i < l; ++i) {
var handlerInfo = handlerInfos[i];
if (handlerInfo.isResolved) {
var {
name,
params,
route,
paramNames
} = handlerInfos[i];
handlerInfos[i] = new UnresolvedRouteInfoByParam(this.router, name, paramNames, params, route);
}
}
}
getHandlerInfoForDynamicSegment(name, names, objects, oldHandlerInfo, _targetRouteName, i) {
var objectToUse;
if (objects.length > 0) {
// Use the objects provided for this transition.
objectToUse = objects[objects.length - 1];
if (isParam(objectToUse)) {
return this.createParamHandlerInfo(name, names, objects, oldHandlerInfo);
} else {
objects.pop();
}
} else if (oldHandlerInfo && oldHandlerInfo.name === name) {
// Reuse the matching oldHandlerInfo
return oldHandlerInfo;
} else {
if (this.preTransitionState) {
var preTransitionHandlerInfo = this.preTransitionState.routeInfos[i];
objectToUse = preTransitionHandlerInfo && preTransitionHandlerInfo.context;
} else {
// Ideally we should throw this error to provide maximal
// information to the user that not enough context objects
// were provided, but this proves too cumbersome in Ember
// in cases where inner template helpers are evaluated
// before parent helpers un-render, in which cases this
// error somewhat prematurely fires.
//throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]");
return oldHandlerInfo;
}
}
return new UnresolvedRouteInfoByObject(this.router, name, names, objectToUse);
}
createParamHandlerInfo(name, names, objects, oldHandlerInfo) {
var params = {}; // Soak up all the provided string/numbers
var numNames = names.length;
var missingParams = [];
while (numNames--) {
// Only use old params if the names match with the new handler
var oldParams = oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params || {};
var peek = objects[objects.length - 1];
var paramName = names[numNames];
if (isParam(peek)) {
params[paramName] = '' + objects.pop();
} else {
// If we're here, this means only some of the params
// were string/number params, so try and use a param
// value from a previous handler.
if (oldParams.hasOwnProperty(paramName)) {
params[paramName] = oldParams[paramName];
} else {
missingParams.push(paramName);
}
}
}
if (missingParams.length > 0) {
throw new Error(`You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route ${name}.` + ` Missing params: ${missingParams}`);
}
return new UnresolvedRouteInfoByParam(this.router, name, names, params);
}
}
var UnrecognizedURLError = function () {
UnrecognizedURLError.prototype = Object.create(Error.prototype);
UnrecognizedURLError.prototype.constructor = UnrecognizedURLError;
function UnrecognizedURLError(message) {
var error = Error.call(this, message);
this.name = 'UnrecognizedURLError';
this.message = message || 'UnrecognizedURL';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, UnrecognizedURLError);
} else {
this.stack = error.stack;
}
}
return UnrecognizedURLError;
}();
class URLTransitionIntent extends TransitionIntent {
constructor(router, url, data) {
super(router, data);
this.url = url;
this.preTransitionState = undefined;
}
applyToState(oldState) {
var newState = new TransitionState();
var results = this.router.recognizer.recognize(this.url),
i,
len;
if (!results) {
throw new UnrecognizedURLError(this.url);
}
var statesDiffer = false;
var _url = this.url; // Checks if a handler is accessible by URL. If it is not, an error is thrown.
// For the case where the handler is loaded asynchronously, the error will be
// thrown once it is loaded.
function checkHandlerAccessibility(handler) {
if (handler && handler.inaccessibleByURL) {
throw new UnrecognizedURLError(_url);
}
return handler;
}
for (i = 0, len = results.length; i < len; ++i) {
var result = results[i];
var name = result.handler;
var paramNames = [];
if (this.router.recognizer.hasRoute(name)) {
paramNames = this.router.recognizer.handlersFor(name)[i].names;
}
var newRouteInfo = new UnresolvedRouteInfoByParam(this.router, name, paramNames, result.params);
var route = newRouteInfo.route;
if (route) {
checkHandlerAccessibility(route);
} else {
// If the hanlder is being loaded asynchronously, check if we can
// access it after it has resolved
newRouteInfo.routePromise = newRouteInfo.routePromise.then(checkHandlerAccessibility);
}
var oldRouteInfo = oldState.routeInfos[i];
if (statesDiffer || newRouteInfo.shouldSupercede(oldRouteInfo)) {
statesDiffer = true;
newState.routeInfos[i] = newRouteInfo;
} else {
newState.routeInfos[i] = oldRouteInfo;
}
}
merge(newState.queryParams, results.queryParams);
return newState;
}
}
class Router {
constructor(logger) {
this._lastQueryParams = {};
this.state = undefined;
this.oldState = undefined;
this.activeTransition = undefined;
this.currentRouteInfos = undefined;
this._changedQueryParams = undefined;
this.currentSequence = 0;
this.log = logger;
this.recognizer = new _routeRecognizer.default();
this.reset();
}
/**
The main entry point into the router. The API is essentially
the same as the `map` method in `route-recognizer`.
This method extracts the String handler at the last `.to()`
call and uses it as the name of the whole route.
@param {Function} callback
*/
map(callback) {
this.recognizer.map(callback, function (recognizer, routes) {
for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) {
var route = routes[i];
var handler = route.handler;
recognizer.add(routes, {
as: handler
});
proceed = route.path === '/' || route.path === '' || handler.slice(-6) === '.index';
}
});
}
hasRoute(route) {
return this.recognizer.hasRoute(route);
}
queryParamsTransition(changelist, wasTransitioning, oldState, newState) {
this.fireQueryParamDidChange(newState, changelist);
if (!wasTransitioning && this.activeTransition) {
// One of the routes in queryParamsDidChange
// caused a transition. Just return that transition.
return this.activeTransition;
} else {
// Running queryParamsDidChange didn't change anything.
// Just update query params and be on our way.
// We have to return a noop transition that will
// perform a URL update at the end. This gives
// the user the ability to set the url update
// method (default is replaceState).
var newTransition = new Transition(this, undefined, undefined);
newTransition.queryParamsOnly = true;
oldState.queryParams = this.finalizeQueryParamChange(newState.routeInfos, newState.queryParams, newTransition);
newTransition[QUERY_PARAMS_SYMBOL] = newState.queryParams;
this.toReadOnlyInfos(newTransition, newState);
this.routeWillChange(newTransition);
newTransition.promise = newTransition.promise.then(result => {
this._updateURL(newTransition, oldState);
this.didTransition(this.currentRouteInfos);
this.toInfos(newTransition, newState.routeInfos, true);
this.routeDidChange(newTransition);
return result;
}, null, promiseLabel('Transition complete'));
return newTransition;
}
}
transitionByIntent(intent, isIntermediate) {
try {
return this.getTransitionByIntent(intent, isIntermediate);
} catch (e) {
return new Transition(this, intent, undefined, e, undefined);
}
}
recognize(url) {
var intent = new URLTransitionIntent(this, url);
var newState = this.generateNewState(intent);
if (newState === null) {
return newState;
}
var readonlyInfos = toReadOnlyRouteInfo(newState.routeInfos, newState.queryParams);
return readonlyInfos[readonlyInfos.length - 1];
}
recognizeAndLoad(url) {
var intent = new URLTransitionIntent(this, url);
var newState = this.generateNewState(intent);
if (newState === null) {
return _rsvp.Promise.reject(`URL ${url} was not recognized`);
}
var newTransition = new Transition(this, intent, newState, undefined);
return newTransition.then(() => {
var routeInfosWithAttributes = toReadOnlyRouteInfo(newState.routeInfos, newTransition[QUERY_PARAMS_SYMBOL], true);
return routeInfosWithAttributes[routeInfosWithAttributes.length - 1];
});
}
generateNewState(intent) {
try {
return intent.applyToState(this.state, false);
} catch (e) {
return null;
}
}
getTransitionByIntent(intent, isIntermediate) {
var wasTransitioning = !!this.activeTransition;
var oldState = wasTransitioning ? this.activeTransition[STATE_SYMBOL] : this.state;
var newTransition;
var newState = intent.applyToState(oldState, isIntermediate);
var queryParamChangelist = getChangelist(oldState.queryParams, newState.queryParams);
if (routeInfosEqual(newState.routeInfos, oldState.routeInfos)) {
// This is a no-op transition. See if query params changed.
if (queryParamChangelist) {
var _newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState);
_newTransition.queryParamsOnly = true;
return _newTransition;
} // No-op. No need to create a new transition.
return this.activeTransition || new Transition(this, undefined, undefined);
}
if (isIntermediate) {
var transition = new Transition(this, undefined, undefined);
this.toReadOnlyInfos(transition, newState);
this.setupContexts(newState);
this.routeWillChange(transition);
return this.activeTransition;
} // Create a new transition to the destination route.
newTransition = new Transition(this, intent, newState, undefined, this.activeTransition); // transition is to same route with same params, only query params differ.
// not caught above probably because refresh() has been used
if (routeInfosSameExceptQueryParams(newState.routeInfos, oldState.routeInfos)) {
newTransition.queryParamsOnly = true;
}
this.toReadOnlyInfos(newTransition, newState); // Abort and usurp any previously active transition.
if (this.activeTransition) {
this.activeTransition.redirect(newTransition);
}
this.activeTransition = newTransition; // Transition promises by default resolve with resolved state.
// For our purposes, swap out the promise to resolve
// after the transition has been finalized.
newTransition.promise = newTransition.promise.then(result => {
return this.finalizeTransition(newTransition, result);
}, null, promiseLabel('Settle transition promise when transition is finalized'));
if (!wasTransitioning) {
this.notifyExistingHandlers(newState, newTransition);
}
this.fireQueryParamDidChange(newState, queryParamChangelist);
return newTransition;
}
/**
@private
Begins and returns a Transition based on the provided
arguments. Accepts arguments in the form of both URL
transitions and named transitions.
@param {Router} router
@param {Array[Object]} args arguments passed to transitionTo,
replaceWith, or handleURL
*/
doTransition(name, modelsArray = [], isIntermediate = false) {
var lastArg = modelsArray[modelsArray.length - 1];
var queryParams = {};
if (lastArg !== undefined && lastArg.hasOwnProperty('queryParams')) {
queryParams = modelsArray.pop().queryParams;
}
var intent;
if (name === undefined) {
log(this, 'Updating query params'); // A query param update is really just a transition
// into the route you're already on.
var {
routeInfos
} = this.state;
intent = new NamedTransitionIntent(this, routeInfos[routeInfos.length - 1].name, undefined, [], queryParams);
} else if (name.charAt(0) === '/') {
log(this, 'Attempting URL transition to ' + name);
intent = new URLTransitionIntent(this, name);
} else {
log(this, 'Attempting transition to ' + name);
intent = new NamedTransitionIntent(this, name, undefined, modelsArray, queryParams);
}
return this.transitionByIntent(intent, isIntermediate);
}
/**
@private
Updates the URL (if necessary) and calls `setupContexts`
to update the router's array of `currentRouteInfos`.
*/
finalizeTransition(transition, newState) {
try {
log(transition.router, transition.sequence, 'Resolved all models on destination route; finalizing transition.');
var routeInfos = newState.routeInfos; // Run all the necessary enter/setup/exit hooks
this.setupContexts(newState, transition); // Check if a redirect occurred in enter/setup
if (transition.isAborted) {
// TODO: cleaner way? distinguish b/w targetRouteInfos?
this.state.routeInfos = this.currentRouteInfos;
return _rsvp.Promise.reject(logAbort(transition));
}
this._updateURL(transition, newState);
transition.isActive = false;
this.activeTransition = undefined;
this.triggerEvent(this.currentRouteInfos, true, 'didTransition', []);
this.didTransition(this.currentRouteInfos);
this.toInfos(transition, newState.routeInfos, true);
this.routeDidChange(transition);
log(this, transition.sequence, 'TRANSITION COMPLETE.'); // Resolve with the final route.
return routeInfos[routeInfos.length - 1].route;
} catch (e) {
if (!(e instanceof TransitionAbortedError)) {
var infos = transition[STATE_SYMBOL].routeInfos;
transition.trigger(true, 'error', e, transition, infos[infos.length - 1].route);
transition.abort();
}
throw e;
}
}
/**
@private
Takes an Array of `RouteInfo`s, figures out which ones are
exiting, entering, or changing contexts, and calls the
proper route hooks.
For example, consider the following tree of routes. Each route is
followed by the URL segment it handles.
```
|~index ("/")
| |~posts ("/posts")
| | |-showPost ("/:id")
| | |-newPost ("/new")
| | |-editPost ("/edit")
| |~about ("/about/:id")
```
Consider the following transitions:
1. A URL transition to `/posts/1`.
1. Triggers the `*model` callbacks on the
`index`, `posts`, and `showPost` routes
2. Triggers the `enter` callback on the same
3. Triggers the `setup` callback on the same
2. A direct transition to `newPost`
1. Triggers the `exit` callback on `showPost`
2. Triggers the `enter` callback on `newPost`
3. Triggers the `setup` callback on `newPost`
3. A direct transition to `about` with a specified
context object
1. Triggers the `exit` callback on `newPost`
and `posts`
2. Triggers the `serialize` callback on `about`
3. Triggers the `enter` callback on `about`
4. Triggers the `setup` callback on `about`
@param {Router} transition
@param {TransitionState} newState
*/
setupContexts(newState, transition) {
var partition = this.partitionRoutes(this.state, newState);
var i, l, route;
for (i = 0, l = partition.exited.length; i < l; i++) {
route = partition.exited[i].route;
delete route.context;
if (route !== undefined) {
if (route._internalReset !== undefined) {
route._internalReset(true, transition);
}
if (route.exit !== undefined) {
route.exit(transition);
}
}
}
var oldState = this.oldState = this.state;
this.state = newState;
var currentRouteInfos = this.currentRouteInfos = partition.unchanged.slice();
try {
for (i = 0, l = partition.reset.length; i < l; i++) {
route = partition.reset[i].route;
if (route !== undefined) {
if (route._internalReset !== undefined) {
route._internalReset(false, transition);
}
}
}
for (i = 0, l = partition.updatedContext.length; i < l; i++) {
this.routeEnteredOrUpdated(currentRouteInfos, partition.updatedContext[i], false, transition);
}
for (i = 0, l = partition.entered.length; i < l; i++) {
this.routeEnteredOrUpdated(currentRouteInfos, partition.entered[i], true, transition);
}
} catch (e) {
this.state = oldState;
this.currentRouteInfos = oldState.routeInfos;
throw e;
}
this.state.queryParams = this.finalizeQueryParamChange(currentRouteInfos, newState.queryParams, transition);
}
/**
@private
Fires queryParamsDidChange event
*/
fireQueryParamDidChange(newState, queryParamChangelist) {
// If queryParams changed trigger event
if (queryParamChangelist) {
// This is a little hacky but we need some way of storing
// changed query params given that no activeTransition
// is guaranteed to have occurred.
this._changedQueryParams = queryParamChangelist.all;
this.triggerEvent(newState.routeInfos, true, 'queryParamsDidChange', [queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed]);
this._changedQueryParams = undefined;
}
}
/**
@private
Helper method used by setupContexts. Handles errors or redirects
that may happen in enter/setup.
*/
routeEnteredOrUpdated(currentRouteInfos, routeInfo, enter, transition) {
var route = routeInfo.route,
context = routeInfo.context;
function _routeEnteredOrUpdated(route) {
if (enter) {
if (route.enter !== undefined) {
route.enter(transition);
}
}
if (transition && transition.isAborted) {
throw new TransitionAbortedError();
}
route.context = context;
if (route.contextDidChange !== undefined) {
route.contextDidChange();
}
if (route.setup !== undefined) {
route.setup(context, transition);
}
if (transition && transition.isAborted) {
throw new TransitionAbortedError();
}
currentRouteInfos.push(routeInfo);
return route;
} // If the route doesn't exist, it means we haven't resolved the route promise yet
if (route === undefined) {
routeInfo.routePromise = routeInfo.routePromise.then(_routeEnteredOrUpdated);
} else {
_routeEnteredOrUpdated(route);
}
return true;
}
/**
@private
This function is called when transitioning from one URL to
another to determine which routes are no longer active,
which routes are newly active, and which routes remain
active but have their context changed.
Take a list of old routes and new routes and partition
them into four buckets:
* unchanged: the route was active in both the old and
new URL, and its context remains the same
* updated context: the route was active in both the
old and new URL, but its context changed. The route's
`setup` method, if any, will be called with the new
context.
* exited: the route was active in the old URL, but is
no longer active.
* entered: the route was not active in the old URL, but
is now active.
The PartitionedRoutes structure has four fields:
* `updatedContext`: a list of `RouteInfo` objects that
represent routes that remain active but have a changed
context
* `entered`: a list of `RouteInfo` objects that represent
routes that are newly active
* `exited`: a list of `RouteInfo` objects that are no
longer active.
* `unchanged`: a list of `RouteInfo` objects that remain active.
@param {Array[InternalRouteInfo]} oldRoutes a list of the route
information for the previous URL (or `[]` if this is the
first handled transition)
@param {Array[InternalRouteInfo]} newRoutes a list of the route
information for the new URL
@return {Partition}
*/
partitionRoutes(oldState, newState) {
var oldRouteInfos = oldState.routeInfos;
var newRouteInfos = newState.routeInfos;
var routes = {
updatedContext: [],
exited: [],
entered: [],
unchanged: [],
reset: []
};
var routeChanged,
contextChanged = false,
i,
l;
for (i = 0, l = newRouteInfos.length; i < l; i++) {
var oldRouteInfo = oldRouteInfos[i],
newRouteInfo = newRouteInfos[i];
if (!oldRouteInfo || oldRouteInfo.route !== newRouteInfo.route) {
routeChanged = true;
}
if (routeChanged) {
routes.entered.push(newRouteInfo);
if (oldRouteInfo) {
routes.exited.unshift(oldRouteInfo);
}
} else if (contextChanged || oldRouteInfo.context !== newRouteInfo.context) {
contextChanged = true;
routes.updatedContext.push(newRouteInfo);
} else {
routes.unchanged.push(oldRouteInfo);
}
}
for (i = newRouteInfos.length, l = oldRouteInfos.length; i < l; i++) {
routes.exited.unshift(oldRouteInfos[i]);
}
routes.reset = routes.updatedContext.slice();
routes.reset.reverse();
return routes;
}
_updateURL(transition, state) {
var urlMethod = transition.urlMethod;
if (!urlMethod) {
return;
}
var {
routeInfos
} = state;
var {
name: routeName
} = routeInfos[routeInfos.length - 1];
var params = {};
for (var i = routeInfos.length - 1; i >= 0; --i) {
var routeInfo = routeInfos[i];
merge(params, routeInfo.params);
if (routeInfo.route.inaccessibleByURL) {
urlMethod = null;
}
}
if (urlMethod) {
params.queryParams = transition._visibleQueryParams || state.queryParams;
var url = this.recognizer.generate(routeName, params); // transitions during the initial transition must always use replaceURL.
// When the app boots, you are at a url, e.g. /foo. If some route
// redirects to bar as part of the initial transition, you don't want to
// add a history entry for /foo. If you do, pressing back will immediately
// hit the redirect again and take you back to /bar, thus killing the back
// button
var initial = transition.isCausedByInitialTransition; // say you are at / and you click a link to route /foo. In /foo's
// route, the transition is aborted using replacewith('/bar').
// Because the current url is still /, the history entry for / is
// removed from the history. Clicking back will take you to the page
// you were on before /, which is often not even the app, thus killing
// the back button. That's why updateURL is always correct for an
// aborting transition that's not the initial transition
var replaceAndNotAborting = urlMethod === 'replace' && !transition.isCausedByAbortingTransition; // because calling refresh causes an aborted transition, this needs to be
// special cased - if the initial transition is a replace transition, the
// urlMethod should be honored here.
var isQueryParamsRefreshTransition = transition.queryParamsOnly && urlMethod === 'replace'; // say you are at / and you a `replaceWith(/foo)` is called. Then, that
// transition is aborted with `replaceWith(/bar)`. At the end, we should
// end up with /bar replacing /. We are replacing the replace. We only
// will replace the initial route if all subsequent aborts are also
// replaces. However, there is some ambiguity around the correct behavior
// here.
var replacingReplace = urlMethod === 'replace' && transition.isCausedByAbortingReplaceTransition;
if (initial || replaceAndNotAborting || isQueryParamsRefreshTransition || replacingReplace) {
this.replaceURL(url);
} else {
this.updateURL(url);
}
}
}
finalizeQueryParamChange(resolvedHandlers, newQueryParams, transition) {
// We fire a finalizeQueryParamChange event which
// gives the new route hierarchy a chance to tell
// us which query params it's consuming and what
// their final values are. If a query param is
// no longer consumed in the final route hierarchy,
// its serialized segment will be removed
// from the URL.
for (var k in newQueryParams) {
if (newQueryParams.hasOwnProperty(k) && newQueryParams[k] === null) {
delete newQueryParams[k];
}
}
var finalQueryParamsArray = [];
this.triggerEvent(resolvedHandlers, true, 'finalizeQueryParamChange', [newQueryParams, finalQueryParamsArray, transition]);
if (transition) {
transition._visibleQueryParams = {};
}
var finalQueryParams = {};
for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) {
var qp = finalQueryParamsArray[i];
finalQueryParams[qp.key] = qp.value;
if (transition && qp.visible !== false) {
transition._visibleQueryParams[qp.key] = qp.value;
}
}
return finalQueryParams;
}
toReadOnlyInfos(newTransition, newState) {
var oldRouteInfos = this.state.routeInfos;
this.fromInfos(newTransition, oldRouteInfos);
this.toInfos(newTransition, newState.routeInfos);
this._lastQueryParams = newState.queryParams;
}
fromInfos(newTransition, oldRouteInfos) {
if (newTransition !== undefined && oldRouteInfos.length > 0) {
var fromInfos = toReadOnlyRouteInfo(oldRouteInfos, (0, _polyfills.assign)({}, this._lastQueryParams), true);
newTransition.from = fromInfos[fromInfos.length - 1] || null;
}
}
toInfos(newTransition, newRouteInfos, includeAttributes = false) {
if (newTransition !== undefined && newRouteInfos.length > 0) {
var toInfos = toReadOnlyRouteInfo(newRouteInfos, (0, _polyfills.assign)({}, newTransition[QUERY_PARAMS_SYMBOL]), includeAttributes);
newTransition.to = toInfos[toInfos.length - 1] || null;
}
}
notifyExistingHandlers(newState, newTransition) {
var oldRouteInfos = this.state.routeInfos,
i,
oldRouteInfoLen,
oldHandler,
newRouteInfo;
oldRouteInfoLen = oldRouteInfos.length;
for (i = 0; i < oldRouteInfoLen; i++) {
oldHandler = oldRouteInfos[i];
newRouteInfo = newState.routeInfos[i];
if (!newRouteInfo || oldHandler.name !== newRouteInfo.name) {
break;
}
if (!newRouteInfo.isResolved) {}
}
this.triggerEvent(oldRouteInfos, true, 'willTransition', [newTransition]);
this.routeWillChange(newTransition);
this.willTransition(oldRouteInfos, newState.routeInfos, newTransition);
}
/**
Clears the current and target route routes and triggers exit
on each of them starting at the leaf and traversing up through
its ancestors.
*/
reset() {
if (this.state) {
forEach(this.state.routeInfos.slice().reverse(), function (routeInfo) {
var route = routeInfo.route;
if (route !== undefined) {
if (route.exit !== undefined) {
route.exit();
}
}
return true;
});
}
this.oldState = undefined;
this.state = new TransitionState();
this.currentRouteInfos = undefined;
}
/**
let handler = routeInfo.handler;
The entry point for handling a change to the URL (usually
via the back and forward button).
Returns an Array of handlers and the parameters associated
with those parameters.
@param {String} url a URL to process
@return {Array} an Array of `[handler, parameter]` tuples
*/
handleURL(url) {
// Perform a URL-based transition, but don't change
// the URL afterward, since it already happened.
if (url.charAt(0) !== '/') {
url = '/' + url;
}
return this.doTransition(url).method(null);
}
/**
Transition into the specified named route.
If necessary, trigger the exit callback on any routes
that are no longer represented by the target route.
@param {String} name the name of the route
*/
transitionTo(name, ...contexts) {
if (typeof name === 'object') {
contexts.push(name);
return this.doTransition(undefined, contexts, false);
}
return this.doTransition(name, contexts);
}
intermediateTransitionTo(name, ...args) {
return this.doTransition(name, args, true);
}
refresh(pivotRoute) {
var previousTransition = this.activeTransition;
var state = previousTransition ? previousTransition[STATE_SYMBOL] : this.state;
var routeInfos = state.routeInfos;
if (pivotRoute === undefined) {
pivotRoute = routeInfos[0].route;
}
log(this, 'Starting a refresh transition');
var name = routeInfos[routeInfos.length - 1].name;
var intent = new NamedTransitionIntent(this, name, pivotRoute, [], this._changedQueryParams || state.queryParams);
var newTransition = this.transitionByIntent(intent, false); // if the previous transition is a replace transition, that needs to be preserved
if (previousTransition && previousTransition.urlMethod === 'replace') {
newTransition.method(previousTransition.urlMethod);
}
return newTransition;
}
/**
Identical to `transitionTo` except that the current URL will be replaced
if possible.
This method is intended primarily for use with `replaceState`.
@param {String} name the name of the route
*/
replaceWith(name) {
return this.doTransition(name).method('replace');
}
/**
Take a named route and context objects and generate a
URL.
@param {String} name the name of the route to generate
a URL for
@param {...Object} objects a list of objects to serialize
@return {String} a URL
*/
generate(routeName, ...args) {
var partitionedArgs = extractQueryParams(args),
suppliedParams = partitionedArgs[0],
queryParams = partitionedArgs[1]; // Construct a TransitionIntent with the provided params
// and apply it to the present state of the router.
var intent = new NamedTransitionIntent(this, routeName, undefined, suppliedParams);
var state = intent.applyToState(this.state, false);
var params = {};
for (var i = 0, len = state.routeInfos.length; i < len; ++i) {
var routeInfo = state.routeInfos[i];
var routeParams = routeInfo.serialize();
merge(params, routeParams);
}
params.queryParams = queryParams;
return this.recognizer.generate(routeName, params);
}
applyIntent(routeName, contexts) {
var intent = new NamedTransitionIntent(this, routeName, undefined, contexts);
var state = this.activeTransition && this.activeTransition[STATE_SYMBOL] || this.state;
return intent.applyToState(state, false);
}
isActiveIntent(routeName, contexts, queryParams, _state) {
var state = _state || this.state,
targetRouteInfos = state.routeInfos,
routeInfo,
len;
if (!targetRouteInfos.length) {
return false;
}
var targetHandler = targetRouteInfos[targetRouteInfos.length - 1].name;
var recogHandlers = this.recognizer.handlersFor(targetHandler);
var index = 0;
for (len = recogHandlers.length; index < len; ++index) {
routeInfo = targetRouteInfos[index];
if (routeInfo.name === routeName) {
break;
}
}
if (index === recogHandlers.length) {
// The provided route name isn't even in the route hierarchy.
return false;
}
var testState = new TransitionState();
testState.routeInfos = targetRouteInfos.slice(0, index + 1);
recogHandlers = recogHandlers.slice(0, index + 1);
var intent = new NamedTransitionIntent(this, targetHandler, undefined, contexts);
var newState = intent.applyToHandlers(testState, recogHandlers, targetHandler, true, true);
var routesEqual = routeInfosEqual(newState.routeInfos, testState.routeInfos);
if (!queryParams || !routesEqual) {
return routesEqual;
} // Get a hash of QPs that will still be active on new route
var activeQPsOnNewHandler = {};
merge(activeQPsOnNewHandler, queryParams);
var activeQueryParams = state.queryParams;
for (var key in activeQueryParams) {
if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) {
activeQPsOnNewHandler[key] = activeQueryParams[key];
}
}
return routesEqual && !getChangelist(activeQPsOnNewHandler, queryParams);
}
isActive(routeName, ...args) {
var partitionedArgs = extractQueryParams(args);
return this.isActiveIntent(routeName, partitionedArgs[0], partitionedArgs[1]);
}
trigger(name, ...args) {
this.triggerEvent(this.currentRouteInfos, false, name, args);
}
}
function routeInfosEqual(routeInfos, otherRouteInfos) {
if (routeInfos.length !== otherRouteInfos.length) {
return false;
}
for (var i = 0, len = routeInfos.length; i < len; ++i) {
if (routeInfos[i] !== otherRouteInfos[i]) {
return false;
}
}
return true;
}
function routeInfosSameExceptQueryParams(routeInfos, otherRouteInfos) {
if (routeInfos.length !== otherRouteInfos.length) {
return false;
}
for (var i = 0, len = routeInfos.length; i < len; ++i) {
if (routeInfos[i].name !== otherRouteInfos[i].name) {
return false;
}
if (!paramsEqual(routeInfos[i].params, otherRouteInfos[i].params)) {
return false;
}
}
return true;
}
function paramsEqual(params, otherParams) {
if (!params && !otherParams) {
return true;
} else if (!params && !!otherParams || !!params && !otherParams) {
// one is falsy but other is not;
return false;
}
var keys = Object.keys(params);
var otherKeys = Object.keys(otherParams);
if (keys.length !== otherKeys.length) {
return false;
}
for (var i = 0, len = keys.length; i < len; ++i) {
var key = keys[i];
if (params[key] !== otherParams[key]) {
return false;
}
}
return true;
}
var _default = Router;
_exports.default = _default;
});
define("rsvp", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.asap = asap;
_exports.all = all$1;
_exports.allSettled = allSettled;
_exports.race = race$1;
_exports.hash = hash;
_exports.hashSettled = hashSettled;
_exports.rethrow = rethrow;
_exports.defer = defer;
_exports.denodeify = denodeify;
_exports.configure = configure;
_exports.on = on;
_exports.off = off;
_exports.resolve = resolve$2;
_exports.reject = reject$2;
_exports.map = map;
_exports.filter = filter;
_exports.async = _exports.EventTarget = _exports.Promise = _exports.cast = _exports.default = void 0;
function callbacksFor(object) {
var callbacks = object._promiseCallbacks;
if (!callbacks) {
callbacks = object._promiseCallbacks = {};
}
return callbacks;
}
/**
@class EventTarget
@for rsvp
@public
*/
var EventTarget = {
/**
`EventTarget.mixin` extends an object with EventTarget methods. For
Example:
```javascript
import EventTarget from 'rsvp';
let object = {};
EventTarget.mixin(object);
object.on('finished', function(event) {
// handle event
});
object.trigger('finished', { detail: value });
```
`EventTarget.mixin` also works with prototypes:
```javascript
import EventTarget from 'rsvp';
let Person = function() {};
EventTarget.mixin(Person.prototype);
let yehuda = new Person();
let tom = new Person();
yehuda.on('poke', function(event) {
console.log('Yehuda says OW');
});
tom.on('poke', function(event) {
console.log('Tom says OW');
});
yehuda.trigger('poke');
tom.trigger('poke');
```
@method mixin
@for rsvp
@private
@param {Object} object object to extend with EventTarget methods
*/
mixin(object) {
object.on = this.on;
object.off = this.off;
object.trigger = this.trigger;
object._promiseCallbacks = undefined;
return object;
},
/**
Registers a callback to be executed when `eventName` is triggered
```javascript
object.on('event', function(eventInfo){
// handle the event
});
object.trigger('event');
```
@method on
@for EventTarget
@private
@param {String} eventName name of the event to listen for
@param {Function} callback function to be called when the event is triggered.
*/
on(eventName, callback) {
if (typeof callback !== 'function') {
throw new TypeError('Callback must be a function');
}
var allCallbacks = callbacksFor(this);
var callbacks = allCallbacks[eventName];
if (!callbacks) {
callbacks = allCallbacks[eventName] = [];
}
if (callbacks.indexOf(callback) === -1) {
callbacks.push(callback);
}
},
/**
You can use `off` to stop firing a particular callback for an event:
```javascript
function doStuff() { // do stuff! }
object.on('stuff', doStuff);
object.trigger('stuff'); // doStuff will be called
// Unregister ONLY the doStuff callback
object.off('stuff', doStuff);
object.trigger('stuff'); // doStuff will NOT be called
```
If you don't pass a `callback` argument to `off`, ALL callbacks for the
event will not be executed when the event fires. For example:
```javascript
let callback1 = function(){};
let callback2 = function(){};
object.on('stuff', callback1);
object.on('stuff', callback2);
object.trigger('stuff'); // callback1 and callback2 will be executed.
object.off('stuff');
object.trigger('stuff'); // callback1 and callback2 will not be executed!
```
@method off
@for rsvp
@private
@param {String} eventName event to stop listening to
@param {Function} [callback] optional argument. If given, only the function
given will be removed from the event's callback queue. If no `callback`
argument is given, all callbacks will be removed from the event's callback
queue.
*/
off(eventName, callback) {
var allCallbacks = callbacksFor(this);
if (!callback) {
allCallbacks[eventName] = [];
return;
}
var callbacks = allCallbacks[eventName];
var index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
},
/**
Use `trigger` to fire custom events. For example:
```javascript
object.on('foo', function(){
console.log('foo event happened!');
});
object.trigger('foo');
// 'foo event happened!' logged to the console
```
You can also pass a value as a second argument to `trigger` that will be
passed as an argument to all event listeners for the event:
```javascript
object.on('foo', function(value){
console.log(value.name);
});
object.trigger('foo', { name: 'bar' });
// 'bar' logged to the console
```
@method trigger
@for rsvp
@private
@param {String} eventName name of the event to be triggered
@param {*} [options] optional value to be passed to any event handlers for
the given `eventName`
*/
trigger(eventName, options, label) {
var allCallbacks = callbacksFor(this);
var callbacks = allCallbacks[eventName];
if (callbacks) {
// Don't cache the callbacks.length since it may grow
var callback;
for (var i = 0; i < callbacks.length; i++) {
callback = callbacks[i];
callback(options, label);
}
}
}
};
_exports.EventTarget = EventTarget;
var config = {
instrument: false
};
EventTarget['mixin'](config);
function configure(name, value) {
if (arguments.length === 2) {
config[name] = value;
} else {
return config[name];
}
}
var queue = [];
function scheduleFlush() {
setTimeout(() => {
for (var i = 0; i < queue.length; i++) {
var entry = queue[i];
var payload = entry.payload;
payload.guid = payload.key + payload.id;
payload.childGuid = payload.key + payload.childId;
if (payload.error) {
payload.stack = payload.error.stack;
}
config['trigger'](entry.name, entry.payload);
}
queue.length = 0;
}, 50);
}
function instrument(eventName, promise, child) {
if (1 === queue.push({
name: eventName,
payload: {
key: promise._guidKey,
id: promise._id,
eventName: eventName,
detail: promise._result,
childId: child && child._id,
label: promise._label,
timeStamp: Date.now(),
error: config["instrument-with-stack"] ? new Error(promise._label) : null
}
})) {
scheduleFlush();
}
}
/**
`Promise.resolve` returns a promise that will become resolved with the
passed `value`. It is shorthand for the following:
```javascript
import Promise from 'rsvp';
let promise = new Promise(function(resolve, reject){
resolve(1);
});
promise.then(function(value){
// value === 1
});
```
Instead of writing the above, your code now simply becomes the following:
```javascript
import Promise from 'rsvp';
let promise = RSVP.Promise.resolve(1);
promise.then(function(value){
// value === 1
});
```
@method resolve
@for Promise
@static
@param {*} object value that the returned promise will be resolved with
@param {String} [label] optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise that will become fulfilled with the given
`value`
*/
function resolve$$1(object, label) {
/*jshint validthis:true */
var Constructor = this;
if (object && typeof object === 'object' && object.constructor === Constructor) {
return object;
}
var promise = new Constructor(noop, label);
resolve$1(promise, object);
return promise;
}
function withOwnPromise() {
return new TypeError('A promises callback cannot return that same promise.');
}
function objectOrFunction(x) {
var type = typeof x;
return x !== null && (type === 'object' || type === 'function');
}
function noop() {}
var PENDING = void 0;
var FULFILLED = 1;
var REJECTED = 2;
function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) {
try {
then$$1.call(value, fulfillmentHandler, rejectionHandler);
} catch (e) {
return e;
}
}
function handleForeignThenable(promise, thenable, then$$1) {
config.async(promise => {
var sealed = false;
var error = tryThen(then$$1, thenable, value => {
if (sealed) {
return;
}
sealed = true;
if (thenable === value) {
fulfill(promise, value);
} else {
resolve$1(promise, value);
}
}, reason => {
if (sealed) {
return;
}
sealed = true;
reject(promise, reason);
}, 'Settle: ' + (promise._label || ' unknown promise'));
if (!sealed && error) {
sealed = true;
reject(promise, error);
}
}, promise);
}
function handleOwnThenable(promise, thenable) {
if (thenable._state === FULFILLED) {
fulfill(promise, thenable._result);
} else if (thenable._state === REJECTED) {
thenable._onError = null;
reject(promise, thenable._result);
} else {
subscribe(thenable, undefined, value => {
if (thenable === value) {
fulfill(promise, value);
} else {
resolve$1(promise, value);
}
}, reason => reject(promise, reason));
}
}
function handleMaybeThenable(promise, maybeThenable, then$$1) {
var isOwnThenable = maybeThenable.constructor === promise.constructor && then$$1 === then && promise.constructor.resolve === resolve$$1;
if (isOwnThenable) {
handleOwnThenable(promise, maybeThenable);
} else if (typeof then$$1 === 'function') {
handleForeignThenable(promise, maybeThenable, then$$1);
} else {
fulfill(promise, maybeThenable);
}
}
function resolve$1(promise, value) {
if (promise === value) {
fulfill(promise, value);
} else if (objectOrFunction(value)) {
var then$$1;
try {
then$$1 = value.then;
} catch (error) {
reject(promise, error);
return;
}
handleMaybeThenable(promise, value, then$$1);
} else {
fulfill(promise, value);
}
}
function publishRejection(promise) {
if (promise._onError) {
promise._onError(promise._result);
}
publish(promise);
}
function fulfill(promise, value) {
if (promise._state !== PENDING) {
return;
}
promise._result = value;
promise._state = FULFILLED;
if (promise._subscribers.length === 0) {
if (config.instrument) {
instrument('fulfilled', promise);
}
} else {
config.async(publish, promise);
}
}
function reject(promise, reason) {
if (promise._state !== PENDING) {
return;
}
promise._state = REJECTED;
promise._result = reason;
config.async(publishRejection, promise);
}
function subscribe(parent, child, onFulfillment, onRejection) {
var subscribers = parent._subscribers;
var length = subscribers.length;
parent._onError = null;
subscribers[length] = child;
subscribers[length + FULFILLED] = onFulfillment;
subscribers[length + REJECTED] = onRejection;
if (length === 0 && parent._state) {
config.async(publish, parent);
}
}
function publish(promise) {
var subscribers = promise._subscribers;
var settled = promise._state;
if (config.instrument) {
instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
}
if (subscribers.length === 0) {
return;
}
var child,
callback,
result = promise._result;
for (var i = 0; i < subscribers.length; i += 3) {
child = subscribers[i];
callback = subscribers[i + settled];
if (child) {
invokeCallback(settled, child, callback, result);
} else {
callback(result);
}
}
promise._subscribers.length = 0;
}
function invokeCallback(state, promise, callback, result) {
var hasCallback = typeof callback === 'function';
var value,
succeeded = true,
error;
if (hasCallback) {
try {
value = callback(result);
} catch (e) {
succeeded = false;
error = e;
}
} else {
value = result;
}
if (promise._state !== PENDING) {// noop
} else if (value === promise) {
reject(promise, withOwnPromise());
} else if (succeeded === false) {
reject(promise, error);
} else if (hasCallback) {
resolve$1(promise, value);
} else if (state === FULFILLED) {
fulfill(promise, value);
} else if (state === REJECTED) {
reject(promise, value);
}
}
function initializePromise(promise, resolver) {
var resolved = false;
try {
resolver(value => {
if (resolved) {
return;
}
resolved = true;
resolve$1(promise, value);
}, reason => {
if (resolved) {
return;
}
resolved = true;
reject(promise, reason);
});
} catch (e) {
reject(promise, e);
}
}
function then(onFulfillment, onRejection, label) {
var parent = this;
var state = parent._state;
if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
config.instrument && instrument('chained', parent, parent);
return parent;
}
parent._onError = null;
var child = new parent.constructor(noop, label);
var result = parent._result;
config.instrument && instrument('chained', parent, child);
if (state === PENDING) {
subscribe(parent, child, onFulfillment, onRejection);
} else {
var callback = state === FULFILLED ? onFulfillment : onRejection;
config.async(() => invokeCallback(state, child, callback, result));
}
return child;
}
class Enumerator {
constructor(Constructor, input, abortOnReject, label) {
this._instanceConstructor = Constructor;
this.promise = new Constructor(noop, label);
this._abortOnReject = abortOnReject;
this._isUsingOwnPromise = Constructor === Promise;
this._isUsingOwnResolve = Constructor.resolve === resolve$$1;
this._init(...arguments);
}
_init(Constructor, input) {
var len = input.length || 0;
this.length = len;
this._remaining = len;
this._result = new Array(len);
this._enumerate(input);
}
_enumerate(input) {
var length = this.length;
var promise = this.promise;
for (var i = 0; promise._state === PENDING && i < length; i++) {
this._eachEntry(input[i], i, true);
}
this._checkFullfillment();
}
_checkFullfillment() {
if (this._remaining === 0) {
var result = this._result;
fulfill(this.promise, result);
this._result = null;
}
}
_settleMaybeThenable(entry, i, firstPass) {
var c = this._instanceConstructor;
if (this._isUsingOwnResolve) {
var then$$1,
error,
succeeded = true;
try {
then$$1 = entry.then;
} catch (e) {
succeeded = false;
error = e;
}
if (then$$1 === then && entry._state !== PENDING) {
entry._onError = null;
this._settledAt(entry._state, i, entry._result, firstPass);
} else if (typeof then$$1 !== 'function') {
this._settledAt(FULFILLED, i, entry, firstPass);
} else if (this._isUsingOwnPromise) {
var promise = new c(noop);
if (succeeded === false) {
reject(promise, error);
} else {
handleMaybeThenable(promise, entry, then$$1);
this._willSettleAt(promise, i, firstPass);
}
} else {
this._willSettleAt(new c(resolve => resolve(entry)), i, firstPass);
}
} else {
this._willSettleAt(c.resolve(entry), i, firstPass);
}
}
_eachEntry(entry, i, firstPass) {
if (entry !== null && typeof entry === 'object') {
this._settleMaybeThenable(entry, i, firstPass);
} else {
this._setResultAt(FULFILLED, i, entry, firstPass);
}
}
_settledAt(state, i, value, firstPass) {
var promise = this.promise;
if (promise._state === PENDING) {
if (this._abortOnReject && state === REJECTED) {
reject(promise, value);
} else {
this._setResultAt(state, i, value, firstPass);
this._checkFullfillment();
}
}
}
_setResultAt(state, i, value, firstPass) {
this._remaining--;
this._result[i] = value;
}
_willSettleAt(promise, i, firstPass) {
subscribe(promise, undefined, value => this._settledAt(FULFILLED, i, value, firstPass), reason => this._settledAt(REJECTED, i, reason, firstPass));
}
}
function setSettledResult(state, i, value) {
this._remaining--;
if (state === FULFILLED) {
this._result[i] = {
state: 'fulfilled',
value: value
};
} else {
this._result[i] = {
state: 'rejected',
reason: value
};
}
}
/**
`Promise.all` accepts an array of promises, and returns a new promise which
is fulfilled with an array of fulfillment values for the passed promises, or
rejected with the reason of the first passed promise to be rejected. It casts all
elements of the passed iterable to promises as it runs this algorithm.
Example:
```javascript
import Promise, { resolve } from 'rsvp';
let promise1 = resolve(1);
let promise2 = resolve(2);
let promise3 = resolve(3);
let promises = [ promise1, promise2, promise3 ];
Promise.all(promises).then(function(array){
// The array here would be [ 1, 2, 3 ];
});
```
If any of the `promises` given to `RSVP.all` are rejected, the first promise
that is rejected will be given as an argument to the returned promises's
rejection handler. For example:
Example:
```javascript
import Promise, { resolve, reject } from 'rsvp';
let promise1 = resolve(1);
let promise2 = reject(new Error("2"));
let promise3 = reject(new Error("3"));
let promises = [ promise1, promise2, promise3 ];
Promise.all(promises).then(function(array){
// Code here never runs because there are rejected promises!
}, function(error) {
// error.message === "2"
});
```
@method all
@for Promise
@param {Array} entries array of promises
@param {String} [label] optional string for labeling the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled when all `promises` have been
fulfilled, or rejected if any of them become rejected.
@static
*/
function all(entries, label) {
if (!Array.isArray(entries)) {
return this.reject(new TypeError("Promise.all must be called with an array"), label);
}
return new Enumerator(this, entries, true
/* abort on reject */
, label).promise;
}
/**
`Promise.race` returns a new promise which is settled in the same way as the
first passed promise to settle.
Example:
```javascript
import Promise from 'rsvp';
let promise1 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve('promise 1');
}, 200);
});
let promise2 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve('promise 2');
}, 100);
});
Promise.race([promise1, promise2]).then(function(result){
// result === 'promise 2' because it was resolved before promise1
// was resolved.
});
```
`Promise.race` is deterministic in that only the state of the first
settled promise matters. For example, even if other promises given to the
`promises` array argument are resolved, but the first settled promise has
become rejected before the other promises became fulfilled, the returned
promise will become rejected:
```javascript
import Promise from 'rsvp';
let promise1 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve('promise 1');
}, 200);
});
let promise2 = new Promise(function(resolve, reject){
setTimeout(function(){
reject(new Error('promise 2'));
}, 100);
});
Promise.race([promise1, promise2]).then(function(result){
// Code here never runs
}, function(reason){
// reason.message === 'promise 2' because promise 2 became rejected before
// promise 1 became fulfilled
});
```
An example real-world use case is implementing timeouts:
```javascript
import Promise from 'rsvp';
Promise.race([ajax('foo.json'), timeout(5000)])
```
@method race
@for Promise
@static
@param {Array} entries array of promises to observe
@param {String} [label] optional string for describing the promise returned.
Useful for tooling.
@return {Promise} a promise which settles in the same way as the first passed
promise to settle.
*/
function race(entries, label) {
/*jshint validthis:true */
var Constructor = this;
var promise = new Constructor(noop, label);
if (!Array.isArray(entries)) {
reject(promise, new TypeError('Promise.race must be called with an array'));
return promise;
}
for (var i = 0; promise._state === PENDING && i < entries.length; i++) {
subscribe(Constructor.resolve(entries[i]), undefined, value => resolve$1(promise, value), reason => reject(promise, reason));
}
return promise;
}
/**
`Promise.reject` returns a promise rejected with the passed `reason`.
It is shorthand for the following:
```javascript
import Promise from 'rsvp';
let promise = new Promise(function(resolve, reject){
reject(new Error('WHOOPS'));
});
promise.then(function(value){
// Code here doesn't run because the promise is rejected!
}, function(reason){
// reason.message === 'WHOOPS'
});
```
Instead of writing the above, your code now simply becomes the following:
```javascript
import Promise from 'rsvp';
let promise = Promise.reject(new Error('WHOOPS'));
promise.then(function(value){
// Code here doesn't run because the promise is rejected!
}, function(reason){
// reason.message === 'WHOOPS'
});
```
@method reject
@for Promise
@static
@param {*} reason value that the returned promise will be rejected with.
@param {String} [label] optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise rejected with the given `reason`.
*/
function reject$1(reason, label) {
/*jshint validthis:true */
var Constructor = this;
var promise = new Constructor(noop, label);
reject(promise, reason);
return promise;
}
var guidKey = 'rsvp_' + Date.now() + '-';
var counter = 0;
function needsResolver() {
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}
function needsNew() {
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}
/**
Promise objects represent the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promise’s eventual value or the reason
why the promise cannot be fulfilled.
Terminology
-----------
- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
- `thenable` is an object or function that defines a `then` method.
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
- `exception` is a value that is thrown using the throw statement.
- `reason` is a value that indicates why a promise was rejected.
- `settled` the final resting state of a promise, fulfilled or rejected.
A promise can be in one of three states: pending, fulfilled, or rejected.
Promises that are fulfilled have a fulfillment value and are in the fulfilled
state. Promises that are rejected have a rejection reason and are in the
rejected state. A fulfillment value is never a thenable.
Promises can also be said to *resolve* a value. If this value is also a
promise, then the original promise's settled state will match the value's
settled state. So a promise that *resolves* a promise that rejects will
itself reject, and a promise that *resolves* a promise that fulfills will
itself fulfill.
Basic Usage:
------------
```js
let promise = new Promise(function(resolve, reject) {
// on success
resolve(value);
// on failure
reject(reason);
});
promise.then(function(value) {
// on fulfillment
}, function(reason) {
// on rejection
});
```
Advanced Usage:
---------------
Promises shine when abstracting away asynchronous interactions such as
`XMLHttpRequest`s.
```js
function getJSON(url) {
return new Promise(function(resolve, reject){
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = handler;
xhr.responseType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
}
}
};
});
}
getJSON('/posts.json').then(function(json) {
// on fulfillment
}, function(reason) {
// on rejection
});
```
Unlike callbacks, promises are great composable primitives.
```js
Promise.all([
getJSON('/posts'),
getJSON('/comments')
]).then(function(values){
values[0] // => postsJSON
values[1] // => commentsJSON
return values;
});
```
@class Promise
@public
@param {function} resolver
@param {String} [label] optional string for labeling the promise.
Useful for tooling.
@constructor
*/
class Promise {
constructor(resolver, label) {
this._id = counter++;
this._label = label;
this._state = undefined;
this._result = undefined;
this._subscribers = [];
config.instrument && instrument('created', this);
if (noop !== resolver) {
typeof resolver !== 'function' && needsResolver();
this instanceof Promise ? initializePromise(this, resolver) : needsNew();
}
}
_onError(reason) {
config.after(() => {
if (this._onError) {
config.trigger('error', reason, this._label);
}
});
}
/**
`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
as the catch block of a try/catch statement.
```js
function findAuthor(){
throw new Error('couldn\'t find that author');
}
// synchronous
try {
findAuthor();
} catch(reason) {
// something went wrong
}
// async with promises
findAuthor().catch(function(reason){
// something went wrong
});
```
@method catch
@param {Function} onRejection
@param {String} [label] optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
catch(onRejection, label) {
return this.then(undefined, onRejection, label);
}
/**
`finally` will be invoked regardless of the promise's fate just as native
try/catch/finally behaves
Synchronous example:
```js
findAuthor() {
if (Math.random() > 0.5) {
throw new Error();
}
return new Author();
}
try {
return findAuthor(); // succeed or fail
} catch(error) {
return findOtherAuthor();
} finally {
// always runs
// doesn't affect the return value
}
```
Asynchronous example:
```js
findAuthor().catch(function(reason){
return findOtherAuthor();
}).finally(function(){
// author was either found, or not
});
```
@method finally
@param {Function} callback
@param {String} [label] optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
finally(callback, label) {
var promise = this;
var constructor = promise.constructor;
if (typeof callback === 'function') {
return promise.then(value => constructor.resolve(callback()).then(() => value), reason => constructor.resolve(callback()).then(() => {
throw reason;
}));
}
return promise.then(callback, callback);
}
}
_exports.Promise = Promise;
Promise.cast = resolve$$1; // deprecated
Promise.all = all;
Promise.race = race;
Promise.resolve = resolve$$1;
Promise.reject = reject$1;
Promise.prototype._guidKey = guidKey;
/**
The primary way of interacting with a promise is through its `then` method,
which registers callbacks to receive either a promise's eventual value or the
reason why the promise cannot be fulfilled.
```js
findUser().then(function(user){
// user is available
}, function(reason){
// user is unavailable, and you are given the reason why
});
```
Chaining
--------
The return value of `then` is itself a promise. This second, 'downstream'
promise is resolved with the return value of the first promise's fulfillment
or rejection handler, or rejected if the handler throws an exception.
```js
findUser().then(function (user) {
return user.name;
}, function (reason) {
return 'default name';
}).then(function (userName) {
// If `findUser` fulfilled, `userName` will be the user's name, otherwise it
// will be `'default name'`
});
findUser().then(function (user) {
throw new Error('Found user, but still unhappy');
}, function (reason) {
throw new Error('`findUser` rejected and we\'re unhappy');
}).then(function (value) {
// never reached
}, function (reason) {
// if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
// If `findUser` rejected, `reason` will be '`findUser` rejected and we\'re unhappy'.
});
```
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
```js
findUser().then(function (user) {
throw new PedagogicalException('Upstream error');
}).then(function (value) {
// never reached
}).then(function (value) {
// never reached
}, function (reason) {
// The `PedgagocialException` is propagated all the way down to here
});
```
Assimilation
------------
Sometimes the value you want to propagate to a downstream promise can only be
retrieved asynchronously. This can be achieved by returning a promise in the
fulfillment or rejection handler. The downstream promise will then be pending
until the returned promise is settled. This is called *assimilation*.
```js
findUser().then(function (user) {
return findCommentsByAuthor(user);
}).then(function (comments) {
// The user's comments are now available
});
```
If the assimliated promise rejects, then the downstream promise will also reject.
```js
findUser().then(function (user) {
return findCommentsByAuthor(user);
}).then(function (comments) {
// If `findCommentsByAuthor` fulfills, we'll have the value here
}, function (reason) {
// If `findCommentsByAuthor` rejects, we'll have the reason here
});
```
Simple Example
--------------
Synchronous Example
```javascript
let result;
try {
result = findResult();
// success
} catch(reason) {
// failure
}
```
Errback Example
```js
findResult(function(result, err){
if (err) {
// failure
} else {
// success
}
});
```
Promise Example;
```javascript
findResult().then(function(result){
// success
}, function(reason){
// failure
});
```
Advanced Example
--------------
Synchronous Example
```javascript
let author, books;
try {
author = findAuthor();
books = findBooksByAuthor(author);
// success
} catch(reason) {
// failure
}
```
Errback Example
```js
function foundBooks(books) {
}
function failure(reason) {
}
findAuthor(function(author, err){
if (err) {
failure(err);
// failure
} else {
try {
findBoooksByAuthor(author, function(books, err) {
if (err) {
failure(err);
} else {
try {
foundBooks(books);
} catch(reason) {
failure(reason);
}
}
});
} catch(error) {
failure(err);
}
// success
}
});
```
Promise Example;
```javascript
findAuthor().
then(findBooksByAuthor).
then(function(books){
// found books
}).catch(function(reason){
// something went wrong
});
```
@method then
@param {Function} onFulfillment
@param {Function} onRejection
@param {String} [label] optional string for labeling the promise.
Useful for tooling.
@return {Promise}
*/
Promise.prototype.then = then;
function makeObject(_, argumentNames) {
var obj = {};
var length = _.length;
var args = new Array(length);
for (var x = 0; x < length; x++) {
args[x] = _[x];
}
for (var i = 0; i < argumentNames.length; i++) {
var name = argumentNames[i];
obj[name] = args[i + 1];
}
return obj;
}
function arrayResult(_) {
var length = _.length;
var args = new Array(length - 1);
for (var i = 1; i < length; i++) {
args[i - 1] = _[i];
}
return args;
}
function wrapThenable(then, promise) {
return {
then(onFulFillment, onRejection) {
return then.call(promise, onFulFillment, onRejection);
}
};
}
/**
`denodeify` takes a 'node-style' function and returns a function that
will return an `Promise`. You can use `denodeify` in Node.js or the
browser when you'd prefer to use promises over using callbacks. For example,
`denodeify` transforms the following:
```javascript
let fs = require('fs');
fs.readFile('myfile.txt', function(err, data){
if (err) return handleError(err);
handleData(data);
});
```
into:
```javascript
let fs = require('fs');
let readFile = denodeify(fs.readFile);
readFile('myfile.txt').then(handleData, handleError);
```
If the node function has multiple success parameters, then `denodeify`
just returns the first one:
```javascript
let request = denodeify(require('request'));
request('http://example.com').then(function(res) {
// ...
});
```
However, if you need all success parameters, setting `denodeify`'s
second parameter to `true` causes it to return all success parameters
as an array:
```javascript
let request = denodeify(require('request'), true);
request('http://example.com').then(function(result) {
// result[0] -> res
// result[1] -> body
});
```
Or if you pass it an array with names it returns the parameters as a hash:
```javascript
let request = denodeify(require('request'), ['res', 'body']);
request('http://example.com').then(function(result) {
// result.res
// result.body
});
```
Sometimes you need to retain the `this`:
```javascript
let app = require('express')();
let render = denodeify(app.render.bind(app));
```
The denodified function inherits from the original function. It works in all
environments, except IE 10 and below. Consequently all properties of the original
function are available to you. However, any properties you change on the
denodeified function won't be changed on the original function. Example:
```javascript
let request = denodeify(require('request')),
cookieJar = request.jar(); // <- Inheritance is used here
request('http://example.com', {jar: cookieJar}).then(function(res) {
// cookieJar.cookies holds now the cookies returned by example.com
});
```
Using `denodeify` makes it easier to compose asynchronous operations instead
of using callbacks. For example, instead of:
```javascript
let fs = require('fs');
fs.readFile('myfile.txt', function(err, data){
if (err) { ... } // Handle error
fs.writeFile('myfile2.txt', data, function(err){
if (err) { ... } // Handle error
console.log('done')
});
});
```
you can chain the operations together using `then` from the returned promise:
```javascript
let fs = require('fs');
let readFile = denodeify(fs.readFile);
let writeFile = denodeify(fs.writeFile);
readFile('myfile.txt').then(function(data){
return writeFile('myfile2.txt', data);
}).then(function(){
console.log('done')
}).catch(function(error){
// Handle error
});
```
@method denodeify
@public
@static
@for rsvp
@param {Function} nodeFunc a 'node-style' function that takes a callback as
its last argument. The callback expects an error to be passed as its first
argument (if an error occurred, otherwise null), and the value from the
operation as its second argument ('function(err, value){ }').
@param {Boolean|Array} [options] An optional paramter that if set
to `true` causes the promise to fulfill with the callback's success arguments
as an array. This is useful if the node function has multiple success
paramters. If you set this paramter to an array with names, the promise will
fulfill with a hash with these names as keys and the success parameters as
values.
@return {Function} a function that wraps `nodeFunc` to return a `Promise`
*/
function denodeify(nodeFunc, options) {
var fn = function () {
var l = arguments.length;
var args = new Array(l + 1);
var promiseInput = false;
for (var i = 0; i < l; ++i) {
var arg = arguments[i]; // TODO: this code really needs to be cleaned up
if (!promiseInput) {
if (arg !== null && typeof arg === 'object') {
if (arg.constructor === Promise) {
promiseInput = true;
} else {
try {
promiseInput = arg.then;
} catch (error) {
var p = new Promise(noop);
reject(p, error);
return p;
}
}
} else {
promiseInput = false;
}
if (promiseInput && promiseInput !== true) {
arg = wrapThenable(promiseInput, arg);
}
}
args[i] = arg;
}
var promise = new Promise(noop);
args[l] = function (err, val) {
if (err) {
reject(promise, err);
} else if (options === undefined) {
resolve$1(promise, val);
} else if (options === true) {
resolve$1(promise, arrayResult(arguments));
} else if (Array.isArray(options)) {
resolve$1(promise, makeObject(arguments, options));
} else {
resolve$1(promise, val);
}
};
if (promiseInput) {
return handlePromiseInput(promise, args, nodeFunc, this);
} else {
return handleValueInput(promise, args, nodeFunc, this);
}
};
fn.__proto__ = nodeFunc;
return fn;
}
function handleValueInput(promise, args, nodeFunc, self) {
try {
nodeFunc.apply(self, args);
} catch (error) {
reject(promise, error);
}
return promise;
}
function handlePromiseInput(promise, args, nodeFunc, self) {
return Promise.all(args).then(args => handleValueInput(promise, args, nodeFunc, self));
}
/**
This is a convenient alias for `Promise.all`.
@method all
@public
@static
@for rsvp
@param {Array} array Array of promises.
@param {String} [label] An optional label. This is useful
for tooling.
*/
function all$1(array, label) {
return Promise.all(array, label);
}
/**
@module rsvp
@public
**/
class AllSettled extends Enumerator {
constructor(Constructor, entries, label) {
super(Constructor, entries, false
/* don't abort on reject */
, label);
}
}
AllSettled.prototype._setResultAt = setSettledResult;
/**
`RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
a fail-fast method, it waits until all the promises have returned and
shows you all the results. This is useful if you want to handle multiple
promises' failure states together as a set.
Returns a promise that is fulfilled when all the given promises have been
settled. The return promise is fulfilled with an array of the states of
the promises passed into the `promises` array argument.
Each state object will either indicate fulfillment or rejection, and
provide the corresponding value or reason. The states will take one of
the following formats:
```javascript
{ state: 'fulfilled', value: value }
or
{ state: 'rejected', reason: reason }
```
Example:
```javascript
let promise1 = RSVP.Promise.resolve(1);
let promise2 = RSVP.Promise.reject(new Error('2'));
let promise3 = RSVP.Promise.reject(new Error('3'));
let promises = [ promise1, promise2, promise3 ];
RSVP.allSettled(promises).then(function(array){
// array == [
// { state: 'fulfilled', value: 1 },
// { state: 'rejected', reason: Error },
// { state: 'rejected', reason: Error }
// ]
// Note that for the second item, reason.message will be '2', and for the
// third item, reason.message will be '3'.
}, function(error) {
// Not run. (This block would only be called if allSettled had failed,
// for instance if passed an incorrect argument type.)
});
```
@method allSettled
@public
@static
@for rsvp
@param {Array} entries
@param {String} [label] - optional string that describes the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled with an array of the settled
states of the constituent promises.
*/
function allSettled(entries, label) {
if (!Array.isArray(entries)) {
return Promise.reject(new TypeError("Promise.allSettled must be called with an array"), label);
}
return new AllSettled(Promise, entries, label).promise;
}
/**
This is a convenient alias for `Promise.race`.
@method race
@public
@static
@for rsvp
@param {Array} array Array of promises.
@param {String} [label] An optional label. This is useful
for tooling.
*/
function race$1(array, label) {
return Promise.race(array, label);
}
class PromiseHash extends Enumerator {
constructor(Constructor, object, abortOnReject = true, label) {
super(Constructor, object, abortOnReject, label);
}
_init(Constructor, object) {
this._result = {};
this._enumerate(object);
}
_enumerate(input) {
var keys = Object.keys(input);
var length = keys.length;
var promise = this.promise;
this._remaining = length;
var key, val;
for (var i = 0; promise._state === PENDING && i < length; i++) {
key = keys[i];
val = input[key];
this._eachEntry(val, key, true);
}
this._checkFullfillment();
}
}
/**
`hash` is similar to `all`, but takes an object instead of an array
for its `promises` argument.
Returns a promise that is fulfilled when all the given promises have been
fulfilled, or rejected if any of them become rejected. The returned promise
is fulfilled with a hash that has the same key names as the `promises` object
argument. If any of the values in the object are not promises, they will
simply be copied over to the fulfilled object.
Example:
```javascript
let promises = {
myPromise: resolve(1),
yourPromise: resolve(2),
theirPromise: resolve(3),
notAPromise: 4
};
hash(promises).then(function(hash){
// hash here is an object that looks like:
// {
// myPromise: 1,
// yourPromise: 2,
// theirPromise: 3,
// notAPromise: 4
// }
});
```
If any of the `promises` given to `hash` are rejected, the first promise
that is rejected will be given as the reason to the rejection handler.
Example:
```javascript
let promises = {
myPromise: resolve(1),
rejectedPromise: reject(new Error('rejectedPromise')),
anotherRejectedPromise: reject(new Error('anotherRejectedPromise')),
};
hash(promises).then(function(hash){
// Code here never runs because there are rejected promises!
}, function(reason) {
// reason.message === 'rejectedPromise'
});
```
An important note: `hash` is intended for plain JavaScript objects that
are just a set of keys and values. `hash` will NOT preserve prototype
chains.
Example:
```javascript
import { hash, resolve } from 'rsvp';
function MyConstructor(){
this.example = resolve('Example');
}
MyConstructor.prototype = {
protoProperty: resolve('Proto Property')
};
let myObject = new MyConstructor();
hash(myObject).then(function(hash){
// protoProperty will not be present, instead you will just have an
// object that looks like:
// {
// example: 'Example'
// }
//
// hash.hasOwnProperty('protoProperty'); // false
// 'undefined' === typeof hash.protoProperty
});
```
@method hash
@public
@static
@for rsvp
@param {Object} object
@param {String} [label] optional string that describes the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled when all properties of `promises`
have been fulfilled, or rejected if any of them become rejected.
*/
function hash(object, label) {
return Promise.resolve(object, label).then(function (object) {
if (object === null || typeof object !== 'object') {
throw new TypeError("Promise.hash must be called with an object");
}
return new PromiseHash(Promise, object, label).promise;
});
}
class HashSettled extends PromiseHash {
constructor(Constructor, object, label) {
super(Constructor, object, false, label);
}
}
HashSettled.prototype._setResultAt = setSettledResult;
/**
`hashSettled` is similar to `allSettled`, but takes an object
instead of an array for its `promises` argument.
Unlike `all` or `hash`, which implement a fail-fast method,
but like `allSettled`, `hashSettled` waits until all the
constituent promises have returned and then shows you all the results
with their states and values/reasons. This is useful if you want to
handle multiple promises' failure states together as a set.
Returns a promise that is fulfilled when all the given promises have been
settled, or rejected if the passed parameters are invalid.
The returned promise is fulfilled with a hash that has the same key names as
the `promises` object argument. If any of the values in the object are not
promises, they will be copied over to the fulfilled object and marked with state
'fulfilled'.
Example:
```javascript
import { hashSettled, resolve } from 'rsvp';
let promises = {
myPromise: resolve(1),
yourPromise: resolve(2),
theirPromise: resolve(3),
notAPromise: 4
};
hashSettled(promises).then(function(hash){
// hash here is an object that looks like:
// {
// myPromise: { state: 'fulfilled', value: 1 },
// yourPromise: { state: 'fulfilled', value: 2 },
// theirPromise: { state: 'fulfilled', value: 3 },
// notAPromise: { state: 'fulfilled', value: 4 }
// }
});
```
If any of the `promises` given to `hash` are rejected, the state will
be set to 'rejected' and the reason for rejection provided.
Example:
```javascript
import { hashSettled, reject, resolve } from 'rsvp';
let promises = {
myPromise: resolve(1),
rejectedPromise: reject(new Error('rejection')),
anotherRejectedPromise: reject(new Error('more rejection')),
};
hashSettled(promises).then(function(hash){
// hash here is an object that looks like:
// {
// myPromise: { state: 'fulfilled', value: 1 },
// rejectedPromise: { state: 'rejected', reason: Error },
// anotherRejectedPromise: { state: 'rejected', reason: Error },
// }
// Note that for rejectedPromise, reason.message == 'rejection',
// and for anotherRejectedPromise, reason.message == 'more rejection'.
});
```
An important note: `hashSettled` is intended for plain JavaScript objects that
are just a set of keys and values. `hashSettled` will NOT preserve prototype
chains.
Example:
```javascript
import Promise, { hashSettled, resolve } from 'rsvp';
function MyConstructor(){
this.example = resolve('Example');
}
MyConstructor.prototype = {
protoProperty: Promise.resolve('Proto Property')
};
let myObject = new MyConstructor();
hashSettled(myObject).then(function(hash){
// protoProperty will not be present, instead you will just have an
// object that looks like:
// {
// example: { state: 'fulfilled', value: 'Example' }
// }
//
// hash.hasOwnProperty('protoProperty'); // false
// 'undefined' === typeof hash.protoProperty
});
```
@method hashSettled
@public
@for rsvp
@param {Object} object
@param {String} [label] optional string that describes the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled when when all properties of `promises`
have been settled.
@static
*/
function hashSettled(object, label) {
return Promise.resolve(object, label).then(function (object) {
if (object === null || typeof object !== 'object') {
throw new TypeError("hashSettled must be called with an object");
}
return new HashSettled(Promise, object, false, label).promise;
});
}
/**
`rethrow` will rethrow an error on the next turn of the JavaScript event
loop in order to aid debugging.
Promises A+ specifies that any exceptions that occur with a promise must be
caught by the promises implementation and bubbled to the last handler. For
this reason, it is recommended that you always specify a second rejection
handler function to `then`. However, `rethrow` will throw the exception
outside of the promise, so it bubbles up to your console if in the browser,
or domain/cause uncaught exception in Node. `rethrow` will also throw the
error again so the error can be handled by the promise per the spec.
```javascript
import { rethrow } from 'rsvp';
function throws(){
throw new Error('Whoops!');
}
let promise = new Promise(function(resolve, reject){
throws();
});
promise.catch(rethrow).then(function(){
// Code here doesn't run because the promise became rejected due to an
// error!
}, function (err){
// handle the error here
});
```
The 'Whoops' error will be thrown on the next turn of the event loop
and you can watch for it in your console. You can also handle it using a
rejection handler given to `.then` or `.catch` on the returned promise.
@method rethrow
@public
@static
@for rsvp
@param {Error} reason reason the promise became rejected.
@throws Error
@static
*/
function rethrow(reason) {
setTimeout(() => {
throw reason;
});
throw reason;
}
/**
`defer` returns an object similar to jQuery's `$.Deferred`.
`defer` should be used when porting over code reliant on `$.Deferred`'s
interface. New code should use the `Promise` constructor instead.
The object returned from `defer` is a plain object with three properties:
* promise - an `Promise`.
* reject - a function that causes the `promise` property on this object to
become rejected
* resolve - a function that causes the `promise` property on this object to
become fulfilled.
Example:
```javascript
let deferred = defer();
deferred.resolve("Success!");
deferred.promise.then(function(value){
// value here is "Success!"
});
```
@method defer
@public
@static
@for rsvp
@param {String} [label] optional string for labeling the promise.
Useful for tooling.
@return {Object}
*/
function defer(label) {
var deferred = {
resolve: undefined,
reject: undefined
};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
}, label);
return deferred;
}
class MapEnumerator extends Enumerator {
constructor(Constructor, entries, mapFn, label) {
super(Constructor, entries, true, label, mapFn);
}
_init(Constructor, input, bool, label, mapFn) {
var len = input.length || 0;
this.length = len;
this._remaining = len;
this._result = new Array(len);
this._mapFn = mapFn;
this._enumerate(input);
}
_setResultAt(state, i, value, firstPass) {
if (firstPass) {
try {
this._eachEntry(this._mapFn(value, i), i, false);
} catch (error) {
this._settledAt(REJECTED, i, error, false);
}
} else {
this._remaining--;
this._result[i] = value;
}
}
}
/**
`map` is similar to JavaScript's native `map` method. `mapFn` is eagerly called
meaning that as soon as any promise resolves its value will be passed to `mapFn`.
`map` returns a promise that will become fulfilled with the result of running
`mapFn` on the values the promises become fulfilled with.
For example:
```javascript
import { map, resolve } from 'rsvp';
let promise1 = resolve(1);
let promise2 = resolve(2);
let promise3 = resolve(3);
let promises = [ promise1, promise2, promise3 ];
let mapFn = function(item){
return item + 1;
};
map(promises, mapFn).then(function(result){
// result is [ 2, 3, 4 ]
});
```
If any of the `promises` given to `map` are rejected, the first promise
that is rejected will be given as an argument to the returned promise's
rejection handler. For example:
```javascript
import { map, reject, resolve } from 'rsvp';
let promise1 = resolve(1);
let promise2 = reject(new Error('2'));
let promise3 = reject(new Error('3'));
let promises = [ promise1, promise2, promise3 ];
let mapFn = function(item){
return item + 1;
};
map(promises, mapFn).then(function(array){
// Code here never runs because there are rejected promises!
}, function(reason) {
// reason.message === '2'
});
```
`map` will also wait if a promise is returned from `mapFn`. For example,
say you want to get all comments from a set of blog posts, but you need
the blog posts first because they contain a url to those comments.
```javscript
import { map } from 'rsvp';
let mapFn = function(blogPost){
// getComments does some ajax and returns an Promise that is fulfilled
// with some comments data
return getComments(blogPost.comments_url);
};
// getBlogPosts does some ajax and returns an Promise that is fulfilled
// with some blog post data
map(getBlogPosts(), mapFn).then(function(comments){
// comments is the result of asking the server for the comments
// of all blog posts returned from getBlogPosts()
});
```
@method map
@public
@static
@for rsvp
@param {Array} promises
@param {Function} mapFn function to be called on each fulfilled promise.
@param {String} [label] optional string for labeling the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled with the result of calling
`mapFn` on each fulfilled promise or value when they become fulfilled.
The promise will be rejected if any of the given `promises` become rejected.
*/
function map(promises, mapFn, label) {
if (typeof mapFn !== 'function') {
return Promise.reject(new TypeError("map expects a function as a second argument"), label);
}
return Promise.resolve(promises, label).then(function (promises) {
if (!Array.isArray(promises)) {
throw new TypeError("map must be called with an array");
}
return new MapEnumerator(Promise, promises, mapFn, label).promise;
});
}
/**
This is a convenient alias for `Promise.resolve`.
@method resolve
@public
@static
@for rsvp
@param {*} value value that the returned promise will be resolved with
@param {String} [label] optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise that will become fulfilled with the given
`value`
*/
function resolve$2(value, label) {
return Promise.resolve(value, label);
}
/**
This is a convenient alias for `Promise.reject`.
@method reject
@public
@static
@for rsvp
@param {*} reason value that the returned promise will be rejected with.
@param {String} [label] optional string for identifying the returned promise.
Useful for tooling.
@return {Promise} a promise rejected with the given `reason`.
*/
function reject$2(reason, label) {
return Promise.reject(reason, label);
}
var EMPTY_OBJECT = {};
class FilterEnumerator extends MapEnumerator {
_checkFullfillment() {
if (this._remaining === 0 && this._result !== null) {
var result = this._result.filter(val => val !== EMPTY_OBJECT);
fulfill(this.promise, result);
this._result = null;
}
}
_setResultAt(state, i, value, firstPass) {
if (firstPass) {
this._result[i] = value;
var val,
succeeded = true;
try {
val = this._mapFn(value, i);
} catch (error) {
succeeded = false;
this._settledAt(REJECTED, i, error, false);
}
if (succeeded) {
this._eachEntry(val, i, false);
}
} else {
this._remaining--;
if (!value) {
this._result[i] = EMPTY_OBJECT;
}
}
}
}
/**
`filter` is similar to JavaScript's native `filter` method.
`filterFn` is eagerly called meaning that as soon as any promise
resolves its value will be passed to `filterFn`. `filter` returns
a promise that will become fulfilled with the result of running
`filterFn` on the values the promises become fulfilled with.
For example:
```javascript
import { filter, resolve } from 'rsvp';
let promise1 = resolve(1);
let promise2 = resolve(2);
let promise3 = resolve(3);
let promises = [promise1, promise2, promise3];
let filterFn = function(item){
return item > 1;
};
filter(promises, filterFn).then(function(result){
// result is [ 2, 3 ]
});
```
If any of the `promises` given to `filter` are rejected, the first promise
that is rejected will be given as an argument to the returned promise's
rejection handler. For example:
```javascript
import { filter, reject, resolve } from 'rsvp';
let promise1 = resolve(1);
let promise2 = reject(new Error('2'));
let promise3 = reject(new Error('3'));
let promises = [ promise1, promise2, promise3 ];
let filterFn = function(item){
return item > 1;
};
filter(promises, filterFn).then(function(array){
// Code here never runs because there are rejected promises!
}, function(reason) {
// reason.message === '2'
});
```
`filter` will also wait for any promises returned from `filterFn`.
For instance, you may want to fetch a list of users then return a subset
of those users based on some asynchronous operation:
```javascript
import { filter, resolve } from 'rsvp';
let alice = { name: 'alice' };
let bob = { name: 'bob' };
let users = [ alice, bob ];
let promises = users.map(function(user){
return resolve(user);
});
let filterFn = function(user){
// Here, Alice has permissions to create a blog post, but Bob does not.
return getPrivilegesForUser(user).then(function(privs){
return privs.can_create_blog_post === true;
});
};
filter(promises, filterFn).then(function(users){
// true, because the server told us only Alice can create a blog post.
users.length === 1;
// false, because Alice is the only user present in `users`
users[0] === bob;
});
```
@method filter
@public
@static
@for rsvp
@param {Array} promises
@param {Function} filterFn - function to be called on each resolved value to
filter the final results.
@param {String} [label] optional string describing the promise. Useful for
tooling.
@return {Promise}
*/
function filter(promises, filterFn, label) {
if (typeof filterFn !== 'function') {
return Promise.reject(new TypeError("filter expects function as a second argument"), label);
}
return Promise.resolve(promises, label).then(function (promises) {
if (!Array.isArray(promises)) {
throw new TypeError("filter must be called with an array");
}
return new FilterEnumerator(Promise, promises, filterFn, label).promise;
});
}
var len = 0;
var vertxNext;
function asap(callback, arg) {
queue$1[len] = callback;
queue$1[len + 1] = arg;
len += 2;
if (len === 2) {
// If len is 1, that means that we need to schedule an async flush.
// If additional callbacks are queued before the queue is flushed, they
// will be processed by this flush that we are scheduling.
scheduleFlush$1();
}
}
var browserWindow = typeof window !== 'undefined' ? window : undefined;
var browserGlobal = browserWindow || {};
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; // test for web worker but not in IE10
var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; // node
function useNextTick() {
var nextTick = process.nextTick; // node version 0.10.x displays a deprecation warning when nextTick is used recursively
// setImmediate should be used instead instead
var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
nextTick = setImmediate;
}
return () => nextTick(flush);
} // vertx
function useVertxTimer() {
if (typeof vertxNext !== 'undefined') {
return function () {
vertxNext(flush);
};
}
return useSetTimeout();
}
function useMutationObserver() {
var iterations = 0;
var observer = new BrowserMutationObserver(flush);
var node = document.createTextNode('');
observer.observe(node, {
characterData: true
});
return () => node.data = iterations = ++iterations % 2;
} // web worker
function useMessageChannel() {
var channel = new MessageChannel();
channel.port1.onmessage = flush;
return () => channel.port2.postMessage(0);
}
function useSetTimeout() {
return () => setTimeout(flush, 1);
}
var queue$1 = new Array(1000);
function flush() {
for (var i = 0; i < len; i += 2) {
var callback = queue$1[i];
var arg = queue$1[i + 1];
callback(arg);
queue$1[i] = undefined;
queue$1[i + 1] = undefined;
}
len = 0;
}
function attemptVertex() {
try {
var vertx = Function('return this')().require('vertx');
vertxNext = vertx.runOnLoop || vertx.runOnContext;
return useVertxTimer();
} catch (e) {
return useSetTimeout();
}
}
var scheduleFlush$1; // Decide what async method to use to triggering processing of queued callbacks:
if (isNode) {
scheduleFlush$1 = useNextTick();
} else if (BrowserMutationObserver) {
scheduleFlush$1 = useMutationObserver();
} else if (isWorker) {
scheduleFlush$1 = useMessageChannel();
} else if (browserWindow === undefined && typeof require === 'function') {
scheduleFlush$1 = attemptVertex();
} else {
scheduleFlush$1 = useSetTimeout();
} // defaults
config.async = asap;
config.after = cb => setTimeout(cb, 0);
var cast = resolve$2;
_exports.cast = cast;
var async = (callback, arg) => config.async(callback, arg);
_exports.async = async;
function on() {
config.on(...arguments);
}
function off() {
config.off(...arguments);
} // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
if (typeof window !== 'undefined' && typeof window['__PROMISE_INSTRUMENTATION__'] === 'object') {
var callbacks = window['__PROMISE_INSTRUMENTATION__'];
configure('instrument', true);
for (var eventName in callbacks) {
if (callbacks.hasOwnProperty(eventName)) {
on(eventName, callbacks[eventName]);
}
}
} // the default export here is for backwards compat:
// https://github.com/tildeio/rsvp.js/issues/434
var rsvp = {
asap,
cast,
Promise,
EventTarget,
all: all$1,
allSettled,
race: race$1,
hash,
hashSettled,
rethrow,
defer,
denodeify,
configure,
on,
off,
resolve: resolve$2,
reject: reject$2,
map,
async,
filter
};
var _default = rsvp;
_exports.default = _default;
});
require('ember');
}());
//# sourceMappingURL=ember.map
define("@ember/ordered-set", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
;
const NEEDS_CUSTOM_ORDERED_SET = true;
let OrderedSet;
if (NEEDS_CUSTOM_ORDERED_SET) {
/**
@class OrderedSet
@constructor
*/
OrderedSet = class OrderedSet {
constructor() {
this.clear();
}
/**
@method create
@static
@return {OrderedSet}
*/
static create() {
let Constructor = this;
return new Constructor();
}
/**
@method clear
*/
clear() {
this.presenceSet = Object.create(null);
this.list = [];
this.size = 0;
}
/**
@method add
@param {*} obj
@param {string} [_guid] (for internal use)
@return {OrderedSet}
*/
add(obj, _guid) {
let guid = _guid || Ember.guidFor(obj);
let presenceSet = this.presenceSet;
let list = this.list;
if (presenceSet[guid] !== true) {
presenceSet[guid] = true;
this.size = list.push(obj);
}
return this;
}
/**
@method delete
@param {*} obj
@param {string} [_guid] (for internal use)
@return {Boolean}
*/
delete(obj, _guid) {
let guid = _guid || Ember.guidFor(obj);
let presenceSet = this.presenceSet;
let list = this.list;
if (presenceSet[guid] === true) {
delete presenceSet[guid];
let index = list.indexOf(obj);
if (index > -1) {
list.splice(index, 1);
}
this.size = list.length;
return true;
} else {
return false;
}
}
/**
@method isEmpty
@return {Boolean}
*/
isEmpty() {
return this.size === 0;
}
/**
@method has
@param {*} obj
@return {Boolean}
*/
has(obj) {
if (this.size === 0) {
return false;
}
let guid = Ember.guidFor(obj);
let presenceSet = this.presenceSet;
return presenceSet[guid] === true;
}
/**
@method forEach
@param {Function} fn
@param self
*/
forEach(fn
/*, ...thisArg*/
) {
(true && Ember.assert("".concat(Object.prototype.toString.call(fn), " is not a function"), typeof fn === 'function'));
if (this.size === 0) {
return;
}
let list = this.list;
if (arguments.length === 2) {
for (let i = 0; i < list.length; i++) {
fn.call(arguments[1], list[i]);
}
} else {
for (let i = 0; i < list.length; i++) {
fn(list[i]);
}
}
}
/**
@method toArray
@return {Array}
*/
toArray() {
return this.list.slice();
}
/**
@method copy
@return {OrderedSet}
*/
copy() {
let Constructor = this.constructor;
let set = new Constructor();
set.presenceSet = Object.create(null);
for (let prop in this.presenceSet) {
// hasOwnPropery is not needed because obj is Object.create(null);
set.presenceSet[prop] = this.presenceSet[prop];
}
set.list = this.toArray();
set.size = this.size;
return set;
}
};
} else {
OrderedSet = Ember.__OrderedSet__ || Ember.OrderedSet;
}
var _default = OrderedSet;
_exports.default = _default;
});
define("ember-inflector/index", ["exports", "ember-inflector/lib/system", "ember-inflector/lib/ext/string"], function (_exports, _system, _string) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "defaultRules", {
enumerable: true,
get: function () {
return _system.defaultRules;
}
});
Object.defineProperty(_exports, "pluralize", {
enumerable: true,
get: function () {
return _system.pluralize;
}
});
Object.defineProperty(_exports, "singularize", {
enumerable: true,
get: function () {
return _system.singularize;
}
});
_exports.default = void 0;
_system.Inflector.defaultRules = _system.defaultRules;
Object.defineProperty(Ember, 'Inflector', {
get() {
Ember.deprecate("Ember.Inflector is deprecated. Please explicitly: import Inflector from 'ember-inflector';", false, {
id: 'ember-inflector.globals',
until: '3.0.0'
});
return _system.Inflector;
}
}, {
configurable: true
});
Object.defineProperty(Ember.String, 'singularize', {
get() {
Ember.deprecate("Ember.String.singularize() is deprecated. Please explicitly: import { singularize } from 'ember-inflector';", false, {
id: 'ember-inflector.globals',
until: '3.0.0'
});
return _system.singularize;
}
}, {
configurable: true
});
Object.defineProperty(Ember.String, 'pluralize', {
get() {
Ember.deprecate("Ember.String.pluralize() is deprecated. Please explicitly: import { pluralize } from 'ember-inflector';", false, {
id: 'ember-inflector.globals',
until: '3.0.0'
});
return _system.pluralize;
}
}, {
configurable: true
});
var _default = _system.Inflector;
_exports.default = _default;
});
define("ember-inflector/lib/system", ["exports", "ember-inflector/lib/system/inflector", "ember-inflector/lib/system/string", "ember-inflector/lib/system/inflections"], function (_exports, _inflector, _string, _inflections) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "Inflector", {
enumerable: true,
get: function () {
return _inflector.default;
}
});
Object.defineProperty(_exports, "pluralize", {
enumerable: true,
get: function () {
return _string.pluralize;
}
});
Object.defineProperty(_exports, "singularize", {
enumerable: true,
get: function () {
return _string.singularize;
}
});
Object.defineProperty(_exports, "defaultRules", {
enumerable: true,
get: function () {
return _inflections.default;
}
});
_inflector.default.inflector = new _inflector.default(_inflections.default);
});
define("ember-inflector/lib/ext/string", ["ember-inflector/lib/system/string"], function (_string) {
"use strict";
if (Ember.ENV.EXTEND_PROTOTYPES === true || Ember.ENV.EXTEND_PROTOTYPES.String) {
/**
See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}}
@method pluralize
@for String
*/
Object.defineProperty(String.prototype, 'pluralize', {
get() {
Ember.deprecate("String.prototype.pluralize() is deprecated. Please explicitly: import { pluralize } from 'ember-inflector';", false, {
id: 'ember-inflector.globals',
until: '3.0.0'
});
return function () {
return (0, _string.pluralize)(this);
};
}
}, {
configurable: true
});
/**
See {{#crossLink "Ember.String/singularize"}}{{/crossLink}}
@method singularize
@for String
*/
Object.defineProperty(String.prototype, 'singularize', {
get() {
Ember.deprecate("String.prototype.singularize() is deprecated. Please explicitly: import { singularize } from 'ember-inflector';", false, {
id: 'ember-inflector.globals',
until: '3.0.0'
});
return function () {
return (0, _string.singularize)(this);
};
}
}, {
configurable: true
});
}
});
define("ember-inflector/lib/helpers/pluralize", ["exports", "ember-inflector", "ember-inflector/lib/utils/make-helper"], function (_exports, _emberInflector, _makeHelper) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
*
* If you have Ember Inflector (such as if Ember Data is present),
* pluralize a word. For example, turn "ox" into "oxen".
*
* Example:
*
* {{pluralize count myProperty}}
* {{pluralize 1 "oxen"}}
* {{pluralize myProperty}}
* {{pluralize "ox"}}
*
* @for Ember.HTMLBars.helpers
* @method pluralize
* @param {Number|Property} [count] count of objects
* @param {String|Property} word word to pluralize
*/
var _default = (0, _makeHelper.default)(function (params, hash) {
let fullParams = new Array(...params);
if (fullParams.length === 2) {
fullParams.push({
withoutCount: hash["without-count"]
});
}
return (0, _emberInflector.pluralize)(...fullParams);
});
_exports.default = _default;
});
define("ember-inflector/lib/helpers/singularize", ["exports", "ember-inflector", "ember-inflector/lib/utils/make-helper"], function (_exports, _emberInflector, _makeHelper) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
*
* If you have Ember Inflector (such as if Ember Data is present),
* singularize a word. For example, turn "oxen" into "ox".
*
* Example:
*
* {{singularize myProperty}}
* {{singularize "oxen"}}
*
* @for Ember.HTMLBars.helpers
* @method singularize
* @param {String|Property} word word to singularize
*/
var _default = (0, _makeHelper.default)(function (params) {
return (0, _emberInflector.singularize)(params[0]);
});
_exports.default = _default;
});
define("ember-inflector/lib/system/inflections", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
var _default = {
plurals: [[/$/, 's'], [/s$/i, 's'], [/^(ax|test)is$/i, '$1es'], [/(octop|vir)us$/i, '$1i'], [/(octop|vir)i$/i, '$1i'], [/(alias|status|bonus)$/i, '$1es'], [/(bu)s$/i, '$1ses'], [/(buffal|tomat)o$/i, '$1oes'], [/([ti])um$/i, '$1a'], [/([ti])a$/i, '$1a'], [/sis$/i, 'ses'], [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'], [/(hive)$/i, '$1s'], [/([^aeiouy]|qu)y$/i, '$1ies'], [/(x|ch|ss|sh)$/i, '$1es'], [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'], [/^(m|l)ouse$/i, '$1ice'], [/^(m|l)ice$/i, '$1ice'], [/^(ox)$/i, '$1en'], [/^(oxen)$/i, '$1'], [/(quiz)$/i, '$1zes']],
singular: [[/s$/i, ''], [/(ss)$/i, '$1'], [/(n)ews$/i, '$1ews'], [/([ti])a$/i, '$1um'], [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'], [/(^analy)(sis|ses)$/i, '$1sis'], [/([^f])ves$/i, '$1fe'], [/(hive)s$/i, '$1'], [/(tive)s$/i, '$1'], [/([lr])ves$/i, '$1f'], [/([^aeiouy]|qu)ies$/i, '$1y'], [/(s)eries$/i, '$1eries'], [/(m)ovies$/i, '$1ovie'], [/(x|ch|ss|sh)es$/i, '$1'], [/^(m|l)ice$/i, '$1ouse'], [/(bus)(es)?$/i, '$1'], [/(o)es$/i, '$1'], [/(shoe)s$/i, '$1'], [/(cris|test)(is|es)$/i, '$1is'], [/^(a)x[ie]s$/i, '$1xis'], [/(octop|vir)(us|i)$/i, '$1us'], [/(alias|status|bonus)(es)?$/i, '$1'], [/^(ox)en/i, '$1'], [/(vert|ind)ices$/i, '$1ex'], [/(matr)ices$/i, '$1ix'], [/(quiz)zes$/i, '$1'], [/(database)s$/i, '$1']],
irregularPairs: [['person', 'people'], ['man', 'men'], ['child', 'children'], ['sex', 'sexes'], ['move', 'moves'], ['cow', 'kine'], ['zombie', 'zombies']],
uncountable: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep', 'jeans', 'police']
};
_exports.default = _default;
});
define("ember-inflector/lib/system/inflector", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
const BLANK_REGEX = /^\s*$/;
const LAST_WORD_DASHED_REGEX = /([\w/-]+[_/\s-])([a-z\d]+$)/;
const LAST_WORD_CAMELIZED_REGEX = /([\w/\s-]+)([A-Z][a-z\d]*$)/;
const CAMELIZED_REGEX = /[A-Z][a-z\d]*$/;
function loadUncountable(rules, uncountable) {
for (let i = 0, length = uncountable.length; i < length; i++) {
rules.uncountable[uncountable[i].toLowerCase()] = true;
}
}
function loadIrregular(rules, irregularPairs) {
let pair;
for (let i = 0, length = irregularPairs.length; i < length; i++) {
pair = irregularPairs[i]; //pluralizing
rules.irregular[pair[0].toLowerCase()] = pair[1];
rules.irregular[pair[1].toLowerCase()] = pair[1]; //singularizing
rules.irregularInverse[pair[1].toLowerCase()] = pair[0];
rules.irregularInverse[pair[0].toLowerCase()] = pair[0];
}
}
/**
Inflector.Ember provides a mechanism for supplying inflection rules for your
application. Ember includes a default set of inflection rules, and provides an
API for providing additional rules.
Examples:
Creating an inflector with no rules.
```js
var inflector = new Ember.Inflector();
```
Creating an inflector with the default ember ruleset.
```js
var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
inflector.pluralize('cow'); //=> 'kine'
inflector.singularize('kine'); //=> 'cow'
```
Creating an inflector and adding rules later.
```javascript
var inflector = Ember.Inflector.inflector;
inflector.pluralize('advice'); // => 'advices'
inflector.uncountable('advice');
inflector.pluralize('advice'); // => 'advice'
inflector.pluralize('formula'); // => 'formulas'
inflector.irregular('formula', 'formulae');
inflector.pluralize('formula'); // => 'formulae'
// you would not need to add these as they are the default rules
inflector.plural(/$/, 's');
inflector.singular(/s$/i, '');
```
Creating an inflector with a nondefault ruleset.
```javascript
var rules = {
plurals: [
[ /$/, 's' ]
],
singular: [
[ /\s$/, '' ]
],
irregularPairs: [
[ 'cow', 'kine' ]
],
uncountable: [ 'fish' ]
};
var inflector = new Ember.Inflector(rules);
```
@class Inflector
@namespace Ember
*/
function Inflector(ruleSet) {
ruleSet = ruleSet || {};
ruleSet.uncountable = ruleSet.uncountable || makeDictionary();
ruleSet.irregularPairs = ruleSet.irregularPairs || makeDictionary();
const rules = this.rules = {
plurals: ruleSet.plurals || [],
singular: ruleSet.singular || [],
irregular: makeDictionary(),
irregularInverse: makeDictionary(),
uncountable: makeDictionary()
};
loadUncountable(rules, ruleSet.uncountable);
loadIrregular(rules, ruleSet.irregularPairs);
this.enableCache();
}
if (!Object.create && !Object.create(null).hasOwnProperty) {
throw new Error("This browser does not support Object.create(null), please polyfil with es5-sham: http://git.io/yBU2rg");
}
function makeDictionary() {
var cache = Object.create(null);
cache['_dict'] = null;
delete cache['_dict'];
return cache;
}
Inflector.prototype = {
/**
@public
As inflections can be costly, and commonly the same subset of words are repeatedly
inflected an optional cache is provided.
@method enableCache
*/
enableCache() {
this.purgeCache();
this.singularize = function (word) {
this._cacheUsed = true;
return this._sCache[word] || (this._sCache[word] = this._singularize(word));
};
this.pluralize = function (numberOrWord, word, options = {}) {
this._cacheUsed = true;
var cacheKey = [numberOrWord, word, options.withoutCount];
return this._pCache[cacheKey] || (this._pCache[cacheKey] = this._pluralize(numberOrWord, word, options));
};
},
/**
@public
@method purgedCache
*/
purgeCache() {
this._cacheUsed = false;
this._sCache = makeDictionary();
this._pCache = makeDictionary();
},
/**
@public
disable caching
@method disableCache;
*/
disableCache() {
this._sCache = null;
this._pCache = null;
this.singularize = function (word) {
return this._singularize(word);
};
this.pluralize = function () {
return this._pluralize(...arguments);
};
},
/**
@method plural
@param {RegExp} regex
@param {String} string
*/
plural(regex, string) {
if (this._cacheUsed) {
this.purgeCache();
}
this.rules.plurals.push([regex, string.toLowerCase()]);
},
/**
@method singular
@param {RegExp} regex
@param {String} string
*/
singular(regex, string) {
if (this._cacheUsed) {
this.purgeCache();
}
this.rules.singular.push([regex, string.toLowerCase()]);
},
/**
@method uncountable
@param {String} regex
*/
uncountable(string) {
if (this._cacheUsed) {
this.purgeCache();
}
loadUncountable(this.rules, [string.toLowerCase()]);
},
/**
@method irregular
@param {String} singular
@param {String} plural
*/
irregular(singular, plural) {
if (this._cacheUsed) {
this.purgeCache();
}
loadIrregular(this.rules, [[singular, plural]]);
},
/**
@method pluralize
@param {String} word
*/
pluralize() {
return this._pluralize(...arguments);
},
_pluralize(wordOrCount, word, options = {}) {
if (word === undefined) {
return this.inflect(wordOrCount, this.rules.plurals, this.rules.irregular);
}
if (parseFloat(wordOrCount) !== 1) {
word = this.inflect(word, this.rules.plurals, this.rules.irregular);
}
return options.withoutCount ? word : "".concat(wordOrCount, " ").concat(word);
},
/**
@method singularize
@param {String} word
*/
singularize(word) {
return this._singularize(word);
},
_singularize(word) {
return this.inflect(word, this.rules.singular, this.rules.irregularInverse);
},
/**
@protected
@method inflect
@param {String} word
@param {Object} typeRules
@param {Object} irregular
*/
inflect(word, typeRules, irregular) {
let inflection, substitution, result, lowercase, wordSplit, lastWord, isBlank, isCamelized, rule, isUncountable;
isBlank = !word || BLANK_REGEX.test(word);
isCamelized = CAMELIZED_REGEX.test(word);
if (isBlank) {
return word;
}
lowercase = word.toLowerCase();
wordSplit = LAST_WORD_DASHED_REGEX.exec(word) || LAST_WORD_CAMELIZED_REGEX.exec(word);
if (wordSplit) {
lastWord = wordSplit[2].toLowerCase();
}
isUncountable = this.rules.uncountable[lowercase] || this.rules.uncountable[lastWord];
if (isUncountable) {
return word;
}
for (rule in irregular) {
if (lowercase.match(rule + "$")) {
substitution = irregular[rule];
if (isCamelized && irregular[lastWord]) {
substitution = Ember.String.capitalize(substitution);
rule = Ember.String.capitalize(rule);
}
return word.replace(new RegExp(rule, 'i'), substitution);
}
}
for (var i = typeRules.length, min = 0; i > min; i--) {
inflection = typeRules[i - 1];
rule = inflection[0];
if (rule.test(word)) {
break;
}
}
inflection = inflection || [];
rule = inflection[0];
substitution = inflection[1];
result = word.replace(rule, substitution);
return result;
}
};
var _default = Inflector;
_exports.default = _default;
});
define("ember-inflector/lib/system/string", ["exports", "ember-inflector/lib/system/inflector"], function (_exports, _inflector) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.pluralize = pluralize;
_exports.singularize = singularize;
function pluralize() {
return _inflector.default.inflector.pluralize(...arguments);
}
function singularize(word) {
return _inflector.default.inflector.singularize(word);
}
});
define("ember-inflector/lib/utils/make-helper", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = makeHelper;
function makeHelper(helperFunction) {
if (Ember.Helper) {
return Ember.Helper.helper(helperFunction);
}
if (Ember.HTMLBars) {
return Ember.HTMLBars.makeBoundHelper(helperFunction);
}
return Ember.Handlebars.makeBoundHelper(helperFunction);
}
});
define("ember-data/adapter", ["exports", "@ember-data/adapter"], function (_exports, _adapter) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _adapter.default;
}
});
});
define("ember-data/attr", ["exports", "@ember-data/model"], function (_exports, _model) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _model.attr;
}
});
});
define("ember-data/index", ["exports", "@ember-data/store", "ember-data/-private", "ember-inflector", "ember-data/setup-container", "ember-data/initialize-store-service", "@ember-data/serializer/transform", "@ember-data/serializer/-private", "@ember-data/adapter", "@ember-data/adapter/json-api", "@ember-data/adapter/rest", "@ember-data/adapter/error", "@ember-data/serializer", "@ember-data/serializer/json-api", "@ember-data/serializer/json", "@ember-data/serializer/rest", "@ember-data/model"], function (_exports, _store, _private, _emberInflector, _setupContainer, _initializeStoreService, _transform, _private2, _adapter, _jsonApi, _rest, _error, _serializer, _jsonApi2, _json, _rest2, _model) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
Ember Data
@module ember-data
@main ember-data
*/
if (Ember.VERSION.match(/^1\.([0-9]|1[0-2])\./)) {
throw new Ember.Error('Ember Data requires at least Ember 1.13.0, but you have ' + Ember.VERSION + '. Please upgrade your version of Ember, then upgrade Ember Data.');
}
_private.DS.Store = _store.default;
_private.DS.PromiseArray = _private.PromiseArray;
_private.DS.PromiseObject = _private.PromiseObject;
_private.DS.PromiseManyArray = _private.PromiseManyArray;
_private.DS.Model = _model.default;
_private.DS.RootState = _private.RootState;
_private.DS.attr = _model.attr;
_private.DS.Errors = _private.Errors;
_private.DS.InternalModel = _private.InternalModel;
_private.DS.Snapshot = _private.Snapshot;
_private.DS.Adapter = _adapter.default;
_private.DS.AdapterError = _error.default;
_private.DS.InvalidError = _error.InvalidError;
_private.DS.TimeoutError = _error.TimeoutError;
_private.DS.AbortError = _error.AbortError;
_private.DS.UnauthorizedError = _error.UnauthorizedError;
_private.DS.ForbiddenError = _error.ForbiddenError;
_private.DS.NotFoundError = _error.NotFoundError;
_private.DS.ConflictError = _error.ConflictError;
_private.DS.ServerError = _error.ServerError;
_private.DS.errorsHashToArray = _error.errorsHashToArray;
_private.DS.errorsArrayToHash = _error.errorsArrayToHash;
_private.DS.Serializer = _serializer.default;
_private.DS.DebugAdapter = _private.DebugAdapter;
_private.DS.RecordArray = _private.RecordArray;
_private.DS.AdapterPopulatedRecordArray = _private.AdapterPopulatedRecordArray;
_private.DS.ManyArray = _private.ManyArray;
_private.DS.RecordArrayManager = _private.RecordArrayManager;
_private.DS.RESTAdapter = _rest.default;
_private.DS.BuildURLMixin = _adapter.BuildURLMixin;
_private.DS.RESTSerializer = _rest2.default;
_private.DS.JSONSerializer = _json.default;
_private.DS.JSONAPIAdapter = _jsonApi.default;
_private.DS.JSONAPISerializer = _jsonApi2.default;
_private.DS.Transform = _transform.default;
_private.DS.DateTransform = _private2.DateTransform;
_private.DS.StringTransform = _private2.StringTransform;
_private.DS.NumberTransform = _private2.NumberTransform;
_private.DS.BooleanTransform = _private2.BooleanTransform;
_private.DS.EmbeddedRecordsMixin = _rest2.EmbeddedRecordsMixin;
_private.DS.belongsTo = _model.belongsTo;
_private.DS.hasMany = _model.hasMany;
_private.DS.Relationship = _private.Relationship;
_private.DS._setupContainer = _setupContainer.default;
_private.DS._initializeStoreService = _initializeStoreService.default;
Object.defineProperty(_private.DS, 'normalizeModelName', {
enumerable: true,
writable: false,
configurable: false,
value: _store.normalizeModelName
});
var _default = _private.DS;
_exports.default = _default;
});
define("ember-data/initialize-store-service", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = initializeStoreService;
/*
Configures a registry for use with an Ember-Data
store.
@method initializeStoreService
@param {Ember.ApplicationInstance | Ember.EngineInstance} instance
*/
function initializeStoreService(instance) {
// instance.lookup supports Ember 2.1 and higher
// instance.container supports Ember 1.11 - 2.0
const container = instance.lookup ? instance : instance.container; // Eagerly generate the store so defaultStore is populated.
container.lookup('service:store');
}
});
define("ember-data/model", ["exports", "@ember-data/model"], function (_exports, _model) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _model.default;
}
});
});
define("ember-data/relationships", ["exports", "@ember-data/model"], function (_exports, _model) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "belongsTo", {
enumerable: true,
get: function () {
return _model.belongsTo;
}
});
Object.defineProperty(_exports, "hasMany", {
enumerable: true,
get: function () {
return _model.hasMany;
}
});
});
define("ember-data/serializer", ["exports", "@ember-data/serializer"], function (_exports, _serializer) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _serializer.default;
}
});
});
define("ember-data/setup-container", ["exports", "ember-data/-private", "@ember-data/store", "@ember-data/serializer/json-api", "@ember-data/serializer/json", "@ember-data/serializer/rest", "@ember-data/adapter/json-api", "@ember-data/adapter/rest", "@ember-data/serializer/-private"], function (_exports, _private, _store, _jsonApi, _json, _rest, _jsonApi2, _rest2, _private2) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = setupContainer;
function has(applicationOrRegistry, fullName) {
if (applicationOrRegistry.has) {
// < 2.1.0
return applicationOrRegistry.has(fullName);
} else {
// 2.1.0+
return applicationOrRegistry.hasRegistration(fullName);
}
}
/*
Configures a registry for use with an Ember-Data
store. Accepts an optional namespace argument.
@method initializeStore
@param {Ember.Registry} registry
*/
function initializeStore(registry) {
let registerOptionsForType = registry.registerOptionsForType || registry.optionsForType;
registerOptionsForType.call(registry, 'serializer', {
singleton: false
});
registerOptionsForType.call(registry, 'adapter', {
singleton: false
});
registry.register('serializer:-default', _json.default);
registry.register('serializer:-rest', _rest.default);
registry.register('adapter:-rest', _rest2.default);
registry.register('adapter:-json-api', _jsonApi2.default);
registry.register('serializer:-json-api', _jsonApi.default);
if (!has(registry, 'service:store')) {
registry.register('service:store', _store.default);
}
}
/*
Configures a registry with injections on Ember applications
for the Ember-Data store. Accepts an optional namespace argument.
@method initializeDebugAdapter
@param {Ember.Registry} registry
*/
function initializeDataAdapter(registry) {
registry.register('data-adapter:main', _private.DebugAdapter);
}
/*
Configures a registry with injections on Ember applications
for the Ember-Data store. Accepts an optional namespace argument.
@method initializeStoreInjections
@param {Ember.Registry} registry
*/
function initializeStoreInjections(registry) {
// registry.injection for Ember < 2.1.0
// application.inject for Ember 2.1.0+
let inject = registry.inject || registry.injection;
inject.call(registry, 'controller', 'store', 'service:store');
inject.call(registry, 'route', 'store', 'service:store');
inject.call(registry, 'data-adapter', 'store', 'service:store');
}
/*
Configures a registry for use with Ember-Data
transforms.
@method initializeTransforms
@param {Ember.Registry} registry
*/
function initializeTransforms(registry) {
registry.register('transform:boolean', _private2.BooleanTransform);
registry.register('transform:date', _private2.DateTransform);
registry.register('transform:number', _private2.NumberTransform);
registry.register('transform:string', _private2.StringTransform);
}
function setupContainer(application) {
initializeDataAdapter(application);
initializeTransforms(application);
initializeStoreInjections(application);
initializeStore(application);
}
});
define("ember-data/store", ["exports", "@ember-data/store"], function (_exports, _store) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _store.default;
}
});
});
define("ember-data/transform", ["exports", "@ember-data/serializer/transform"], function (_exports, _transform) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _transform.default;
}
});
});
define("ember-data/-debug/index", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.instrument = instrument;
_exports.assertPolymorphicType = void 0;
function instrument(method) {
return method();
}
/*
Assert that `addedRecord` has a valid type so it can be added to the
relationship of the `record`.
The assert basically checks if the `addedRecord` can be added to the
relationship (specified via `relationshipMeta`) of the `record`.
This utility should only be used internally, as both record parameters must
be an InternalModel and the `relationshipMeta` needs to be the meta
information about the relationship, retrieved via
`record.relationshipFor(key)`.
@method assertPolymorphicType
@param {InternalModel} internalModel
@param {RelationshipMeta} relationshipMeta retrieved via
`record.relationshipFor(key)`
@param {InternalModel} addedRecord record which
should be added/set for the relationship
*/
let assertPolymorphicType;
_exports.assertPolymorphicType = assertPolymorphicType;
if (true
/* DEBUG */
) {
let checkPolymorphic = function checkPolymorphic(modelClass, addedModelClass) {
if (modelClass.__isMixin) {
//TODO Need to do this in order to support mixins, should convert to public api
//once it exists in Ember
return modelClass.__mixin.detect(addedModelClass.PrototypeMixin);
}
return addedModelClass.prototype instanceof modelClass || modelClass.detect(addedModelClass);
};
_exports.assertPolymorphicType = assertPolymorphicType = function assertPolymorphicType(parentInternalModel, relationshipMeta, addedInternalModel, store) {
let addedModelName = addedInternalModel.modelName;
let parentModelName = parentInternalModel.modelName;
let key = relationshipMeta.key;
let relationshipModelName = relationshipMeta.type;
let relationshipClass = store.modelFor(relationshipModelName);
let addedClass = store.modelFor(addedInternalModel.modelName);
let assertionMessage = "The '".concat(addedModelName, "' type does not implement '").concat(relationshipModelName, "' and thus cannot be assigned to the '").concat(key, "' relationship in '").concat(parentModelName, "'. Make it a descendant of '").concat(relationshipModelName, "' or use a mixin of the same name.");
(true && Ember.assert(assertionMessage, checkPolymorphic(relationshipClass, addedClass)));
};
}
});
define("ember-data/-private/core", ["exports", "ember-data/version"], function (_exports, _version) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module ember-data
*/
/**
All Ember Data classes, methods and functions are defined inside of this namespace.
@class DS
@static
*/
/**
@property VERSION
@type String
@static
*/
const DS = Ember.Namespace.create({
VERSION: _version.default,
name: 'DS'
});
if (Ember.libraries) {
Ember.libraries.registerCoreLibrary('Ember Data', DS.VERSION);
}
var _default = DS;
_exports.default = _default;
});
define("ember-data/-private/features", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = featureIsEnabled;
function featureIsEnabled() {
return Ember.FEATURES.isEnabled(...arguments);
}
});
define("ember-data/-private/index", ["exports", "@ember-data/store/-private", "@ember-data/store", "ember-data/-private/core", "ember-data/-private/features", "ember-data/-private/system/debug/debug-adapter"], function (_exports, _private, _store, _core, _features, _debugAdapter) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "Errors", {
enumerable: true,
get: function () {
return _private.Errors;
}
});
Object.defineProperty(_exports, "Snapshot", {
enumerable: true,
get: function () {
return _private.Snapshot;
}
});
Object.defineProperty(_exports, "AdapterPopulatedRecordArray", {
enumerable: true,
get: function () {
return _private.AdapterPopulatedRecordArray;
}
});
Object.defineProperty(_exports, "InternalModel", {
enumerable: true,
get: function () {
return _private.InternalModel;
}
});
Object.defineProperty(_exports, "ManyArray", {
enumerable: true,
get: function () {
return _private.ManyArray;
}
});
Object.defineProperty(_exports, "PromiseArray", {
enumerable: true,
get: function () {
return _private.PromiseArray;
}
});
Object.defineProperty(_exports, "Relationship", {
enumerable: true,
get: function () {
return _private.Relationship;
}
});
Object.defineProperty(_exports, "PromiseManyArray", {
enumerable: true,
get: function () {
return _private.PromiseManyArray;
}
});
Object.defineProperty(_exports, "PromiseObject", {
enumerable: true,
get: function () {
return _private.PromiseObject;
}
});
Object.defineProperty(_exports, "RecordData", {
enumerable: true,
get: function () {
return _private.RecordData;
}
});
Object.defineProperty(_exports, "RecordArray", {
enumerable: true,
get: function () {
return _private.RecordArray;
}
});
Object.defineProperty(_exports, "RecordArrayManager", {
enumerable: true,
get: function () {
return _private.RecordArrayManager;
}
});
Object.defineProperty(_exports, "RootState", {
enumerable: true,
get: function () {
return _private.RootState;
}
});
Object.defineProperty(_exports, "SnapshotRecordArray", {
enumerable: true,
get: function () {
return _private.SnapshotRecordArray;
}
});
Object.defineProperty(_exports, "recordDataFor", {
enumerable: true,
get: function () {
return _private.recordDataFor;
}
});
Object.defineProperty(_exports, "relationshipStateFor", {
enumerable: true,
get: function () {
return _private.relationshipStateFor;
}
});
Object.defineProperty(_exports, "relationshipsFor", {
enumerable: true,
get: function () {
return _private.relationshipsFor;
}
});
Object.defineProperty(_exports, "normalizeModelName", {
enumerable: true,
get: function () {
return _private.normalizeModelName;
}
});
Object.defineProperty(_exports, "coerceId", {
enumerable: true,
get: function () {
return _private.coerceId;
}
});
Object.defineProperty(_exports, "Store", {
enumerable: true,
get: function () {
return _store.default;
}
});
Object.defineProperty(_exports, "DS", {
enumerable: true,
get: function () {
return _core.default;
}
});
Object.defineProperty(_exports, "isEnabled", {
enumerable: true,
get: function () {
return _features.default;
}
});
Object.defineProperty(_exports, "DebugAdapter", {
enumerable: true,
get: function () {
return _debugAdapter.default;
}
});
});
define("ember-data/adapters/errors", ["exports", "@ember-data/adapter/error"], function (_exports, _error) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "AbortError", {
enumerable: true,
get: function () {
return _error.AbortError;
}
});
Object.defineProperty(_exports, "AdapterError", {
enumerable: true,
get: function () {
return _error.AdapterError;
}
});
Object.defineProperty(_exports, "ConflictError", {
enumerable: true,
get: function () {
return _error.ConflictError;
}
});
Object.defineProperty(_exports, "ForbiddenError", {
enumerable: true,
get: function () {
return _error.ForbiddenError;
}
});
Object.defineProperty(_exports, "InvalidError", {
enumerable: true,
get: function () {
return _error.InvalidError;
}
});
Object.defineProperty(_exports, "NotFoundError", {
enumerable: true,
get: function () {
return _error.NotFoundError;
}
});
Object.defineProperty(_exports, "ServerError", {
enumerable: true,
get: function () {
return _error.ServerError;
}
});
Object.defineProperty(_exports, "TimeoutError", {
enumerable: true,
get: function () {
return _error.TimeoutError;
}
});
Object.defineProperty(_exports, "UnauthorizedError", {
enumerable: true,
get: function () {
return _error.UnauthorizedError;
}
});
Object.defineProperty(_exports, "errorsArrayToHash", {
enumerable: true,
get: function () {
return _error.errorsArrayToHash;
}
});
Object.defineProperty(_exports, "errorsHashToArray", {
enumerable: true,
get: function () {
return _error.errorsHashToArray;
}
});
});
define("ember-data/adapters/json-api", ["exports", "@ember-data/adapter/json-api"], function (_exports, _jsonApi) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _jsonApi.default;
}
});
});
define("ember-data/adapters/rest", ["exports", "@ember-data/adapter/rest"], function (_exports, _rest) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _rest.default;
}
});
});
define("ember-data/serializers/embedded-records-mixin", ["exports", "@ember-data/serializer/rest"], function (_exports, _rest) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _rest.EmbeddedRecordsMixin;
}
});
});
define("ember-data/serializers/json-api", ["exports", "@ember-data/serializer/json-api"], function (_exports, _jsonApi) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _jsonApi.default;
}
});
});
define("ember-data/serializers/json", ["exports", "@ember-data/serializer/json"], function (_exports, _json) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _json.default;
}
});
});
define("ember-data/serializers/rest", ["exports", "@ember-data/serializer/rest"], function (_exports, _rest) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _rest.default;
}
});
});
define("ember-data/-private/system/debug/debug-adapter", ["exports", "@ember-data/model"], function (_exports, _model) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/*
Extend `Ember.DataAdapter` with ED specific code.
@class DebugAdapter
@extends Ember.DataAdapter
@private
*/
var _default = Ember.DataAdapter.extend({
getFilters() {
return [{
name: 'isNew',
desc: 'New'
}, {
name: 'isModified',
desc: 'Modified'
}, {
name: 'isClean',
desc: 'Clean'
}];
},
detect(typeClass) {
return typeClass !== _model.default && _model.default.detect(typeClass);
},
columnNameToDesc(name) {
return Ember.String.capitalize(Ember.String.underscore(name).replace(/_/g, ' ').trim());
},
columnsForType(typeClass) {
let columns = [{
name: 'id',
desc: 'Id'
}];
let count = 0;
let self = this;
Ember.get(typeClass, 'attributes').forEach((meta, name) => {
if (count++ > self.attributeLimit) {
return false;
}
let desc = this.columnNameToDesc(name);
columns.push({
name: name,
desc: desc
});
});
return columns;
},
getRecords(modelClass, modelName) {
if (arguments.length < 2) {
// Legacy Ember.js < 1.13 support
let containerKey = modelClass._debugContainerKey;
if (containerKey) {
let match = containerKey.match(/model:(.*)/);
if (match !== null) {
modelName = match[1];
}
}
}
(true && Ember.assert('Cannot find model name. Please upgrade to Ember.js >= 1.13 for Ember Inspector support', !!modelName));
return this.get('store').peekAll(modelName);
},
getRecordColumnValues(record) {
let count = 0;
let columnValues = {
id: Ember.get(record, 'id')
};
record.eachAttribute(key => {
if (count++ > this.attributeLimit) {
return false;
}
columnValues[key] = Ember.get(record, key);
});
return columnValues;
},
getRecordKeywords(record) {
let keywords = [];
let keys = Ember.A(['id']);
record.eachAttribute(key => keys.push(key));
keys.forEach(key => keywords.push(Ember.get(record, key)));
return keywords;
},
getRecordFilterValues(record) {
return {
isNew: record.get('isNew'),
isModified: record.get('hasDirtyAttributes') && !record.get('isNew'),
isClean: !record.get('hasDirtyAttributes')
};
},
getRecordColor(record) {
let color = 'black';
if (record.get('isNew')) {
color = 'green';
} else if (record.get('hasDirtyAttributes')) {
color = 'blue';
}
return color;
},
observeRecord(record, recordUpdated) {
let releaseMethods = Ember.A();
let keysToObserve = Ember.A(['id', 'isNew', 'hasDirtyAttributes']);
record.eachAttribute(key => keysToObserve.push(key));
let adapter = this;
keysToObserve.forEach(function (key) {
let handler = function () {
recordUpdated(adapter.wrapRecord(record));
};
Ember.addObserver(record, key, handler);
releaseMethods.push(function () {
Ember.removeObserver(record, key, handler);
});
});
let release = function () {
releaseMethods.forEach(fn => fn());
};
return release;
}
});
_exports.default = _default;
});
define("@ember-data/adapter/adapter", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
An adapter is an object that receives requests from a store and
translates them into the appropriate action to take against your
persistence layer. The persistence layer is usually an HTTP API but
may be anything, such as the browser's local storage. Typically the
adapter is not invoked directly instead its functionality is accessed
through the `store`.
### Creating an Adapter
Create a new subclass of `Adapter` in the `app/adapters` folder:
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
// ...your code here
});
```
Model-specific adapters can be created by putting your adapter
class in an `app/adapters/` + `model-name` + `.js` file of the application.
```app/adapters/post.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
// ...Post-specific adapter code goes here
});
```
`Adapter` is an abstract base class that you should override in your
application to customize it for your backend. The minimum set of methods
that you should implement is:
* `findRecord()`
* `createRecord()`
* `updateRecord()`
* `deleteRecord()`
* `findAll()`
* `query()`
To improve the network performance of your application, you can optimize
your adapter by overriding these lower-level methods:
* `findMany()`
For an example of the implementation, see `RESTAdapter`, the
included REST adapter.
@class Adapter
@extends EmberObject
*/
var _default = Ember.Object.extend({
/**
If you would like your adapter to use a custom serializer you can
set the `defaultSerializer` property to be the name of the custom
serializer.
Note the `defaultSerializer` serializer has a lower priority than
a model specific serializer (i.e. `PostSerializer`) or the
`application` serializer.
```app/adapters/django.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
defaultSerializer: 'django'
});
```
@property defaultSerializer
@type {String}
*/
defaultSerializer: '-default',
/**
The `findRecord()` method is invoked when the store is asked for a record that
has not previously been loaded. In response to `findRecord()` being called, you
should query your persistence layer for a record with the given ID. The `findRecord`
method should return a promise that will resolve to a JavaScript object that will be
normalized by the serializer.
Here is an example of the `findRecord` implementation:
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend({
findRecord(store, type, id, snapshot) {
return new RSVP.Promise(function(resolve, reject) {
$.getJSON(`/${type.modelName}/${id}`).then(function(data) {
resolve(data);
}, function(jqXHR) {
reject(jqXHR);
});
});
}
});
```
@method findRecord
@param {DS.Store} store
@param {DS.Model} type
@param {String} id
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
findRecord: null,
/**
The `findAll()` method is used to retrieve all records for a given type.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend({
findAll(store, type) {
return new RSVP.Promise(function(resolve, reject) {
$.getJSON(`/${type.modelName}`).then(function(data) {
resolve(data);
}, function(jqXHR) {
reject(jqXHR);
});
});
}
});
```
@method findAll
@param {DS.Store} store
@param {DS.Model} type
@param {undefined} neverSet a value is never provided to this argument
@param {DS.SnapshotRecordArray} snapshotRecordArray
@return {Promise} promise
*/
findAll: null,
/**
This method is called when you call `query` on the store.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend({
query(store, type, query) {
return new RSVP.Promise(function(resolve, reject) {
$.getJSON(`/${type.modelName}`, query).then(function(data) {
resolve(data);
}, function(jqXHR) {
reject(jqXHR);
});
});
}
});
```
@method query
@param {DS.Store} store
@param {DS.Model} type
@param {Object} query
@param {DS.AdapterPopulatedRecordArray} recordArray
@return {Promise} promise
*/
query: null,
/**
The `queryRecord()` method is invoked when the store is asked for a single
record through a query object.
In response to `queryRecord()` being called, you should always fetch fresh
data. Once found, you can asynchronously call the store's `push()` method
to push the record into the store.
Here is an example `queryRecord` implementation:
Example
```app/adapters/application.js
import Adapter, { BuildURLMixin } from '@ember-data/adapter';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend(BuildURLMixin, {
queryRecord(store, type, query) {
return new RSVP.Promise(function(resolve, reject) {
$.getJSON(`/${type.modelName}`, query).then(function(data) {
resolve(data);
}, function(jqXHR) {
reject(jqXHR);
});
});
}
});
```
@method queryRecord
@param {DS.Store} store
@param {subclass of DS.Model} type
@param {Object} query
@return {Promise} promise
*/
queryRecord: null,
/**
If the globally unique IDs for your records should be generated on the client,
implement the `generateIdForRecord()` method. This method will be invoked
each time you create a new record, and the value returned from it will be
assigned to the record's `primaryKey`.
Most traditional REST-like HTTP APIs will not use this method. Instead, the ID
of the record will be set by the server, and your adapter will update the store
with the new ID when it calls `didCreateRecord()`. Only implement this method if
you intend to generate record IDs on the client-side.
The `generateIdForRecord()` method will be invoked with the requesting store as
the first parameter and the newly created record as the second parameter:
```javascript
import Adapter from '@ember-data/adapter';
import { v4 } from 'uuid';
export default Adapter.extend({
generateIdForRecord(store, type, inputProperties) {
return v4();
}
});
```
@method generateIdForRecord
@param {DS.Store} store
@param {DS.Model} type the Model class of the record
@param {Object} inputProperties a hash of properties to set on the
newly created record.
@return {(String|Number)} id
*/
generateIdForRecord: null,
/**
Proxies to the serializer's `serialize` method.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
createRecord(store, type, snapshot) {
let data = this.serialize(snapshot, { includeId: true });
let url = `/${type.modelName}`;
// ...
}
});
```
@method serialize
@param {DS.Snapshot} snapshot
@param {Object} options
@return {Object} serialized snapshot
*/
serialize(snapshot, options) {
return snapshot.serialize(options);
},
/**
Implement this method in a subclass to handle the creation of
new records.
Serializes the record and sends it to the server.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
import { run } from '@ember/runloop';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend({
createRecord(store, type, snapshot) {
let data = this.serialize(snapshot, { includeId: true });
return new RSVP.Promise(function(resolve, reject) {
$.ajax({
type: 'POST',
url: `/${type.modelName}`,
dataType: 'json',
data: data
}).then(function(data) {
run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
run(null, reject, jqXHR);
});
});
}
});
```
@method createRecord
@param {DS.Store} store
@param {DS.Model} type the Model class of the record
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
createRecord: null,
/**
Implement this method in a subclass to handle the updating of
a record.
Serializes the record update and sends it to the server.
The updateRecord method is expected to return a promise that will
resolve with the serialized record. This allows the backend to
inform the Ember Data store the current state of this record after
the update. If it is not possible to return a serialized record
the updateRecord promise can also resolve with `undefined` and the
Ember Data store will assume all of the updates were successfully
applied on the backend.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
import { run } from '@ember/runloop';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend({
updateRecord(store, type, snapshot) {
let data = this.serialize(snapshot, { includeId: true });
let id = snapshot.id;
return new RSVP.Promise(function(resolve, reject) {
$.ajax({
type: 'PUT',
url: `/${type.modelName}/${id}`,
dataType: 'json',
data: data
}).then(function(data) {
run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
run(null, reject, jqXHR);
});
});
}
});
```
@method updateRecord
@param {DS.Store} store
@param {DS.Model} type the Model class of the record
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
updateRecord: null,
/**
Implement this method in a subclass to handle the deletion of
a record.
Sends a delete request for the record to the server.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
import { run } from '@ember/runloop';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend({
deleteRecord(store, type, snapshot) {
let data = this.serialize(snapshot, { includeId: true });
let id = snapshot.id;
return new RSVP.Promise(function(resolve, reject) {
$.ajax({
type: 'DELETE',
url: `/${type.modelName}/${id}`,
dataType: 'json',
data: data
}).then(function(data) {
run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
run(null, reject, jqXHR);
});
});
}
});
```
@method deleteRecord
@param {DS.Store} store
@param {DS.Model} type the Model class of the record
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
deleteRecord: null,
/**
By default the store will try to coalesce all `fetchRecord` calls within the same runloop
into as few requests as possible by calling groupRecordsForFindMany and passing it into a findMany call.
You can opt out of this behaviour by either not implementing the findMany hook or by setting
coalesceFindRequests to false.
@property coalesceFindRequests
@type {boolean}
*/
coalesceFindRequests: true,
/**
The store will call `findMany` instead of multiple `findRecord`
requests to find multiple records at once if coalesceFindRequests
is true.
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
import { run } from '@ember/runloop';
import RSVP from 'RSVP';
import $ from 'jquery';
export default Adapter.extend({
findMany(store, type, ids, snapshots) {
return new RSVP.Promise(function(resolve, reject) {
$.ajax({
type: 'GET',
url: `/${type.modelName}/`,
dataType: 'json',
data: { filter: { id: ids.join(',') } }
}).then(function(data) {
run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
run(null, reject, jqXHR);
});
});
}
});
```
@method findMany
@param {DS.Store} store
@param {DS.Model} type the Model class of the records
@param {Array} ids
@param {Array} snapshots
@return {Promise} promise
*/
findMany: null,
/**
Organize records into groups, each of which is to be passed to separate
calls to `findMany`.
For example, if your API has nested URLs that depend on the parent, you will
want to group records by their parent.
The default implementation returns the records as a single group.
@method groupRecordsForFindMany
@param {DS.Store} store
@param {Array} snapshots
@return {Array} an array of arrays of records, each of which is to be
loaded separately by `findMany`.
*/
groupRecordsForFindMany(store, snapshots) {
return [snapshots];
},
/**
This method is used by the store to determine if the store should
reload a record from the adapter when a record is requested by
`store.findRecord`.
If this method returns `true`, the store will re-fetch a record from
the adapter. If this method returns `false`, the store will resolve
immediately using the cached record.
For example, if you are building an events ticketing system, in which users
can only reserve tickets for 20 minutes at a time, and want to ensure that
in each route you have data that is no more than 20 minutes old you could
write:
```javascript
shouldReloadRecord(store, ticketSnapshot) {
let lastAccessedAt = ticketSnapshot.attr('lastAccessedAt');
let timeDiff = moment().diff(lastAccessedAt, 'minutes');
if (timeDiff > 20) {
return true;
} else {
return false;
}
}
```
This method would ensure that whenever you do `store.findRecord('ticket',
id)` you will always get a ticket that is no more than 20 minutes old. In
case the cached version is more than 20 minutes old, `findRecord` will not
resolve until you fetched the latest version.
By default this hook returns `false`, as most UIs should not block user
interactions while waiting on data update.
Note that, with default settings, `shouldBackgroundReloadRecord` will always
re-fetch the records in the background even if `shouldReloadRecord` returns
`false`. You can override `shouldBackgroundReloadRecord` if this does not
suit your use case.
@since 1.13.0
@method shouldReloadRecord
@param {DS.Store} store
@param {DS.Snapshot} snapshot
@return {Boolean}
*/
shouldReloadRecord(store, snapshot) {
return false;
},
/**
This method is used by the store to determine if the store should
reload all records from the adapter when records are requested by
`store.findAll`.
If this method returns `true`, the store will re-fetch all records from
the adapter. If this method returns `false`, the store will resolve
immediately using the cached records.
For example, if you are building an events ticketing system, in which users
can only reserve tickets for 20 minutes at a time, and want to ensure that
in each route you have data that is no more than 20 minutes old you could
write:
```javascript
shouldReloadAll(store, snapshotArray) {
let snapshots = snapshotArray.snapshots();
return snapshots.any((ticketSnapshot) => {
let lastAccessedAt = ticketSnapshot.attr('lastAccessedAt');
let timeDiff = moment().diff(lastAccessedAt, 'minutes');
if (timeDiff > 20) {
return true;
} else {
return false;
}
});
}
```
This method would ensure that whenever you do `store.findAll('ticket')` you
will always get a list of tickets that are no more than 20 minutes old. In
case a cached version is more than 20 minutes old, `findAll` will not
resolve until you fetched the latest versions.
By default, this method returns `true` if the passed `snapshotRecordArray`
is empty (meaning that there are no records locally available yet),
otherwise, it returns `false`.
Note that, with default settings, `shouldBackgroundReloadAll` will always
re-fetch all the records in the background even if `shouldReloadAll` returns
`false`. You can override `shouldBackgroundReloadAll` if this does not suit
your use case.
@since 1.13.0
@method shouldReloadAll
@param {DS.Store} store
@param {DS.SnapshotRecordArray} snapshotRecordArray
@return {Boolean}
*/
shouldReloadAll(store, snapshotRecordArray) {
return !snapshotRecordArray.length;
},
/**
This method is used by the store to determine if the store should
reload a record after the `store.findRecord` method resolves a
cached record.
This method is *only* checked by the store when the store is
returning a cached record.
If this method returns `true` the store will re-fetch a record from
the adapter.
For example, if you do not want to fetch complex data over a mobile
connection, or if the network is down, you can implement
`shouldBackgroundReloadRecord` as follows:
```javascript
shouldBackgroundReloadRecord(store, snapshot) {
let { downlink, effectiveType } = navigator.connection;
return downlink > 0 && effectiveType === '4g';
}
```
By default, this hook returns `true` so the data for the record is updated
in the background.
@since 1.13.0
@method shouldBackgroundReloadRecord
@param {DS.Store} store
@param {DS.Snapshot} snapshot
@return {Boolean}
*/
shouldBackgroundReloadRecord(store, snapshot) {
return true;
},
/**
This method is used by the store to determine if the store should
reload a record array after the `store.findAll` method resolves
with a cached record array.
This method is *only* checked by the store when the store is
returning a cached record array.
If this method returns `true` the store will re-fetch all records
from the adapter.
For example, if you do not want to fetch complex data over a mobile
connection, or if the network is down, you can implement
`shouldBackgroundReloadAll` as follows:
```javascript
shouldBackgroundReloadAll(store, snapshotArray) {
let { downlink, effectiveType } = navigator.connection;
return downlink > 0 && effectiveType === '4g';
}
```
By default this method returns `true`, indicating that a background reload
should always be triggered.
@since 1.13.0
@method shouldBackgroundReloadAll
@param {DS.Store} store
@param {DS.SnapshotRecordArray} snapshotRecordArray
@return {Boolean}
*/
shouldBackgroundReloadAll(store, snapshotRecordArray) {
return true;
}
});
_exports.default = _default;
});
define("@ember-data/adapter/error", ["exports", "@ember-data/store/-private"], function (_exports, _private) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "errorsHashToArray", {
enumerable: true,
get: function () {
return _private.errorsHashToArray;
}
});
Object.defineProperty(_exports, "errorsArrayToHash", {
enumerable: true,
get: function () {
return _private.errorsArrayToHash;
}
});
_exports.ServerError = _exports.ConflictError = _exports.NotFoundError = _exports.ForbiddenError = _exports.UnauthorizedError = _exports.AbortError = _exports.TimeoutError = _exports.InvalidError = _exports.default = void 0;
/**
@module @ember-data/adapter
*/
/**
A `AdapterError` is used by an adapter to signal that an error occurred
during a request to an external API. It indicates a generic error, and
subclasses are used to indicate specific error states. The following
subclasses are provided:
- `InvalidError`
- `TimeoutError`
- `AbortError`
- `UnauthorizedError`
- `ForbiddenError`
- `NotFoundError`
- `ConflictError`
- `ServerError`
To create a custom error to signal a specific error state in communicating
with an external API, extend the `DS.AdapterError`. For example, if the
external API exclusively used HTTP `503 Service Unavailable` to indicate
it was closed for maintenance:
```app/adapters/maintenance-error.js
import AdapterError from '@ember-data/adapter/error';
export default AdapterError.extend({ message: "Down for maintenance." });
```
This error would then be returned by an adapter's `handleResponse` method:
```app/adapters/application.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
import MaintenanceError from './maintenance-error';
export default JSONAPIAdapter.extend({
handleResponse(status) {
if (503 === status) {
return new MaintenanceError();
}
return this._super(...arguments);
}
});
```
And can then be detected in an application and used to send the user to an
`under-maintenance` route:
```app/routes/application.js
import Route from '@ember/routing/route';
import MaintenanceError from '../adapters/maintenance-error';
export default Route.extend({
actions: {
error(error, transition) {
if (error instanceof MaintenanceError) {
this.transitionTo('under-maintenance');
return;
}
// ...other error handling logic
}
}
});
```
@class AdapterError
*/
function AdapterError(errors, message = 'Adapter operation failed') {
this.isAdapterError = true;
let error = Ember.Error.call(this, message); // in ember 3.8+ Error is a Native Error and we don't
// gain these automatically from the EmberError.call
if (error) {
this.stack = error.stack;
this.description = error.description;
this.fileName = error.fileName;
this.lineNumber = error.lineNumber;
this.message = error.message;
this.name = error.name;
this.number = error.number;
this.code = error.code;
}
this.errors = errors || [{
title: 'Adapter Error',
detail: message
}];
}
var _default = AdapterError;
_exports.default = _default;
function extendFn(ErrorClass) {
return function ({
message: defaultMessage
} = {}) {
return extend(ErrorClass, defaultMessage);
};
}
function extend(ParentErrorClass, defaultMessage) {
let ErrorClass = function (errors, message) {
(true && Ember.assert('`AdapterError` expects json-api formatted errors array.', Array.isArray(errors || [])));
ParentErrorClass.call(this, errors, message || defaultMessage);
};
ErrorClass.prototype = Object.create(ParentErrorClass.prototype);
ErrorClass.extend = extendFn(ErrorClass);
return ErrorClass;
}
AdapterError.prototype = Object.create(Ember.Error.prototype);
AdapterError.extend = extendFn(AdapterError);
/**
A `InvalidError` is used by an adapter to signal the external API
was unable to process a request because the content was not
semantically correct or meaningful per the API. Usually, this means a
record failed some form of server-side validation. When a promise
from an adapter is rejected with a `InvalidError` the record will
transition to the `invalid` state and the errors will be set to the
`errors` property on the record.
For Ember Data to correctly map errors to their corresponding
properties on the model, Ember Data expects each error to be
a valid JSON-API error object with a `source/pointer` that matches
the property name. For example, if you had a Post model that
looked like this.
```app/models/post.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
title: attr('string'),
content: attr('string')
});
```
To show an error from the server related to the `title` and
`content` properties your adapter could return a promise that
rejects with a `InvalidError` object that looks like this:
```app/adapters/post.js
import RSVP from 'RSVP';
import RESTAdapter from '@ember-data/adapter/rest';
import { InvalidError } from '@ember-data/adapter/error';
export default RESTAdapter.extend({
updateRecord() {
// Fictional adapter that always rejects
return RSVP.reject(new InvalidError([
{
detail: 'Must be unique',
source: { pointer: '/data/attributes/title' }
},
{
detail: 'Must not be blank',
source: { pointer: '/data/attributes/content'}
}
]));
}
});
```
Your backend may use different property names for your records the
store will attempt to extract and normalize the errors using the
serializer's `extractErrors` method before the errors get added to
the model. As a result, it is safe for the `InvalidError` to
wrap the error payload unaltered.
@class InvalidError
@extends AdapterError
*/
const InvalidError = extend(AdapterError, 'The adapter rejected the commit because it was invalid');
/**
A `TimeoutError` is used by an adapter to signal that a request
to the external API has timed out. I.e. no response was received from
the external API within an allowed time period.
An example use case would be to warn the user to check their internet
connection if an adapter operation has timed out:
```app/routes/application.js
import Route from '@ember/routing/route';
import { TimeoutError } from '@ember-data/adapter/error';
export default Route.extend({
actions: {
error(error, transition) {
if (error instanceof TimeoutError) {
// alert the user
alert('Are you still connected to the internet?');
return;
}
// ...other error handling logic
}
}
});
```
@class TimeoutError
@extends AdapterError
*/
_exports.InvalidError = InvalidError;
const TimeoutError = extend(AdapterError, 'The adapter operation timed out');
/**
A `AbortError` is used by an adapter to signal that a request to
the external API was aborted. For example, this can occur if the user
navigates away from the current page after a request to the external API
has been initiated but before a response has been received.
@class AbortError
@extends AdapterError
*/
_exports.TimeoutError = TimeoutError;
const AbortError = extend(AdapterError, 'The adapter operation was aborted');
/**
A `UnauthorizedError` equates to a HTTP `401 Unauthorized` response
status. It is used by an adapter to signal that a request to the external
API was rejected because authorization is required and has failed or has not
yet been provided.
An example use case would be to redirect the user to a login route if a
request is unauthorized:
```app/routes/application.js
import Route from '@ember/routing/route';
import { UnauthorizedError } from '@ember-data/adapter/error';
export default Route.extend({
actions: {
error(error, transition) {
if (error instanceof UnauthorizedError) {
// go to the sign in route
this.transitionTo('login');
return;
}
// ...other error handling logic
}
}
});
```
@class UnauthorizedError
@extends AdapterError
*/
_exports.AbortError = AbortError;
const UnauthorizedError = extend(AdapterError, 'The adapter operation is unauthorized');
/**
A `ForbiddenError` equates to a HTTP `403 Forbidden` response status.
It is used by an adapter to signal that a request to the external API was
valid but the server is refusing to respond to it. If authorization was
provided and is valid, then the authenticated user does not have the
necessary permissions for the request.
@class ForbiddenError
@extends AdapterError
*/
_exports.UnauthorizedError = UnauthorizedError;
const ForbiddenError = extend(AdapterError, 'The adapter operation is forbidden');
/**
A `NotFoundError` equates to a HTTP `404 Not Found` response status.
It is used by an adapter to signal that a request to the external API
was rejected because the resource could not be found on the API.
An example use case would be to detect if the user has entered a route
for a specific model that does not exist. For example:
```app/routes/post.js
import Route from '@ember/routing/route';
import { NotFoundError } from '@ember-data/adapter/error';
export default Route.extend({
model(params) {
return this.get('store').findRecord('post', params.post_id);
},
actions: {
error(error, transition) {
if (error instanceof NotFoundError) {
// redirect to a list of all posts instead
this.transitionTo('posts');
} else {
// otherwise let the error bubble
return true;
}
}
}
});
```
@class NotFoundError
@extends AdapterError
*/
_exports.ForbiddenError = ForbiddenError;
const NotFoundError = extend(AdapterError, 'The adapter could not find the resource');
/**
A `ConflictError` equates to a HTTP `409 Conflict` response status.
It is used by an adapter to indicate that the request could not be processed
because of a conflict in the request. An example scenario would be when
creating a record with a client-generated ID but that ID is already known
to the external API.
@class ConflictError
@extends AdapterError
*/
_exports.NotFoundError = NotFoundError;
const ConflictError = extend(AdapterError, 'The adapter operation failed due to a conflict');
/**
A `ServerError` equates to a HTTP `500 Internal Server Error` response
status. It is used by the adapter to indicate that a request has failed
because of an error in the external API.
@class ServerError
@extends AdapterError
*/
_exports.ConflictError = ConflictError;
const ServerError = extend(AdapterError, 'The adapter operation failed due to a server error');
_exports.ServerError = ServerError;
});
define("@ember-data/adapter/index", ["exports", "@ember-data/adapter/-private", "@ember-data/adapter/adapter"], function (_exports, _private, _adapter) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "BuildURLMixin", {
enumerable: true,
get: function () {
return _private.BuildURLMixin;
}
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _adapter.default;
}
});
});
define("@ember-data/adapter/json-api", ["exports", "@ember-data/adapter/rest", "ember-inflector", "@ember-data/adapter/-private"], function (_exports, _rest, _emberInflector, _private) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
The `JSONAPIAdapter` is the default adapter used by Ember Data. It
is responsible for transforming the store's requests into HTTP
requests that follow the [JSON API](http://jsonapi.org/format/)
format.
## JSON API Conventions
The JSONAPIAdapter uses JSON API conventions for building the URL
for a record and selecting the HTTP verb to use with a request. The
actions you can take on a record map onto the following URLs in the
JSON API adapter:
Action
HTTP Verb
URL
`store.findRecord('post', 123)`
GET
/posts/123
`store.findAll('post')`
GET
/posts
Update `postRecord.save()`
PATCH
/posts/123
Create `store.createRecord('post').save()`
POST
/posts
Delete `postRecord.destroyRecord()`
DELETE
/posts/123
## Success and failure
The JSONAPIAdapter will consider a success any response with a
status code of the 2xx family ("Success"), as well as 304 ("Not
Modified"). Any other status code will be considered a failure.
On success, the request promise will be resolved with the full
response payload.
Failed responses with status code 422 ("Unprocessable Entity") will
be considered "invalid". The response will be discarded, except for
the `errors` key. The request promise will be rejected with a
`InvalidError`. This error object will encapsulate the saved
`errors` value.
Any other status codes will be treated as an adapter error. The
request promise will be rejected, similarly to the invalid case,
but with an instance of `AdapterError` instead.
### Endpoint path customization
Endpoint paths can be prefixed with a `namespace` by setting the
namespace property on the adapter:
```app/adapters/application.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
namespace: 'api/1'
});
```
Requests for the `person` model would now target `/api/1/people/1`.
### Host customization
An adapter can target other hosts by setting the `host` property.
```app/adapters/application.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
host: 'https://api.example.com'
});
```
Requests for the `person` model would now target
`https://api.example.com/people/1`.
@since 1.13.0
@class JSONAPIAdapter
@constructor
@extends RESTAdapter
*/
const JSONAPIAdapter = _rest.default.extend({
defaultSerializer: '-json-api',
/**
@method ajaxOptions
@private
@param {String} url
@param {String} type The request type GET, POST, PUT, DELETE etc.
@param {Object} options
@return {Object}
*/
ajaxOptions(url, type, options = {}) {
options.contentType = options.contentType || 'application/vnd.api+json';
let hash = this._super(url, type, options);
hash.headers['Accept'] = hash.headers['Accept'] || 'application/vnd.api+json';
return hash;
},
/**
By default the JSONAPIAdapter will send each find request coming from a `store.find`
or from accessing a relationship separately to the server. If your server supports passing
ids as a query string, you can set coalesceFindRequests to true to coalesce all find requests
within a single runloop.
For example, if you have an initial payload of:
```javascript
{
data: {
id: 1,
type: 'post',
relationship: {
comments: {
data: [
{ id: 1, type: 'comment' },
{ id: 2, type: 'comment' }
]
}
}
}
}
```
By default calling `post.get('comments')` will trigger the following requests(assuming the
comments haven't been loaded before):
```
GET /comments/1
GET /comments/2
```
If you set coalesceFindRequests to `true` it will instead trigger the following request:
```
GET /comments?filter[id]=1,2
```
Setting coalesceFindRequests to `true` also works for `store.find` requests and `belongsTo`
relationships accessed within the same runloop. If you set `coalesceFindRequests: true`
```javascript
store.findRecord('comment', 1);
store.findRecord('comment', 2);
```
will also send a request to: `GET /comments?filter[id]=1,2`
Note: Requests coalescing rely on URL building strategy. So if you override `buildURL` in your app
`groupRecordsForFindMany` more likely should be overridden as well in order for coalescing to work.
@property coalesceFindRequests
@type {boolean}
*/
coalesceFindRequests: false,
findMany(store, type, ids, snapshots) {
let url = this.buildURL(type.modelName, ids, snapshots, 'findMany');
return this.ajax(url, 'GET', {
data: {
filter: {
id: ids.join(',')
}
}
});
},
pathForType(modelName) {
let dasherized = Ember.String.dasherize(modelName);
return (0, _emberInflector.pluralize)(dasherized);
},
updateRecord(store, type, snapshot) {
const data = (0, _private.serializeIntoHash)(store, type, snapshot);
let url = this.buildURL(type.modelName, snapshot.id, snapshot, 'updateRecord');
return this.ajax(url, 'PATCH', {
data: data
});
}
});
var _default = JSONAPIAdapter;
_exports.default = _default;
});
define("@ember-data/adapter/rest", ["exports", "@ember-data/adapter", "@ember-data/adapter/-private", "@ember-data/adapter/error"], function (_exports, _adapter, _private, _error) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.fetchOptions = fetchOptions;
_exports.default = void 0;
const Promise = Ember.RSVP.Promise;
const hasJQuery = typeof jQuery !== 'undefined';
const hasNajax = typeof najax !== 'undefined';
/**
The REST adapter allows your store to communicate with an HTTP server by
transmitting JSON via XHR. Most Ember.js apps that consume a JSON API
should use the REST adapter.
This adapter is designed around the idea that the JSON exchanged with
the server should be conventional.
## Success and failure
The REST adapter will consider a success any response with a status code
of the 2xx family ("Success"), as well as 304 ("Not Modified"). Any other
status code will be considered a failure.
On success, the request promise will be resolved with the full response
payload.
Failed responses with status code 422 ("Unprocessable Entity") will be
considered "invalid". The response will be discarded, except for the
`errors` key. The request promise will be rejected with a `InvalidError`.
This error object will encapsulate the saved `errors` value.
Any other status codes will be treated as an "adapter error". The request
promise will be rejected, similarly to the "invalid" case, but with
an instance of `AdapterError` instead.
## JSON Structure
The REST adapter expects the JSON returned from your server to follow
these conventions.
### Object Root
The JSON payload should be an object that contains the record inside a
root property. For example, in response to a `GET` request for
`/posts/1`, the JSON should look like this:
```js
{
"posts": {
"id": 1,
"title": "I'm Running to Reform the W3C's Tag",
"author": "Yehuda Katz"
}
}
```
Similarly, in response to a `GET` request for `/posts`, the JSON should
look like this:
```js
{
"posts": [
{
"id": 1,
"title": "I'm Running to Reform the W3C's Tag",
"author": "Yehuda Katz"
},
{
"id": 2,
"title": "Rails is omakase",
"author": "D2H"
}
]
}
```
Note that the object root can be pluralized for both a single-object response
and an array response: the REST adapter is not strict on this. Further, if the
HTTP server responds to a `GET` request to `/posts/1` (e.g. the response to a
`findRecord` query) with more than one object in the array, Ember Data will
only display the object with the matching ID.
### Conventional Names
Attribute names in your JSON payload should be the camelCased versions of
the attributes in your Ember.js models.
For example, if you have a `Person` model:
```app/models/person.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
firstName: attr('string'),
lastName: attr('string'),
occupation: attr('string')
});
```
The JSON returned should look like this:
```js
{
"people": {
"id": 5,
"firstName": "Zaphod",
"lastName": "Beeblebrox",
"occupation": "President"
}
}
```
#### Relationships
Relationships are usually represented by ids to the record in the
relationship. The related records can then be sideloaded in the
response under a key for the type.
```js
{
"posts": {
"id": 5,
"title": "I'm Running to Reform the W3C's Tag",
"author": "Yehuda Katz",
"comments": [1, 2]
},
"comments": [{
"id": 1,
"author": "User 1",
"message": "First!",
}, {
"id": 2,
"author": "User 2",
"message": "Good Luck!",
}]
}
```
If the records in the relationship are not known when the response
is serialized it's also possible to represent the relationship as a
URL using the `links` key in the response. Ember Data will fetch
this URL to resolve the relationship when it is accessed for the
first time.
```js
{
"posts": {
"id": 5,
"title": "I'm Running to Reform the W3C's Tag",
"author": "Yehuda Katz",
"links": {
"comments": "/posts/5/comments"
}
}
}
```
### Errors
If a response is considered a failure, the JSON payload is expected to include
a top-level key `errors`, detailing any specific issues. For example:
```js
{
"errors": {
"msg": "Something went wrong"
}
}
```
This adapter does not make any assumptions as to the format of the `errors`
object. It will simply be passed along as is, wrapped in an instance
of `InvalidError` or `AdapterError`. The serializer can interpret it
afterwards.
## Customization
### Endpoint path customization
Endpoint paths can be prefixed with a `namespace` by setting the namespace
property on the adapter:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
namespace: 'api/1'
});
```
Requests for the `Person` model would now target `/api/1/people/1`.
### Host customization
An adapter can target other hosts by setting the `host` property.
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
host: 'https://api.example.com'
});
```
### Headers customization
Some APIs require HTTP headers, e.g. to provide an API key. Arbitrary
headers can be set as key/value pairs on the `RESTAdapter`'s `headers`
object and Ember Data will send them along with each ajax request.
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
import { computed } from '@ember/object';
export default RESTAdapter.extend({
headers: computed(function() {
return {
'API_KEY': 'secret key',
'ANOTHER_HEADER': 'Some header value'
};
}
});
```
`headers` can also be used as a computed property to support dynamic
headers. In the example below, the `session` object has been
injected into an adapter by Ember's container.
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
import { computed } from '@ember/object';
export default RESTAdapter.extend({
headers: computed('session.authToken', function() {
return {
'API_KEY': this.get('session.authToken'),
'ANOTHER_HEADER': 'Some header value'
};
})
});
```
In some cases, your dynamic headers may require data from some
object outside of Ember's observer system (for example
`document.cookie`). You can use the
[volatile](/api/classes/Ember.ComputedProperty.html#method_volatile)
function to set the property into a non-cached mode causing the headers to
be recomputed with every request.
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
import { get } from '@ember/object';
import { computed } from '@ember/object';
export default RESTAdapter.extend({
headers: computed(function() {
return {
'API_KEY': get(document.cookie.match(/apiKey\=([^;]*)/), '1'),
'ANOTHER_HEADER': 'Some header value'
};
}).volatile()
});
```
@class RESTAdapter
@constructor
@extends Adapter
@uses BuildURLMixin
*/
const RESTAdapter = _adapter.default.extend(_adapter.BuildURLMixin, {
defaultSerializer: '-rest',
fastboot: Ember.computed(function () {
return Ember.getOwner(this).lookup('service:fastboot');
}),
useFetch: Ember.computed(function () {
let ENV = Ember.getOwner(this).resolveRegistration('config:environment'); // TODO: https://github.com/emberjs/data/issues/6093
let jQueryIntegrationDisabled = ENV && ENV.EmberENV && ENV.EmberENV._JQUERY_INTEGRATION === false;
if (jQueryIntegrationDisabled) {
return true;
} else if (hasNajax || hasJQuery) {
return false;
} else {
return true;
}
}),
/**
By default, the RESTAdapter will send the query params sorted alphabetically to the
server.
For example:
```js
store.query('posts', { sort: 'price', category: 'pets' });
```
will generate a requests like this `/posts?category=pets&sort=price`, even if the
parameters were specified in a different order.
That way the generated URL will be deterministic and that simplifies caching mechanisms
in the backend.
Setting `sortQueryParams` to a falsey value will respect the original order.
In case you want to sort the query parameters with a different criteria, set
`sortQueryParams` to your custom sort function.
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
sortQueryParams(params) {
let sortedKeys = Object.keys(params).sort().reverse();
let len = sortedKeys.length, newParams = {};
for (let i = 0; i < len; i++) {
newParams[sortedKeys[i]] = params[sortedKeys[i]];
}
return newParams;
}
});
```
@method sortQueryParams
@param {Object} obj
@return {Object}
*/
sortQueryParams(obj) {
let keys = Object.keys(obj);
let len = keys.length;
if (len < 2) {
return obj;
}
let newQueryParams = {};
let sortedKeys = keys.sort();
for (let i = 0; i < len; i++) {
newQueryParams[sortedKeys[i]] = obj[sortedKeys[i]];
}
return newQueryParams;
},
/**
By default the RESTAdapter will send each find request coming from a `store.find`
or from accessing a relationship separately to the server. If your server supports passing
ids as a query string, you can set coalesceFindRequests to true to coalesce all find requests
within a single runloop.
For example, if you have an initial payload of:
```javascript
{
post: {
id: 1,
comments: [1, 2]
}
}
```
By default calling `post.get('comments')` will trigger the following requests(assuming the
comments haven't been loaded before):
```
GET /comments/1
GET /comments/2
```
If you set coalesceFindRequests to `true` it will instead trigger the following request:
```
GET /comments?ids[]=1&ids[]=2
```
Setting coalesceFindRequests to `true` also works for `store.find` requests and `belongsTo`
relationships accessed within the same runloop. If you set `coalesceFindRequests: true`
```javascript
store.findRecord('comment', 1);
store.findRecord('comment', 2);
```
will also send a request to: `GET /comments?ids[]=1&ids[]=2`
Note: Requests coalescing rely on URL building strategy. So if you override `buildURL` in your app
`groupRecordsForFindMany` more likely should be overridden as well in order for coalescing to work.
@property coalesceFindRequests
@type {boolean}
*/
coalesceFindRequests: false,
/**
Endpoint paths can be prefixed with a `namespace` by setting the namespace
property on the adapter:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
namespace: 'api/1'
});
```
Requests for the `Post` model would now target `/api/1/post/`.
@property namespace
@type {String}
*/
/**
An adapter can target other hosts by setting the `host` property.
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
host: 'https://api.example.com'
});
```
Requests for the `Post` model would now target `https://api.example.com/post/`.
@property host
@type {String}
*/
/**
Some APIs require HTTP headers, e.g. to provide an API
key. Arbitrary headers can be set as key/value pairs on the
`RESTAdapter`'s `headers` object and Ember Data will send them
along with each ajax request. For dynamic headers see [headers
customization](/api/data/classes/DS.RESTAdapter.html).
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
import { computed } from '@ember/object';
export default RESTAdapter.extend({
headers: computed(function() {
return {
'API_KEY': 'secret key',
'ANOTHER_HEADER': 'Some header value'
};
})
});
```
@property headers
@type {Object}
*/
/**
Called by the store in order to fetch the JSON for a given
type and ID.
The `findRecord` method makes an Ajax request to a URL computed by
`buildURL`, and returns a promise for the resulting payload.
This method performs an HTTP `GET` request with the id provided as part of the query string.
@since 1.13.0
@method findRecord
@param {DS.Store} store
@param {DS.Model} type
@param {String} id
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
findRecord(store, type, id, snapshot) {
let url = this.buildURL(type.modelName, id, snapshot, 'findRecord');
let query = this.buildQuery(snapshot);
return this.ajax(url, 'GET', {
data: query
});
},
/**
Called by the store in order to fetch a JSON array for all
of the records for a given type.
The `findAll` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a
promise for the resulting payload.
@method findAll
@param {DS.Store} store
@param {DS.Model} type
@param {undefined} neverSet a value is never provided to this argument
@param {DS.SnapshotRecordArray} snapshotRecordArray
@return {Promise} promise
*/
findAll(store, type, sinceToken, snapshotRecordArray) {
let query = this.buildQuery(snapshotRecordArray);
let url = this.buildURL(type.modelName, null, snapshotRecordArray, 'findAll');
if (sinceToken) {
query.since = sinceToken;
}
return this.ajax(url, 'GET', {
data: query
});
},
/**
Called by the store in order to fetch a JSON array for
the records that match a particular query.
The `query` method makes an Ajax (HTTP GET) request to a URL
computed by `buildURL`, and returns a promise for the resulting
payload.
The `query` argument is a simple JavaScript object that will be passed directly
to the server as parameters.
@method query
@param {DS.Store} store
@param {DS.Model} type
@param {Object} query
@return {Promise} promise
*/
query(store, type, query) {
let url = this.buildURL(type.modelName, null, null, 'query', query);
if (this.sortQueryParams) {
query = this.sortQueryParams(query);
}
return this.ajax(url, 'GET', {
data: query
});
},
/**
Called by the store in order to fetch a JSON object for
the record that matches a particular query.
The `queryRecord` method makes an Ajax (HTTP GET) request to a URL
computed by `buildURL`, and returns a promise for the resulting
payload.
The `query` argument is a simple JavaScript object that will be passed directly
to the server as parameters.
@since 1.13.0
@method queryRecord
@param {DS.Store} store
@param {DS.Model} type
@param {Object} query
@return {Promise} promise
*/
queryRecord(store, type, query) {
let url = this.buildURL(type.modelName, null, null, 'queryRecord', query);
if (this.sortQueryParams) {
query = this.sortQueryParams(query);
}
return this.ajax(url, 'GET', {
data: query
});
},
/**
Called by the store in order to fetch several records together if `coalesceFindRequests` is true
For example, if the original payload looks like:
```js
{
"id": 1,
"title": "Rails is omakase",
"comments": [ 1, 2, 3 ]
}
```
The IDs will be passed as a URL-encoded Array of IDs, in this form:
```
ids[]=1&ids[]=2&ids[]=3
```
Many servers, such as Rails and PHP, will automatically convert this URL-encoded array
into an Array for you on the server-side. If you want to encode the
IDs, differently, just override this (one-line) method.
The `findMany` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a
promise for the resulting payload.
@method findMany
@param {DS.Store} store
@param {DS.Model} type
@param {Array} ids
@param {Array} snapshots
@return {Promise} promise
*/
findMany(store, type, ids, snapshots) {
let url = this.buildURL(type.modelName, ids, snapshots, 'findMany');
return this.ajax(url, 'GET', {
data: {
ids: ids
}
});
},
/**
Called by the store in order to fetch a JSON array for
the unloaded records in a has-many relationship that were originally
specified as a URL (inside of `links`).
For example, if your original payload looks like this:
```js
{
"post": {
"id": 1,
"title": "Rails is omakase",
"links": { "comments": "/posts/1/comments" }
}
}
```
This method will be called with the parent record and `/posts/1/comments`.
The `findHasMany` method will make an Ajax (HTTP GET) request to the originally specified URL.
The format of your `links` value will influence the final request URL via the `urlPrefix` method:
* Links beginning with `//`, `http://`, `https://`, will be used as is, with no further manipulation.
* Links beginning with a single `/` will have the current adapter's `host` value prepended to it.
* Links with no beginning `/` will have a parentURL prepended to it, via the current adapter's `buildURL`.
@method findHasMany
@param {DS.Store} store
@param {DS.Snapshot} snapshot
@param {String} url
@param {Object} relationship meta object describing the relationship
@return {Promise} promise
*/
findHasMany(store, snapshot, url, relationship) {
let id = snapshot.id;
let type = snapshot.modelName;
url = this.urlPrefix(url, this.buildURL(type, id, snapshot, 'findHasMany'));
return this.ajax(url, 'GET');
},
/**
Called by the store in order to fetch the JSON for the unloaded record in a
belongs-to relationship that was originally specified as a URL (inside of
`links`).
For example, if your original payload looks like this:
```js
{
"person": {
"id": 1,
"name": "Tom Dale",
"links": { "group": "/people/1/group" }
}
}
```
This method will be called with the parent record and `/people/1/group`.
The `findBelongsTo` method will make an Ajax (HTTP GET) request to the originally specified URL.
The format of your `links` value will influence the final request URL via the `urlPrefix` method:
* Links beginning with `//`, `http://`, `https://`, will be used as is, with no further manipulation.
* Links beginning with a single `/` will have the current adapter's `host` value prepended to it.
* Links with no beginning `/` will have a parentURL prepended to it, via the current adapter's `buildURL`.
@method findBelongsTo
@param {DS.Store} store
@param {DS.Snapshot} snapshot
@param {String} url
@param {Object} relationship meta object describing the relationship
@return {Promise} promise
*/
findBelongsTo(store, snapshot, url, relationship) {
let id = snapshot.id;
let type = snapshot.modelName;
url = this.urlPrefix(url, this.buildURL(type, id, snapshot, 'findBelongsTo'));
return this.ajax(url, 'GET');
},
/**
Called by the store when a newly created record is
saved via the `save` method on a model record instance.
The `createRecord` method serializes the record and makes an Ajax (HTTP POST) request
to a URL computed by `buildURL`.
See `serialize` for information on how to customize the serialized form
of a record.
@method createRecord
@param {DS.Store} store
@param {DS.Model} type
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
createRecord(store, type, snapshot) {
let url = this.buildURL(type.modelName, null, snapshot, 'createRecord');
const data = (0, _private.serializeIntoHash)(store, type, snapshot);
return this.ajax(url, 'POST', {
data
});
},
/**
Called by the store when an existing record is saved
via the `save` method on a model record instance.
The `updateRecord` method serializes the record and makes an Ajax (HTTP PUT) request
to a URL computed by `buildURL`.
See `serialize` for information on how to customize the serialized form
of a record.
@method updateRecord
@param {DS.Store} store
@param {DS.Model} type
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
updateRecord(store, type, snapshot) {
const data = (0, _private.serializeIntoHash)(store, type, snapshot, {});
let id = snapshot.id;
let url = this.buildURL(type.modelName, id, snapshot, 'updateRecord');
return this.ajax(url, 'PUT', {
data
});
},
/**
Called by the store when a record is deleted.
The `deleteRecord` method makes an Ajax (HTTP DELETE) request to a URL computed by `buildURL`.
@method deleteRecord
@param {DS.Store} store
@param {DS.Model} type
@param {DS.Snapshot} snapshot
@return {Promise} promise
*/
deleteRecord(store, type, snapshot) {
let id = snapshot.id;
return this.ajax(this.buildURL(type.modelName, id, snapshot, 'deleteRecord'), 'DELETE');
},
_stripIDFromURL(store, snapshot) {
let url = this.buildURL(snapshot.modelName, snapshot.id, snapshot);
let expandedURL = url.split('/'); // Case when the url is of the format ...something/:id
// We are decodeURIComponent-ing the lastSegment because if it represents
// the id, it has been encodeURIComponent-ified within `buildURL`. If we
// don't do this, then records with id having special characters are not
// coalesced correctly (see GH #4190 for the reported bug)
let lastSegment = expandedURL[expandedURL.length - 1];
let id = snapshot.id;
if (decodeURIComponent(lastSegment) === id) {
expandedURL[expandedURL.length - 1] = '';
} else if (endsWith(lastSegment, '?id=' + id)) {
//Case when the url is of the format ...something?id=:id
expandedURL[expandedURL.length - 1] = lastSegment.substring(0, lastSegment.length - id.length - 1);
}
return expandedURL.join('/');
},
// http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
maxURLLength: 2048,
/**
Organize records into groups, each of which is to be passed to separate
calls to `findMany`.
This implementation groups together records that have the same base URL but
differing ids. For example `/comments/1` and `/comments/2` will be grouped together
because we know findMany can coalesce them together as `/comments?ids[]=1&ids[]=2`
It also supports urls where ids are passed as a query param, such as `/comments?id=1`
but not those where there is more than 1 query param such as `/comments?id=2&name=David`
Currently only the query param of `id` is supported. If you need to support others, please
override this or the `_stripIDFromURL` method.
It does not group records that have differing base urls, such as for example: `/posts/1/comments/2`
and `/posts/2/comments/3`
@method groupRecordsForFindMany
@param {DS.Store} store
@param {Array} snapshots
@return {Array} an array of arrays of records, each of which is to be
loaded separately by `findMany`.
*/
groupRecordsForFindMany(store, snapshots) {
let groups = new Map();
let adapter = this;
let maxURLLength = this.maxURLLength;
snapshots.forEach(snapshot => {
let baseUrl = adapter._stripIDFromURL(store, snapshot);
if (!groups.has(baseUrl)) {
groups.set(baseUrl, []);
}
groups.get(baseUrl).push(snapshot);
});
function splitGroupToFitInUrl(group, maxURLLength, paramNameLength) {
let idsSize = 0;
let baseUrl = adapter._stripIDFromURL(store, group[0]);
let splitGroups = [[]];
group.forEach(snapshot => {
let additionalLength = encodeURIComponent(snapshot.id).length + paramNameLength;
if (baseUrl.length + idsSize + additionalLength >= maxURLLength) {
idsSize = 0;
splitGroups.push([]);
}
idsSize += additionalLength;
let lastGroupIndex = splitGroups.length - 1;
splitGroups[lastGroupIndex].push(snapshot);
});
return splitGroups;
}
let groupsArray = [];
groups.forEach((group, key) => {
let paramNameLength = '&ids%5B%5D='.length;
let splitGroups = splitGroupToFitInUrl(group, maxURLLength, paramNameLength);
splitGroups.forEach(splitGroup => groupsArray.push(splitGroup));
});
return groupsArray;
},
/**
Takes an ajax response, and returns the json payload or an error.
By default this hook just returns the json payload passed to it.
You might want to override it in two cases:
1. Your API might return useful results in the response headers.
Response headers are passed in as the second argument.
2. Your API might return errors as successful responses with status code
200 and an Errors text or object. You can return a `InvalidError` or a
`AdapterError` (or a sub class) from this hook and it will automatically
reject the promise and put your record into the invalid or error state.
Returning a `InvalidError` from this method will cause the
record to transition into the `invalid` state and make the
`errors` object available on the record. When returning an
`InvalidError` the store will attempt to normalize the error data
returned from the server using the serializer's `extractErrors`
method.
@since 1.13.0
@method handleResponse
@param {Number} status
@param {Object} headers
@param {Object} payload
@param {Object} requestData - the original request information
@return {Object | DS.AdapterError} response
*/
handleResponse(status, headers, payload, requestData) {
if (this.isSuccess(status, headers, payload)) {
return payload;
} else if (this.isInvalid(status, headers, payload)) {
return new _error.InvalidError(payload.errors);
}
let errors = this.normalizeErrorResponse(status, headers, payload);
let detailedMessage = this.generatedDetailedMessage(status, headers, payload, requestData);
switch (status) {
case 401:
return new _error.UnauthorizedError(errors, detailedMessage);
case 403:
return new _error.ForbiddenError(errors, detailedMessage);
case 404:
return new _error.NotFoundError(errors, detailedMessage);
case 409:
return new _error.ConflictError(errors, detailedMessage);
default:
if (status >= 500) {
return new _error.ServerError(errors, detailedMessage);
}
}
return new _error.default(errors, detailedMessage);
},
/**
Default `handleResponse` implementation uses this hook to decide if the
response is a success.
@since 1.13.0
@method isSuccess
@param {Number} status
@param {Object} headers
@param {Object} payload
@return {Boolean}
*/
isSuccess(status, headers, payload) {
return status >= 200 && status < 300 || status === 304;
},
/**
Default `handleResponse` implementation uses this hook to decide if the
response is an invalid error.
@since 1.13.0
@method isInvalid
@param {Number} status
@param {Object} headers
@param {Object} payload
@return {Boolean}
*/
isInvalid(status, headers, payload) {
return status === 422;
},
/**
Takes a URL, an HTTP method and a hash of data, and makes an
HTTP request.
When the server responds with a payload, Ember Data will call into `extractSingle`
or `extractArray` (depending on whether the original query was for one record or
many records).
By default, `ajax` method has the following behavior:
* It sets the response `dataType` to `"json"`
* If the HTTP method is not `"GET"`, it sets the `Content-Type` to be
`application/json; charset=utf-8`
* If the HTTP method is not `"GET"`, it stringifies the data passed in. The
data is the serialized record in the case of a save.
* Registers success and failure handlers.
@method ajax
@private
@param {String} url
@param {String} type The request type GET, POST, PUT, DELETE etc.
@param {Object} options
@return {Promise} promise
*/
ajax(url, type, options) {
let adapter = this;
let useFetch = Ember.get(this, 'useFetch');
let requestData = {
url: url,
method: type
};
let hash = adapter.ajaxOptions(url, type, options);
if (useFetch) {
return this._fetchRequest(hash).then(response => {
return Ember.RSVP.hash({
response,
payload: (0, _private.determineBodyPromise)(response, requestData)
});
}).then(({
response,
payload
}) => {
if (response.ok) {
return fetchSuccessHandler(adapter, payload, response, requestData);
} else {
throw fetchErrorHandler(adapter, payload, response, null, requestData);
}
});
}
return new Promise(function (resolve, reject) {
hash.success = function (payload, textStatus, jqXHR) {
let response = ajaxSuccessHandler(adapter, payload, jqXHR, requestData);
Ember.run.join(null, resolve, response);
};
hash.error = function (jqXHR, textStatus, errorThrown) {
let error = ajaxErrorHandler(adapter, jqXHR, errorThrown, requestData);
Ember.run.join(null, reject, error);
};
adapter._ajax(hash);
}, 'DS: RESTAdapter#ajax ' + type + ' to ' + url);
},
/**
@method _ajaxRequest
@private
@param {Object} options jQuery ajax options to be used for the ajax request
*/
_ajaxRequest(options) {
jQuery.ajax(options);
},
/**
@method _najaxRequest
@private
@param {Object} options jQuery ajax options to be used for the najax request
*/
_najaxRequest(options) {
if (hasNajax) {
najax(options);
} else {
throw new Error('najax does not seem to be defined in your app. Did you override it via `addOrOverrideSandboxGlobals` in the fastboot server?');
}
},
_fetchRequest(options) {
let fetchFunction = (0, _private.fetch)();
if (fetchFunction) {
return fetchFunction(options.url, options);
} else {
throw new Error('cannot find the `fetch` module or the `fetch` global. Did you mean to install the `ember-fetch` addon?');
}
},
_ajax(options) {
if (Ember.get(this, 'useFetch')) {
this._fetchRequest(options);
} else if (Ember.get(this, 'fastboot.isFastBoot')) {
(window.$ && window.$.ajax) ? this._ajaxRequest(options) : this._najaxRequest(options);
} else {
this._ajaxRequest(options);
}
},
/**
@method ajaxOptions
@private
@param {String} url
@param {String} type The request type GET, POST, PUT, DELETE etc.
@param {Object} options
@return {Object}
*/
ajaxOptions(url, method, options) {
options = Ember.assign({
url,
method,
type: method
}, options);
let headers = Ember.get(this, 'headers');
if (headers !== undefined) {
options.headers = Ember.assign({}, options.headers, headers);
} else if (!options.headers) {
options.headers = {};
}
if (options.data && options.type !== 'GET') {
let contentType = options.contentType || 'application/json; charset=utf-8';
options.headers['content-type'] = contentType;
}
if (Ember.get(this, 'useFetch')) {
options = fetchOptions(options, this);
} else {
options = ajaxOptions(options, this);
}
options.url = this._ajaxURL(options.url);
return options;
},
_ajaxURL(url) {
if (Ember.get(this, 'fastboot.isFastBoot')) {
let httpRegex = /^https?:\/\//;
let protocolRelativeRegex = /^\/\//;
let protocol = Ember.get(this, 'fastboot.request.protocol');
let host = Ember.get(this, 'fastboot.request.host');
if (protocolRelativeRegex.test(url)) {
return "".concat(protocol).concat(url);
} else if (!httpRegex.test(url)) {
try {
return "".concat(protocol, "//").concat(host).concat(url);
} catch (fbError) {
throw new Error('You are using Ember Data with no host defined in your adapter. This will attempt to use the host of the FastBoot request, which is not configured for the current host of this request. Please set the hostWhitelist property for in your environment.js. FastBoot Error: ' + fbError.message);
}
}
}
return url;
},
/**
@method parseErrorResponse
@private
@param {String} responseText
@return {Object}
*/
parseErrorResponse(responseText) {
let json = responseText;
try {
json = JSON.parse(responseText);
} catch (e) {// ignored
}
return json;
},
/**
@method normalizeErrorResponse
@private
@param {Number} status
@param {Object} headers
@param {Object} payload
@return {Array} errors payload
*/
normalizeErrorResponse(status, headers, payload) {
if (payload && typeof payload === 'object' && payload.errors) {
return payload.errors;
} else {
return [{
status: "".concat(status),
title: 'The backend responded with an error',
detail: "".concat(payload)
}];
}
},
/**
Generates a detailed ("friendly") error message, with plenty
of information for debugging (good luck!)
@method generatedDetailedMessage
@private
@param {Number} status
@param {Object} headers
@param {Object} payload
@param {Object} requestData
@return {String} detailed error message
*/
generatedDetailedMessage: function (status, headers, payload, requestData) {
let shortenedPayload;
let payloadContentType = headers['content-type'] || 'Empty Content-Type';
if (payloadContentType === 'text/html' && payload.length > 250) {
shortenedPayload = '[Omitted Lengthy HTML]';
} else {
shortenedPayload = payload;
}
let requestDescription = requestData.method + ' ' + requestData.url;
let payloadDescription = 'Payload (' + payloadContentType + ')';
return ['Ember Data Request ' + requestDescription + ' returned a ' + status, payloadDescription, shortenedPayload].join('\n');
},
// @since 2.5.0
buildQuery(snapshot) {
let query = {};
if (snapshot) {
let {
include
} = snapshot;
if (include) {
query.include = include;
}
}
return query;
}
});
function ajaxSuccess(adapter, payload, requestData, responseData) {
let response;
try {
response = adapter.handleResponse(responseData.status, responseData.headers, payload, requestData);
} catch (error) {
return Promise.reject(error);
}
if (response && response.isAdapterError) {
return Promise.reject(response);
} else {
return response;
}
}
function ajaxError(adapter, payload, requestData, responseData) {
if (true
/* DEBUG */
) {
let message = "The server returned an empty string for ".concat(requestData.method, " ").concat(requestData.url, ", which cannot be parsed into a valid JSON. Return either null or {}.");
let validJSONString = !(responseData.textStatus === 'parsererror' && payload === '');
(true && Ember.warn(message, validJSONString, {
id: 'ds.adapter.returned-empty-string-as-JSON'
}));
}
let error;
if (responseData.errorThrown instanceof Error) {
error = responseData.errorThrown;
} else if (responseData.textStatus === 'timeout') {
error = new _error.TimeoutError();
} else if (responseData.textStatus === 'abort' || responseData.status === 0) {
error = handleAbort(requestData, responseData);
} else {
try {
error = adapter.handleResponse(responseData.status, responseData.headers, payload || responseData.errorThrown, requestData);
} catch (e) {
error = e;
}
}
return error;
} // Adapter abort error to include any relevent info, e.g. request/response:
function handleAbort(requestData, responseData) {
let {
method,
url,
errorThrown
} = requestData;
let {
status
} = responseData;
let msg = "Request failed: ".concat(method, " ").concat(url, " ").concat(errorThrown || '');
let errors = [{
title: 'Adapter Error',
detail: msg.trim(),
status
}];
return new _error.AbortError(errors);
} //From http://stackoverflow.com/questions/280634/endswith-in-javascript
function endsWith(string, suffix) {
if (typeof String.prototype.endsWith !== 'function') {
return string.indexOf(suffix, string.length - suffix.length) !== -1;
} else {
return string.endsWith(suffix);
}
}
function fetchSuccessHandler(adapter, payload, response, requestData) {
let responseData = fetchResponseData(response);
return ajaxSuccess(adapter, payload, requestData, responseData);
}
function fetchErrorHandler(adapter, payload, response, errorThrown, requestData) {
let responseData = fetchResponseData(response);
responseData.errorThrown = errorThrown;
return ajaxError(adapter, payload, requestData, responseData);
}
function ajaxSuccessHandler(adapter, payload, jqXHR, requestData) {
let responseData = ajaxResponseData(jqXHR);
return ajaxSuccess(adapter, payload, requestData, responseData);
}
function ajaxErrorHandler(adapter, jqXHR, errorThrown, requestData) {
let responseData = ajaxResponseData(jqXHR);
responseData.errorThrown = errorThrown;
let payload = adapter.parseErrorResponse(jqXHR.responseText);
return ajaxError(adapter, payload, requestData, responseData);
}
function fetchResponseData(response) {
return {
status: response.status,
textStatus: response.textStatus,
headers: headersToObject(response.headers)
};
}
function ajaxResponseData(jqXHR) {
return {
status: jqXHR.status,
textStatus: jqXHR.statusText,
headers: (0, _private.parseResponseHeaders)(jqXHR.getAllResponseHeaders())
};
}
function headersToObject(headers) {
let headersObject = {};
if (headers) {
headers.forEach((value, key) => headersObject[key] = value);
}
return headersObject;
}
/**
* Helper function that translates the options passed to `jQuery.ajax` into a format that `fetch` expects.
* @param {Object} _options
* @param {DS.Adapter} adapter
* @returns {Object}
*/
function fetchOptions(options, adapter) {
options.credentials = 'same-origin';
if (options.data) {
// GET and HEAD requests can't have a `body`
if (options.method === 'GET' || options.method === 'HEAD') {
// If no options are passed, Ember Data sets `data` to an empty object, which we test for.
if (Object.keys(options.data).length) {
// Test if there are already query params in the url (mimics jQuey.ajax).
const queryParamDelimiter = options.url.indexOf('?') > -1 ? '&' : '?';
options.url += "".concat(queryParamDelimiter).concat((0, _private.serializeQueryParams)(options.data));
}
} else {
// NOTE: a request's body cannot be an object, so we stringify it if it is.
// JSON.stringify removes keys with values of `undefined` (mimics jQuery.ajax).
options.body = JSON.stringify(options.data);
}
}
return options;
}
function ajaxOptions(options, adapter) {
options.dataType = 'json';
options.context = adapter;
if (options.data && options.type !== 'GET') {
options.data = JSON.stringify(options.data);
options.contentType = 'application/json; charset=utf-8';
}
options.beforeSend = function (xhr) {
Object.keys(options.headers).forEach(key => xhr.setRequestHeader(key, options.headers[key]));
};
return options;
}
var _default = RESTAdapter;
_exports.default = _default;
});
define("@ember-data/adapter/-private/build-url-mixin", ["exports", "ember-inflector"], function (_exports, _emberInflector) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/adapter
*/
/**
WARNING: This interface is likely to change in order to accommodate [RFC: Ember Data url templates](https://github.com/emberjs/rfcs/pull/4)
## Using BuildURLMixin
To use URL building, include the mixin when extending an adapter, and call `buildURL` where needed.
The default behaviour is designed for RESTAdapter.
### Example
```javascript
import Adapter, { BuildURLMixin } from '@ember-data/adapter';
export default Adapter.extend(BuildURLMixin, {
findRecord: function(store, type, id, snapshot) {
var url = this.buildURL(type.modelName, id, snapshot, 'findRecord');
return this.ajax(url, 'GET');
}
});
```
### Attributes
The `host` and `namespace` attributes will be used if defined, and are optional.
@class BuildURLMixin
*/
var _default = Ember.Mixin.create({
/**
Builds a URL for a given type and optional ID.
By default, it pluralizes the type's name (for example, 'post'
becomes 'posts' and 'person' becomes 'people'). To override the
pluralization see [pathForType](#method_pathForType).
If an ID is specified, it adds the ID to the path generated
for the type, separated by a `/`.
When called by `RESTAdapter.findMany()` the `id` and `snapshot` parameters
will be arrays of ids and snapshots.
@method buildURL
@param {String} modelName
@param {(String|Array|Object)} id single id or array of ids or query
@param {(DS.Snapshot|Array)} snapshot single snapshot or array of snapshots
@param {String} requestType
@param {Object} query object of query parameters to send for query requests.
@return {String} url
*/
buildURL(modelName, id, snapshot, requestType, query) {
switch (requestType) {
case 'findRecord':
return this.urlForFindRecord(id, modelName, snapshot);
case 'findAll':
return this.urlForFindAll(modelName, snapshot);
case 'query':
return this.urlForQuery(query, modelName);
case 'queryRecord':
return this.urlForQueryRecord(query, modelName);
case 'findMany':
return this.urlForFindMany(id, modelName, snapshot);
case 'findHasMany':
return this.urlForFindHasMany(id, modelName, snapshot);
case 'findBelongsTo':
return this.urlForFindBelongsTo(id, modelName, snapshot);
case 'createRecord':
return this.urlForCreateRecord(modelName, snapshot);
case 'updateRecord':
return this.urlForUpdateRecord(id, modelName, snapshot);
case 'deleteRecord':
return this.urlForDeleteRecord(id, modelName, snapshot);
default:
return this._buildURL(modelName, id);
}
},
/**
@method _buildURL
@private
@param {String} modelName
@param {String} id
@return {String} url
*/
_buildURL(modelName, id) {
let path;
let url = [];
let host = Ember.get(this, 'host');
let prefix = this.urlPrefix();
if (modelName) {
path = this.pathForType(modelName);
if (path) {
url.push(path);
}
}
if (id) {
url.push(encodeURIComponent(id));
}
if (prefix) {
url.unshift(prefix);
}
url = url.join('/');
if (!host && url && url.charAt(0) !== '/') {
url = '/' + url;
}
return url;
},
/**
Builds a URL for a `store.findRecord(type, id)` call.
Example:
```app/adapters/user.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
urlForFindRecord(id, modelName, snapshot) {
let baseUrl = this.buildURL(modelName, id, snapshot);
return `${baseUrl}/users/${snapshot.adapterOptions.user_id}/playlists/${id}`;
}
});
```
@method urlForFindRecord
@param {String} id
@param {String} modelName
@param {DS.Snapshot} snapshot
@return {String} url
*/
urlForFindRecord(id, modelName, snapshot) {
return this._buildURL(modelName, id);
},
/**
Builds a URL for a `store.findAll(type)` call.
Example:
```app/adapters/comment.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
urlForFindAll(modelName, snapshot) {
return 'data/comments.json';
}
});
```
@method urlForFindAll
@param {String} modelName
@param {DS.SnapshotRecordArray} snapshot
@return {String} url
*/
urlForFindAll(modelName, snapshot) {
return this._buildURL(modelName);
},
/**
Builds a URL for a `store.query(type, query)` call.
Example:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
host: 'https://api.github.com',
urlForQuery (query, modelName) {
switch(modelName) {
case 'repo':
return `https://api.github.com/orgs/${query.orgId}/repos`;
default:
return this._super(...arguments);
}
}
});
```
@method urlForQuery
@param {Object} query
@param {String} modelName
@return {String} url
*/
urlForQuery(query, modelName) {
return this._buildURL(modelName);
},
/**
Builds a URL for a `store.queryRecord(type, query)` call.
Example:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
urlForQueryRecord({ slug }, modelName) {
let baseUrl = this.buildURL();
return `${baseUrl}/${encodeURIComponent(slug)}`;
}
});
```
@method urlForQueryRecord
@param {Object} query
@param {String} modelName
@return {String} url
*/
urlForQueryRecord(query, modelName) {
return this._buildURL(modelName);
},
/**
Builds a URL for coalescing multiple `store.findRecord(type, id)`
records into 1 request when the adapter's `coalesceFindRequests`
property is `true`.
Example:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
urlForFindMany(ids, modelName) {
let baseUrl = this.buildURL();
return `${baseUrl}/coalesce`;
}
});
```
@method urlForFindMany
@param {Array} ids
@param {String} modelName
@param {Array} snapshots
@return {String} url
*/
urlForFindMany(ids, modelName, snapshots) {
return this._buildURL(modelName);
},
/**
Builds a URL for fetching an async `hasMany` relationship when a URL
is not provided by the server.
Example:
```app/adapters/application.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
urlForFindHasMany(id, modelName, snapshot) {
let baseUrl = this.buildURL(modelName, id);
return `${baseUrl}/relationships`;
}
});
```
@method urlForFindHasMany
@param {String} id
@param {String} modelName
@param {DS.Snapshot} snapshot
@return {String} url
*/
urlForFindHasMany(id, modelName, snapshot) {
return this._buildURL(modelName, id);
},
/**
Builds a URL for fetching an async `belongsTo` relationship when a url
is not provided by the server.
Example:
```app/adapters/application.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
urlForFindBelongsTo(id, modelName, snapshot) {
let baseUrl = this.buildURL(modelName, id);
return `${baseUrl}/relationships`;
}
});
```
@method urlForFindBelongsTo
@param {String} id
@param {String} modelName
@param {DS.Snapshot} snapshot
@return {String} url
*/
urlForFindBelongsTo(id, modelName, snapshot) {
return this._buildURL(modelName, id);
},
/**
Builds a URL for a `record.save()` call when the record was created
locally using `store.createRecord()`.
Example:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
urlForCreateRecord(modelName, snapshot) {
return this._super(...arguments) + '/new';
}
});
```
@method urlForCreateRecord
@param {String} modelName
@param {DS.Snapshot} snapshot
@return {String} url
*/
urlForCreateRecord(modelName, snapshot) {
return this._buildURL(modelName);
},
/**
Builds a URL for a `record.save()` call when the record has been updated locally.
Example:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
urlForUpdateRecord(id, modelName, snapshot) {
return `/${id}/feed?access_token=${snapshot.adapterOptions.token}`;
}
});
```
@method urlForUpdateRecord
@param {String} id
@param {String} modelName
@param {DS.Snapshot} snapshot
@return {String} url
*/
urlForUpdateRecord(id, modelName, snapshot) {
return this._buildURL(modelName, id);
},
/**
Builds a URL for a `record.save()` call when the record has been deleted locally.
Example:
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default RESTAdapter.extend({
urlForDeleteRecord(id, modelName, snapshot) {
return this._super(...arguments) + '/destroy';
}
});
```
@method urlForDeleteRecord
@param {String} id
@param {String} modelName
@param {DS.Snapshot} snapshot
@return {String} url
*/
urlForDeleteRecord(id, modelName, snapshot) {
return this._buildURL(modelName, id);
},
/**
@method urlPrefix
@private
@param {String} path
@param {String} parentURL
@return {String} urlPrefix
*/
urlPrefix(path, parentURL) {
let host = Ember.get(this, 'host');
let namespace = Ember.get(this, 'namespace');
if (!host || host === '/') {
host = '';
}
if (path) {
// Protocol relative url
if (/^\/\//.test(path) || /http(s)?:\/\//.test(path)) {
// Do nothing, the full host is already included.
return path; // Absolute path
} else if (path.charAt(0) === '/') {
return "".concat(host).concat(path); // Relative path
} else {
return "".concat(parentURL, "/").concat(path);
}
} // No path provided
let url = [];
if (host) {
url.push(host);
}
if (namespace) {
url.push(namespace);
}
return url.join('/');
},
/**
Determines the pathname for a given type.
By default, it pluralizes the type's name (for example,
'post' becomes 'posts' and 'person' becomes 'people').
### Pathname customization
For example, if you have an object `LineItem` with an
endpoint of `/line_items/`.
```app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
import { decamelize } from '@ember/string';
import { pluralize } from 'ember-inflector';
export default RESTAdapter.extend({
pathForType: function(modelName) {
var decamelized = decamelize(modelName);
return pluralize(decamelized);
}
});
```
@method pathForType
@param {String} modelName
@return {String} path
**/
pathForType(modelName) {
let camelized = Ember.String.camelize(modelName);
return (0, _emberInflector.pluralize)(camelized);
}
});
_exports.default = _default;
});
define("@ember-data/adapter/-private/index", ["exports", "@ember-data/adapter/-private/utils/parse-response-headers", "@ember-data/adapter/-private/utils/determine-body-promise", "@ember-data/adapter/-private/utils/serialize-query-params", "@ember-data/adapter/-private/utils/fetch", "@ember-data/adapter/-private/build-url-mixin", "@ember-data/adapter/-private/utils/serialize-into-hash"], function (_exports, _parseResponseHeaders, _determineBodyPromise, _serializeQueryParams, _fetch, _buildUrlMixin, _serializeIntoHash) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "parseResponseHeaders", {
enumerable: true,
get: function () {
return _parseResponseHeaders.default;
}
});
Object.defineProperty(_exports, "determineBodyPromise", {
enumerable: true,
get: function () {
return _determineBodyPromise.determineBodyPromise;
}
});
Object.defineProperty(_exports, "serializeQueryParams", {
enumerable: true,
get: function () {
return _serializeQueryParams.serializeQueryParams;
}
});
Object.defineProperty(_exports, "fetch", {
enumerable: true,
get: function () {
return _fetch.default;
}
});
Object.defineProperty(_exports, "BuildURLMixin", {
enumerable: true,
get: function () {
return _buildUrlMixin.default;
}
});
Object.defineProperty(_exports, "serializeIntoHash", {
enumerable: true,
get: function () {
return _serializeIntoHash.default;
}
});
});
define("@ember-data/adapter/-private/utils/determine-body-promise", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.determineBodyPromise = determineBodyPromise;
/*
* Function that always attempts to parse the response as json, and if an error is thrown,
* returns `undefined` if the response is successful and has a status code of 204 (No Content),
* or 205 (Reset Content) or if the request method was 'HEAD', and the plain payload otherwise.
*/
function determineBodyPromise(response, requestData) {
return response.text().then(function (payload) {
let ret = payload;
try {
ret = JSON.parse(payload);
} catch (error) {
if (!(error instanceof SyntaxError)) {
throw error;
}
const status = response.status;
if (response.ok && (status === 204 || status === 205 || requestData.method === 'HEAD')) {
ret = undefined;
} else {
console.warn('This response was unable to be parsed as json.', payload);
}
}
return ret;
});
}
});
define("@ember-data/adapter/-private/utils/fetch", ["exports", "require"], function (_exports, _require) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = getFetchFunction;
let _fetch = null;
function getFetchFunction() {
if (_fetch !== null) {
return _fetch();
}
if ((0, _require.has)('fetch')) {
// use `fetch` module by default, this is commonly provided by ember-fetch
let fetchFn = (0, _require.default)("fetch").default;
_fetch = () => fetchFn;
} else if (typeof fetch === 'function') {
// fallback to using global fetch
_fetch = () => fetch;
} else {
throw new Error('cannot find the `fetch` module or the `fetch` global. Did you mean to install the `ember-fetch` addon?');
}
return _fetch();
}
});
define("@ember-data/adapter/-private/utils/parse-response-headers", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = parseResponseHeaders;
const newline = /\r?\n/;
function parseResponseHeaders(headersString) {
let headers = Object.create(null);
if (!headersString) {
return headers;
}
let headerPairs = headersString.split(newline);
for (let i = 0; i < headerPairs.length; i++) {
let header = headerPairs[i];
let j = 0;
let foundSep = false;
for (; j < header.length; j++) {
if (header.charCodeAt(j) === 58
/* ':' */
) {
foundSep = true;
break;
}
}
if (foundSep === false) {
continue;
}
let field = header.substring(0, j).trim();
let value = header.substring(j + 1, header.length).trim();
if (value) {
let lowerCasedField = field.toLowerCase();
headers[lowerCasedField] = value;
headers[field] = value;
}
}
return headers;
}
});
define("@ember-data/adapter/-private/utils/serialize-into-hash", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = serializeIntoHash;
function serializeIntoHash(store, modelClass, snapshot, options = {
includeId: true
}) {
const serializer = store.serializerFor(modelClass.modelName);
if (typeof serializer.serializeIntoHash === 'function') {
const data = {};
serializer.serializeIntoHash(data, modelClass, snapshot, options);
return data;
}
return serializer.serialize(snapshot, options);
}
});
define("@ember-data/adapter/-private/utils/serialize-query-params", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.serializeQueryParams = serializeQueryParams;
const RBRACKET = /\[\]$/;
function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
/*
* Helper function that turns the data/body of a request into a query param string.
* This is directly copied from jQuery.param.
*/
function serializeQueryParams(queryParamsObject) {
var s = [];
function buildParams(prefix, obj) {
var i, len, key;
if (prefix) {
if (Array.isArray(obj)) {
for (i = 0, len = obj.length; i < len; i++) {
if (RBRACKET.test(prefix)) {
add(s, prefix, obj[i]);
} else {
buildParams(prefix + '[' + (typeof obj[i] === 'object' ? i : '') + ']', obj[i]);
}
}
} else if (isPlainObject(obj)) {
for (key in obj) {
buildParams(prefix + '[' + key + ']', obj[key]);
}
} else {
add(s, prefix, obj);
}
} else if (Array.isArray(obj)) {
for (i = 0, len = obj.length; i < len; i++) {
add(s, obj[i].name, obj[i].value);
}
} else {
for (key in obj) {
buildParams(key, obj[key]);
}
}
return s;
}
return buildParams('', queryParamsObject).join('&').replace(/%20/g, '+');
}
/*
* Part of the `serializeQueryParams` helper function.
*/
function add(s, k, v) {
// Strip out keys with undefined value and replace null values with
// empty strings (mimics jQuery.ajax)
if (v === undefined) {
return;
} else if (v === null) {
v = '';
}
v = typeof v === 'function' ? v() : v;
s[s.length] = "".concat(encodeURIComponent(k), "=").concat(encodeURIComponent(v));
}
});
define("@ember-data/canary-features/default-features", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/canary-features
*/
/*
This list of features is used both at build time (by `@ember-data/-build-infra`)
and at runtime (by `@ember-data/canary-features`).
The valid values are:
- true - The feature is enabled at all times, and cannot be disabled.
- false - The feature is disabled at all times, and cannot be enabled.
- null - The feature is disabled by default, but can be enabled at runtime via `EmberDataENV`.
*/
var _default = {
SAMPLE_FEATURE_FLAG: null,
RECORD_DATA_ERRORS: null,
RECORD_DATA_STATE: null,
IDENTIFIERS: null,
REQUEST_SERVICE: null
};
_exports.default = _default;
});
define("@ember-data/canary-features/index", ["exports", "@ember-data/canary-features/default-features"], function (_exports, _defaultFeatures) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.IDENTIFIERS = _exports.REQUEST_SERVICE = _exports.RECORD_DATA_STATE = _exports.RECORD_DATA_ERRORS = _exports.SAMPLE_FEATURE_FLAG = _exports.FEATURES = void 0;
const ENV = typeof EmberDataENV === 'object' && EmberDataENV !== null ? EmberDataENV : {};
function featureValue(value) {
if (ENV.ENABLE_OPTIONAL_FEATURES && value === null) {
return true;
}
return value;
}
const FEATURES = Ember.assign({}, _defaultFeatures.default, ENV.FEATURES);
_exports.FEATURES = FEATURES;
const SAMPLE_FEATURE_FLAG = featureValue(FEATURES.SAMPLE_FEATURE_FLAG);
_exports.SAMPLE_FEATURE_FLAG = SAMPLE_FEATURE_FLAG;
const RECORD_DATA_ERRORS = featureValue(FEATURES.RECORD_DATA_ERRORS);
_exports.RECORD_DATA_ERRORS = RECORD_DATA_ERRORS;
const RECORD_DATA_STATE = featureValue(FEATURES.RECORD_DATA_STATE);
_exports.RECORD_DATA_STATE = RECORD_DATA_STATE;
const REQUEST_SERVICE = featureValue(FEATURES.REQUEST_SERVICE);
_exports.REQUEST_SERVICE = REQUEST_SERVICE;
const IDENTIFIERS = featureValue(FEATURES.IDENTIFIERS);
_exports.IDENTIFIERS = IDENTIFIERS;
});
define("@ember-data/model/index", ["exports", "@ember-data/model/-private", "@ember-data/store/-private"], function (_exports, _private, _private2) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "attr", {
enumerable: true,
get: function () {
return _private.attr;
}
});
Object.defineProperty(_exports, "belongsTo", {
enumerable: true,
get: function () {
return _private.belongsTo;
}
});
Object.defineProperty(_exports, "hasMany", {
enumerable: true,
get: function () {
return _private.hasMany;
}
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _private2.Model;
}
});
});
define("@ember-data/model/-private/attr", ["exports", "@ember-data/store/-private", "@ember-data/canary-features", "@ember-data/model/-private/util"], function (_exports, _private, _canaryFeatures, _util) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/model
*/
function getDefaultValue(record, options, key) {
if (typeof options.defaultValue === 'function') {
return options.defaultValue.apply(null, arguments);
} else {
let defaultValue = options.defaultValue;
(true && Ember.assert("Non primitive defaultValues are not supported because they are shared between all instances. If you would like to use a complex object as a default value please provide a function that returns the complex object.", typeof defaultValue !== 'object' || defaultValue === null));
return defaultValue;
}
}
function hasValue(internalModel, key) {
return (0, _private.recordDataFor)(internalModel).hasAttr(key);
}
/**
`attr` defines an attribute on a [Model](/api/data/classes/DS.Model.html).
By default, attributes are passed through as-is, however you can specify an
optional type to have the value automatically transformed.
Ember Data ships with four basic transform types: `string`, `number`,
`boolean` and `date`. You can define your own transforms by subclassing
[Transform](/api/data/classes/DS.Transform.html).
Note that you cannot use `attr` to define an attribute of `id`.
`attr` takes an optional hash as a second parameter, currently
supported options are:
- `defaultValue`: Pass a string or a function to be called to set the attribute
to a default value if none is supplied.
Example
```app/models/user.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
username: attr('string'),
email: attr('string'),
verified: attr('boolean', { defaultValue: false })
});
```
Default value can also be a function. This is useful it you want to return
a new object for each attribute.
```app/models/user.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
username: attr('string'),
email: attr('string'),
settings: attr({
defaultValue() {
return {};
}
})
});
```
The `options` hash is passed as second argument to a transforms'
`serialize` and `deserialize` method. This allows to configure a
transformation and adapt the corresponding value, based on the config:
```app/models/post.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
text: attr('text', {
uppercase: true
})
});
```
```app/transforms/text.js
import Transform from '@ember-data/serializer/transform';
export default Transform.extend({
serialize(value, options) {
if (options.uppercase) {
return value.toUpperCase();
}
return value;
},
deserialize(value) {
return value;
}
})
```
@method attr
@public
@static
@for @ember-data/model
@param {String|Object} type the attribute type
@param {Object} options a hash of options
@return {Attribute}
*/
function attr(type, options) {
if (typeof type === 'object') {
options = type;
type = undefined;
} else {
options = options || {};
}
let meta = {
type: type,
isAttribute: true,
kind: 'attribute',
options: options
};
return Ember.computed({
get(key) {
if (true
/* DEBUG */
) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
throw new Error("'".concat(key, "' is a reserved property name on instances of classes extending Model. Please choose a different property name for your attr on ").concat(this.constructor.toString()));
}
}
let internalModel = this._internalModel;
if (hasValue(internalModel, key)) {
return internalModel.getAttributeValue(key);
} else {
return getDefaultValue(this, options, key);
}
},
set(key, value) {
if (true
/* DEBUG */
) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
throw new Error("'".concat(key, "' is a reserved property name on instances of classes extending Model. Please choose a different property name for your attr on ").concat(this.constructor.toString()));
}
}
if (_canaryFeatures.RECORD_DATA_ERRORS) {
let oldValue = this._internalModel._recordData.getAttr(key);
if (oldValue !== value) {
let errors = this.get('errors');
if (errors.get(key)) {
errors.remove(key);
}
this._markInvalidRequestAsClean();
}
}
return this._internalModel.setDirtyAttribute(key, value);
}
}).meta(meta);
}
var _default = (0, _util.computedMacroWithOptionalParams)(attr);
_exports.default = _default;
});
define("@ember-data/model/-private/belongs-to", ["exports", "@ember-data/store", "@ember-data/model/-private/util"], function (_exports, _store, _util) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/model
*/
/**
`belongsTo` is used to define One-To-One and One-To-Many
relationships on a [Model](/api/data/classes/DS.Model.html).
`belongsTo` takes an optional hash as a second parameter, currently
supported options are:
- `async`: A boolean value used to explicitly declare this to be an async relationship. The default is true.
- `inverse`: A string used to identify the inverse property on a
related model in a One-To-Many relationship. See [Explicit Inverses](#explicit-inverses)
#### One-To-One
To declare a one-to-one relationship between two models, use
`belongsTo`:
```app/models/user.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
profile: belongsTo('profile')
});
```
```app/models/profile.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo('user')
});
```
#### One-To-Many
To declare a one-to-many relationship between two models, use
`belongsTo` in combination with `hasMany`, like this:
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany('comment')
});
```
```app/models/comment.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
post: belongsTo('post')
});
```
You can avoid passing a string as the first parameter. In that case Ember Data
will infer the type from the key name.
```app/models/comment.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
post: belongsTo()
});
```
will lookup for a Post type.
#### Sync relationships
Ember Data resolves sync relationships with the related resources
available in its local store, hence it is expected these resources
to be loaded before or along-side the primary resource.
```app/models/comment.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
post: belongsTo('post', {
async: false
})
});
```
In contrast to async relationship, accessing a sync relationship
will always return the record (Model instance) for the existing
local resource, or null. But it will error on access when
a related resource is known to exist and it has not been loaded.
```
let post = comment.get('post');
```
@method belongsTo
@public
@static
@for @ember-data/model
@param {String} modelName (optional) type of the relationship
@param {Object} options (optional) a hash of options
@return {Ember.computed} relationship
*/
function belongsTo(modelName, options) {
let opts, userEnteredModelName;
if (typeof modelName === 'object') {
opts = modelName;
userEnteredModelName = undefined;
} else {
opts = options;
userEnteredModelName = modelName;
}
if (typeof userEnteredModelName === 'string') {
userEnteredModelName = (0, _store.normalizeModelName)(userEnteredModelName);
}
(true && Ember.assert('The first argument to belongsTo must be a string representing a model type key, not an instance of ' + Ember.inspect(userEnteredModelName) + ". E.g., to define a relation to the Person model, use belongsTo('person')", typeof userEnteredModelName === 'string' || typeof userEnteredModelName === 'undefined'));
opts = opts || {};
let meta = {
type: userEnteredModelName,
isRelationship: true,
options: opts,
kind: 'belongsTo',
name: 'Belongs To',
key: null
};
return Ember.computed({
get(key) {
if (true
/* DEBUG */
) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
throw new Error("'".concat(key, "' is a reserved property name on instances of classes extending Model. Please choose a different property name for your belongsTo on ").concat(this.constructor.toString()));
}
if (opts.hasOwnProperty('serialize')) {
(true && Ember.warn("You provided a serialize option on the \"".concat(key, "\" property in the \"").concat(this._internalModel.modelName, "\" class, this belongs in the serializer. See Serializer and it's implementations https://emberjs.com/api/data/classes/DS.Serializer.html"), false, {
id: 'ds.model.serialize-option-in-belongs-to'
}));
}
if (opts.hasOwnProperty('embedded')) {
(true && Ember.warn("You provided an embedded option on the \"".concat(key, "\" property in the \"").concat(this._internalModel.modelName, "\" class, this belongs in the serializer. See EmbeddedRecordsMixin https://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html"), false, {
id: 'ds.model.embedded-option-in-belongs-to'
}));
}
}
return this._internalModel.getBelongsTo(key);
},
set(key, value) {
if (true
/* DEBUG */
) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
throw new Error("'".concat(key, "' is a reserved property name on instances of classes extending Model. Please choose a different property name for your belongsTo on ").concat(this.constructor.toString()));
}
}
this._internalModel.setDirtyBelongsTo(key, value);
return this._internalModel.getBelongsTo(key);
}
}).meta(meta);
}
var _default = (0, _util.computedMacroWithOptionalParams)(belongsTo);
_exports.default = _default;
});
define("@ember-data/model/-private/has-many", ["exports", "@ember-data/store", "@ember-data/model/-private/util"], function (_exports, _store, _util) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
`hasMany` is used to define One-To-Many and Many-To-Many
relationships on a [Model](/api/data/classes/DS.Model.html).
`hasMany` takes an optional hash as a second parameter, currently
supported options are:
- `async`: A boolean value used to explicitly declare this to be an async relationship. The default is true.
- `inverse`: A string used to identify the inverse property on a related model.
#### One-To-Many
To declare a one-to-many relationship between two models, use
`belongsTo` in combination with `hasMany`, like this:
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany('comment')
});
```
```app/models/comment.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
post: belongsTo('post')
});
```
#### Many-To-Many
To declare a many-to-many relationship between two models, use
`hasMany`:
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
tags: hasMany('tag')
});
```
```app/models/tag.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
posts: hasMany('post')
});
```
You can avoid passing a string as the first parameter. In that case Ember Data
will infer the type from the singularized key name.
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
tags: hasMany()
});
```
will lookup for a Tag type.
#### Explicit Inverses
Ember Data will do its best to discover which relationships map to
one another. In the one-to-many code above, for example, Ember Data
can figure out that changing the `comments` relationship should update
the `post` relationship on the inverse because post is the only
relationship to that model.
However, sometimes you may have multiple `belongsTo`/`hasMany` for the
same type. You can specify which property on the related model is
the inverse using `hasMany`'s `inverse` option:
```app/models/comment.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
onePost: belongsTo('post'),
twoPost: belongsTo('post'),
redPost: belongsTo('post'),
bluePost: belongsTo('post')
});
```
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany('comment', {
inverse: 'redPost'
})
});
```
You can also specify an inverse on a `belongsTo`, which works how
you'd expect.
#### Sync relationships
Ember Data resolves sync relationships with the related resources
available in its local store, hence it is expected these resources
to be loaded before or along-side the primary resource.
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany('comment', {
async: false
})
});
```
In contrast to async relationship, accessing a sync relationship
will always return a [ManyArray](/api/data/classes/DS.ManyArray.html) instance
containing the existing local resources. But it will error on access
when any of the known related resources have not been loaded.
```
post.get('comments').forEach((comment) => {
});
```
If you are using `links` with sync relationships, you have to use
`ref.reload` to fetch the resources.
@method hasMany
@public
@static
@for @ember-data/model
@param {String} type (optional) type of the relationship
@param {Object} options (optional) a hash of options
@return {Ember.computed} relationship
*/
function hasMany(type, options) {
if (typeof type === 'object') {
options = type;
type = undefined;
}
(true && Ember.assert("The first argument to hasMany must be a string representing a model type key, not an instance of ".concat(Ember.inspect(type), ". E.g., to define a relation to the Comment model, use hasMany('comment')"), typeof type === 'string' || typeof type === 'undefined'));
options = options || {};
if (typeof type === 'string') {
type = (0, _store.normalizeModelName)(type);
} // Metadata about relationships is stored on the meta of
// the relationship. This is used for introspection and
// serialization. Note that `key` is populated lazily
// the first time the CP is called.
let meta = {
type,
options,
isRelationship: true,
kind: 'hasMany',
name: 'Has Many',
key: null
};
return Ember.computed({
get(key) {
if (true
/* DEBUG */
) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
throw new Error("'".concat(key, "' is a reserved property name on instances of classes extending Model. Please choose a different property name for your hasMany on ").concat(this.constructor.toString()));
}
}
return this._internalModel.getHasMany(key);
},
set(key, records) {
if (true
/* DEBUG */
) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
throw new Error("'".concat(key, "' is a reserved property name on instances of classes extending Model. Please choose a different property name for your hasMany on ").concat(this.constructor.toString()));
}
}
let internalModel = this._internalModel;
internalModel.setDirtyHasMany(key, records);
return internalModel.getHasMany(key);
}
}).meta(meta);
}
var _default = (0, _util.computedMacroWithOptionalParams)(hasMany);
_exports.default = _default;
});
define("@ember-data/model/-private/index", ["exports", "@ember-data/model/-private/attr", "@ember-data/model/-private/belongs-to", "@ember-data/model/-private/has-many"], function (_exports, _attr, _belongsTo, _hasMany) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "attr", {
enumerable: true,
get: function () {
return _attr.default;
}
});
Object.defineProperty(_exports, "belongsTo", {
enumerable: true,
get: function () {
return _belongsTo.default;
}
});
Object.defineProperty(_exports, "hasMany", {
enumerable: true,
get: function () {
return _hasMany.default;
}
});
});
define("@ember-data/model/-private/util", ["exports", "ember-compatibility-helpers"], function (_exports, _emberCompatibilityHelpers) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.isElementDescriptor = isElementDescriptor;
_exports.computedMacroWithOptionalParams = computedMacroWithOptionalParams;
function isElementDescriptor(args) {
let [maybeTarget, maybeKey, maybeDesc] = args;
return (// Ensure we have the right number of args
args.length === 3 && ( // Make sure the target is a class or object (prototype)
typeof maybeTarget === 'function' || typeof maybeTarget === 'object' && maybeTarget !== null) && // Make sure the key is a string
typeof maybeKey === 'string' && ( // Make sure the descriptor is the right shape
typeof maybeDesc === 'object' && maybeDesc !== null && 'enumerable' in maybeDesc && 'configurable' in maybeDesc || // TS compatibility
maybeDesc === undefined)
);
}
function computedMacroWithOptionalParams(fn) {
if (true) {
return (...maybeDesc) => isElementDescriptor(maybeDesc) ? fn()(...maybeDesc) : fn(...maybeDesc);
} else {
return fn;
}
}
});
define("@ember-data/serializer/index", ["exports", "@ember-data/serializer/serializer"], function (_exports, _serializer) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _serializer.default;
}
});
});
define("@ember-data/serializer/json-api", ["exports", "ember-inflector", "@ember-data/serializer/json", "@ember-data/store"], function (_exports, _emberInflector, _json, _store) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
Ember Data 2.0 Serializer:
In Ember Data a Serializer is used to serialize and deserialize
records when they are transferred in and out of an external source.
This process involves normalizing property names, transforming
attribute values and serializing relationships.
`JSONAPISerializer` supports the http://jsonapi.org/ spec and is the
serializer recommended by Ember Data.
This serializer normalizes a JSON API payload that looks like:
```app/models/player.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default Model.extend({
name: attr('string'),
skill: attr('string'),
gamesPlayed: attr('number'),
club: belongsTo('club')
});
```
```app/models/club.js
import Model, { attr, hasMany } from '@ember-data/model';
export default Model.extend({
name: attr('string'),
location: attr('string'),
players: hasMany('player')
});
```
```js
{
"data": [
{
"attributes": {
"name": "Benfica",
"location": "Portugal"
},
"id": "1",
"relationships": {
"players": {
"data": [
{
"id": "3",
"type": "players"
}
]
}
},
"type": "clubs"
}
],
"included": [
{
"attributes": {
"name": "Eusebio Silva Ferreira",
"skill": "Rocket shot",
"games-played": 431
},
"id": "3",
"relationships": {
"club": {
"data": {
"id": "1",
"type": "clubs"
}
}
},
"type": "players"
}
]
}
```
to the format that the Ember Data store expects.
### Customizing meta
Since a JSON API Document can have meta defined in multiple locations you can
use the specific serializer hooks if you need to customize the meta.
One scenario would be to camelCase the meta keys of your payload. The example
below shows how this could be done using `normalizeArrayResponse` and
`extractRelationship`.
```app/serializers/application.js
export default JSONAPISerializer.extend({
normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) {
let normalizedDocument = this._super(...arguments);
// Customize document meta
normalizedDocument.meta = camelCaseKeys(normalizedDocument.meta);
return normalizedDocument;
},
extractRelationship(relationshipHash) {
let normalizedRelationship = this._super(...arguments);
// Customize relationship meta
normalizedRelationship.meta = camelCaseKeys(normalizedRelationship.meta);
return normalizedRelationship;
}
});
```
@since 1.13.0
@class JSONAPISerializer
@extends JSONSerializer
*/
const JSONAPISerializer = _json.default.extend({
/**
@method _normalizeDocumentHelper
@param {Object} documentHash
@return {Object}
@private
*/
_normalizeDocumentHelper(documentHash) {
if (Ember.typeOf(documentHash.data) === 'object') {
documentHash.data = this._normalizeResourceHelper(documentHash.data);
} else if (Array.isArray(documentHash.data)) {
let ret = new Array(documentHash.data.length);
for (let i = 0; i < documentHash.data.length; i++) {
let data = documentHash.data[i];
ret[i] = this._normalizeResourceHelper(data);
}
documentHash.data = ret;
}
if (Array.isArray(documentHash.included)) {
let ret = new Array();
for (let i = 0; i < documentHash.included.length; i++) {
let included = documentHash.included[i];
let normalized = this._normalizeResourceHelper(included);
if (normalized !== null) {
// can be null when unknown type is encountered
ret.push(normalized);
}
}
documentHash.included = ret;
}
return documentHash;
},
/**
@method _normalizeRelationshipDataHelper
@param {Object} relationshipDataHash
@return {Object}
@private
*/
_normalizeRelationshipDataHelper(relationshipDataHash) {
relationshipDataHash.type = this.modelNameFromPayloadKey(relationshipDataHash.type);
return relationshipDataHash;
},
/**
@method _normalizeResourceHelper
@param {Object} resourceHash
@return {Object}
@private
*/
_normalizeResourceHelper(resourceHash) {
(true && Ember.assert(this.warnMessageForUndefinedType(), !Ember.isNone(resourceHash.type), {
id: 'ds.serializer.type-is-undefined'
}));
let modelName, usedLookup;
modelName = this.modelNameFromPayloadKey(resourceHash.type);
usedLookup = 'modelNameFromPayloadKey';
if (!this.store._hasModelFor(modelName)) {
(true && Ember.warn(this.warnMessageNoModelForType(modelName, resourceHash.type, usedLookup), false, {
id: 'ds.serializer.model-for-type-missing'
}));
return null;
}
let modelClass = this.store.modelFor(modelName);
let serializer = this.store.serializerFor(modelName);
let {
data
} = serializer.normalize(modelClass, resourceHash);
return data;
},
/**
@method pushPayload
@param {DS.Store} store
@param {Object} payload
*/
pushPayload(store, payload) {
let normalizedPayload = this._normalizeDocumentHelper(payload);
store.push(normalizedPayload);
},
/**
@method _normalizeResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@param {Boolean} isSingle
@return {Object} JSON-API Document
@private
*/
_normalizeResponse(store, primaryModelClass, payload, id, requestType, isSingle) {
let normalizedPayload = this._normalizeDocumentHelper(payload);
return normalizedPayload;
},
normalizeQueryRecordResponse() {
let normalized = this._super(...arguments);
(true && Ember.assert('Expected the primary data returned by the serializer for a `queryRecord` response to be a single object but instead it was an array.', !Array.isArray(normalized.data), {
id: 'ds.serializer.json-api.queryRecord-array-response'
}));
return normalized;
},
extractAttributes(modelClass, resourceHash) {
let attributes = {};
if (resourceHash.attributes) {
modelClass.eachAttribute(key => {
let attributeKey = this.keyForAttribute(key, 'deserialize');
if (resourceHash.attributes[attributeKey] !== undefined) {
attributes[key] = resourceHash.attributes[attributeKey];
}
if (true
/* DEBUG */
) {
if (resourceHash.attributes[attributeKey] === undefined && resourceHash.attributes[key] !== undefined) {
(true && Ember.assert("Your payload for '".concat(modelClass.modelName, "' contains '").concat(key, "', but your serializer is setup to look for '").concat(attributeKey, "'. This is most likely because Ember Data's JSON API serializer dasherizes attribute keys by default. You should subclass JSONAPISerializer and implement 'keyForAttribute(key) { return key; }' to prevent Ember Data from customizing your attribute keys."), false));
}
}
});
}
return attributes;
},
/**
Returns a relationship formatted as a JSON-API "relationship object".
http://jsonapi.org/format/#document-resource-object-relationships
@method extractRelationship
@param {Object} relationshipHash
@return {Object}
*/
extractRelationship(relationshipHash) {
if (Ember.typeOf(relationshipHash.data) === 'object') {
relationshipHash.data = this._normalizeRelationshipDataHelper(relationshipHash.data);
}
if (Array.isArray(relationshipHash.data)) {
let ret = new Array(relationshipHash.data.length);
for (let i = 0; i < relationshipHash.data.length; i++) {
let data = relationshipHash.data[i];
ret[i] = this._normalizeRelationshipDataHelper(data);
}
relationshipHash.data = ret;
}
return relationshipHash;
},
/**
Returns the resource's relationships formatted as a JSON-API "relationships object".
http://jsonapi.org/format/#document-resource-object-relationships
@method extractRelationships
@param {Object} modelClass
@param {Object} resourceHash
@return {Object}
*/
extractRelationships(modelClass, resourceHash) {
let relationships = {};
if (resourceHash.relationships) {
modelClass.eachRelationship((key, relationshipMeta) => {
let relationshipKey = this.keyForRelationship(key, relationshipMeta.kind, 'deserialize');
if (resourceHash.relationships[relationshipKey] !== undefined) {
let relationshipHash = resourceHash.relationships[relationshipKey];
relationships[key] = this.extractRelationship(relationshipHash);
}
if (true
/* DEBUG */
) {
if (resourceHash.relationships[relationshipKey] === undefined && resourceHash.relationships[key] !== undefined) {
(true && Ember.assert("Your payload for '".concat(modelClass.modelName, "' contains '").concat(key, "', but your serializer is setup to look for '").concat(relationshipKey, "'. This is most likely because Ember Data's JSON API serializer dasherizes relationship keys by default. You should subclass JSONAPISerializer and implement 'keyForRelationship(key) { return key; }' to prevent Ember Data from customizing your relationship keys."), false));
}
}
});
}
return relationships;
},
/**
@method _extractType
@param {DS.Model} modelClass
@param {Object} resourceHash
@return {String}
@private
*/
_extractType(modelClass, resourceHash) {
return this.modelNameFromPayloadKey(resourceHash.type);
},
/**
Dasherizes and singularizes the model name in the payload to match
the format Ember Data uses internally for the model name.
For example the key `posts` would be converted to `post` and the
key `studentAssesments` would be converted to `student-assesment`.
@method modelNameFromPayloadKey
@param {String} key
@return {String} the model's modelName
*/
// TODO @deprecated Use modelNameFromPayloadType instead
modelNameFromPayloadKey(key) {
return (0, _emberInflector.singularize)((0, _store.normalizeModelName)(key));
},
/**
Converts the model name to a pluralized version of the model name.
For example `post` would be converted to `posts` and
`student-assesment` would be converted to `student-assesments`.
@method payloadKeyFromModelName
@param {String} modelName
@return {String}
*/
// TODO @deprecated Use payloadTypeFromModelName instead
payloadKeyFromModelName(modelName) {
return (0, _emberInflector.pluralize)(modelName);
},
normalize(modelClass, resourceHash) {
if (resourceHash.attributes) {
this.normalizeUsingDeclaredMapping(modelClass, resourceHash.attributes);
}
if (resourceHash.relationships) {
this.normalizeUsingDeclaredMapping(modelClass, resourceHash.relationships);
}
let data = {
id: this.extractId(modelClass, resourceHash),
type: this._extractType(modelClass, resourceHash),
attributes: this.extractAttributes(modelClass, resourceHash),
relationships: this.extractRelationships(modelClass, resourceHash)
};
this.applyTransforms(modelClass, data.attributes);
return {
data
};
},
/**
`keyForAttribute` can be used to define rules for how to convert an
attribute name in your model to a key in your JSON.
By default `JSONAPISerializer` follows the format used on the examples of
http://jsonapi.org/format and uses dashes as the word separator in the JSON
attribute keys.
This behaviour can be easily customized by extending this method.
Example
```app/serializers/application.js
import JSONAPISerializer from '@ember-data/serializer/json-api';
import { dasherize } from '@ember/string';
export default JSONAPISerializer.extend({
keyForAttribute(attr, method) {
return dasherize(attr).toUpperCase();
}
});
```
@method keyForAttribute
@param {String} key
@param {String} method
@return {String} normalized key
*/
keyForAttribute(key, method) {
return Ember.String.dasherize(key);
},
/**
`keyForRelationship` can be used to define a custom key when
serializing and deserializing relationship properties.
By default `JSONAPISerializer` follows the format used on the examples of
http://jsonapi.org/format and uses dashes as word separators in
relationship properties.
This behaviour can be easily customized by extending this method.
Example
```app/serializers/post.js
import JSONAPISerializer from '@ember-data/serializer/json-api';
import { underscore } from '@ember/string';
export default JSONAPISerializer.extend({
keyForRelationship(key, relationship, method) {
return underscore(key);
}
});
```
@method keyForRelationship
@param {String} key
@param {String} typeClass
@param {String} method
@return {String} normalized key
*/
keyForRelationship(key, typeClass, method) {
return Ember.String.dasherize(key);
},
serialize(snapshot, options) {
let data = this._super(...arguments);
data.type = this.payloadKeyFromModelName(snapshot.modelName);
return {
data
};
},
serializeAttribute(snapshot, json, key, attribute) {
let type = attribute.type;
if (this._canSerialize(key)) {
json.attributes = json.attributes || {};
let value = snapshot.attr(key);
if (type) {
let transform = this.transformFor(type);
value = transform.serialize(value, attribute.options);
}
let payloadKey = this._getMappedKey(key, snapshot.type);
if (payloadKey === key) {
payloadKey = this.keyForAttribute(key, 'serialize');
}
json.attributes[payloadKey] = value;
}
},
serializeBelongsTo(snapshot, json, relationship) {
let key = relationship.key;
if (this._canSerialize(key)) {
let belongsTo = snapshot.belongsTo(key);
let belongsToIsNotNew = belongsTo && belongsTo.record && !belongsTo.record.get('isNew');
if (belongsTo === null || belongsToIsNotNew) {
json.relationships = json.relationships || {};
let payloadKey = this._getMappedKey(key, snapshot.type);
if (payloadKey === key) {
payloadKey = this.keyForRelationship(key, 'belongsTo', 'serialize');
}
let data = null;
if (belongsTo) {
let payloadType = this.payloadKeyFromModelName(belongsTo.modelName);
data = {
type: payloadType,
id: belongsTo.id
};
}
json.relationships[payloadKey] = {
data
};
}
}
},
serializeHasMany(snapshot, json, relationship) {
let key = relationship.key;
if (this.shouldSerializeHasMany(snapshot, key, relationship)) {
let hasMany = snapshot.hasMany(key);
if (hasMany !== undefined) {
json.relationships = json.relationships || {};
let payloadKey = this._getMappedKey(key, snapshot.type);
if (payloadKey === key && this.keyForRelationship) {
payloadKey = this.keyForRelationship(key, 'hasMany', 'serialize');
} // only serialize has many relationships that are not new
let nonNewHasMany = hasMany.filter(item => item.record && !item.record.get('isNew'));
let data = new Array(nonNewHasMany.length);
for (let i = 0; i < nonNewHasMany.length; i++) {
let item = hasMany[i];
let payloadType = this.payloadKeyFromModelName(item.modelName);
data[i] = {
type: payloadType,
id: item.id
};
}
json.relationships[payloadKey] = {
data
};
}
}
}
});
if (true
/* DEBUG */
) {
JSONAPISerializer.reopen({
willMergeMixin(props) {
let constructor = this.constructor;
(true && Ember.warn("You've defined 'extractMeta' in ".concat(constructor.toString(), " which is not used for serializers extending JSONAPISerializer. Read more at https://emberjs.com/api/data/classes/DS.JSONAPISerializer on how to customize meta when using JSON API."), Ember.isNone(props.extractMeta) || props.extractMeta === _json.default.prototype.extractMeta, {
id: 'ds.serializer.json-api.extractMeta'
}));
(true && Ember.warn('The JSONAPISerializer does not work with the EmbeddedRecordsMixin because the JSON API spec does not describe how to format embedded resources.', !props.isEmbeddedRecordsMixin, {
id: 'ds.serializer.embedded-records-mixin-not-supported'
}));
},
warnMessageForUndefinedType() {
return 'Encountered a resource object with an undefined type (resolved resource using ' + this.constructor.toString() + ')';
},
warnMessageNoModelForType(modelName, originalType, usedLookup) {
return "Encountered a resource object with type \"".concat(originalType, "\", but no model was found for model name \"").concat(modelName, "\" (resolved model name using '").concat(this.constructor.toString(), ".").concat(usedLookup, "(\"").concat(originalType, "\")').");
}
});
}
var _default = JSONAPISerializer;
_exports.default = _default;
});
define("@ember-data/serializer/json", ["exports", "@ember-data/serializer", "@ember-data/adapter/error", "@ember-data/serializer/-private", "@ember-data/store", "@ember-data/store/-private"], function (_exports, _serializer, _error, _private, _store, _private2) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
const emberAssign = Ember.assign || Ember.merge;
/**
Ember Data 2.0 Serializer:
In Ember Data a Serializer is used to serialize and deserialize
records when they are transferred in and out of an external source.
This process involves normalizing property names, transforming
attribute values and serializing relationships.
By default, Ember Data uses and recommends the `JSONAPISerializer`.
`JSONSerializer` is useful for simpler or legacy backends that may
not support the http://jsonapi.org/ spec.
For example, given the following `User` model and JSON payload:
```app/models/user.js
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
export default Model.extend({
friends: hasMany('user'),
house: belongsTo('location'),
name: attr('string')
});
```
```js
{
id: 1,
name: 'Sebastian',
friends: [3, 4],
links: {
house: '/houses/lefkada'
}
}
```
`JSONSerializer` will normalize the JSON payload to the JSON API format that the
Ember Data store expects.
You can customize how JSONSerializer processes its payload by passing options in
the `attrs` hash or by subclassing the `JSONSerializer` and overriding hooks:
- To customize how a single record is normalized, use the `normalize` hook.
- To customize how `JSONSerializer` normalizes the whole server response, use the
`normalizeResponse` hook.
- To customize how `JSONSerializer` normalizes a specific response from the server,
use one of the many specific `normalizeResponse` hooks.
- To customize how `JSONSerializer` normalizes your id, attributes or relationships,
use the `extractId`, `extractAttributes` and `extractRelationships` hooks.
The `JSONSerializer` normalization process follows these steps:
- `normalizeResponse` - entry method to the serializer.
- `normalizeCreateRecordResponse` - a `normalizeResponse` for a specific operation is called.
- `normalizeSingleResponse`|`normalizeArrayResponse` - for methods like `createRecord` we expect
a single record back, while for methods like `findAll` we expect multiple records back.
- `normalize` - `normalizeArray` iterates and calls `normalize` for each of its records while `normalizeSingle`
calls it once. This is the method you most likely want to subclass.
- `extractId` | `extractAttributes` | `extractRelationships` - `normalize` delegates to these methods to
turn the record payload into the JSON API format.
@class JSONSerializer
@extends Serializer
*/
const JSONSerializer = _serializer.default.extend({
/**
The `primaryKey` is used when serializing and deserializing
data. Ember Data always uses the `id` property to store the id of
the record. The external source may not always follow this
convention. In these cases it is useful to override the
`primaryKey` property to match the `primaryKey` of your external
store.
Example
```app/serializers/application.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
primaryKey: '_id'
});
```
@property primaryKey
@type {String}
@default 'id'
*/
primaryKey: 'id',
/**
The `attrs` object can be used to declare a simple mapping between
property names on `Model` records and payload keys in the
serialized JSON object representing the record. An object with the
property `key` can also be used to designate the attribute's key on
the response payload.
Example
```app/models/person.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
firstName: attr('string'),
lastName: attr('string'),
occupation: attr('string'),
admin: attr('boolean')
});
```
```app/serializers/person.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
attrs: {
admin: 'is_admin',
occupation: { key: 'career' }
}
});
```
You can also remove attributes and relationships by setting the `serialize`
key to `false` in your mapping object.
Example
```app/serializers/person.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
attrs: {
admin: { serialize: false },
occupation: { key: 'career' }
}
});
```
When serialized:
```javascript
{
"firstName": "Harry",
"lastName": "Houdini",
"career": "magician"
}
```
Note that the `admin` is now not included in the payload.
Setting `serialize` to `true` enforces serialization for hasMany
relationships even if it's neither a many-to-many nor many-to-none
relationship.
@property attrs
@type {Object}
*/
mergedProperties: ['attrs'],
/**
Given a subclass of `Model` and a JSON object this method will
iterate through each attribute of the `Model` and invoke the
`Transform#deserialize` method on the matching property of the
JSON object. This method is typically called after the
serializer's `normalize` method.
@method applyTransforms
@private
@param {DS.Model} typeClass
@param {Object} data The data to transform
@return {Object} data The transformed data object
*/
applyTransforms(typeClass, data) {
let attributes = Ember.get(typeClass, 'attributes');
typeClass.eachTransformedAttribute((key, typeClass) => {
if (data[key] === undefined) {
return;
}
let transform = this.transformFor(typeClass);
let transformMeta = attributes.get(key);
data[key] = transform.deserialize(data[key], transformMeta.options);
});
return data;
},
/**
The `normalizeResponse` method is used to normalize a payload from the
server to a JSON-API Document.
http://jsonapi.org/format/#document-structure
This method delegates to a more specific normalize method based on
the `requestType`.
To override this method with a custom one, make sure to call
`return this._super(store, primaryModelClass, payload, id, requestType)` with your
pre-processed data.
Here's an example of using `normalizeResponse` manually:
```javascript
socket.on('message', function(message) {
var data = message.data;
var modelClass = store.modelFor(data.modelName);
var serializer = store.serializerFor(data.modelName);
var normalized = serializer.normalizeSingleResponse(store, modelClass, data, data.id);
store.push(normalized);
});
```
@since 1.13.0
@method normalizeResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
switch (requestType) {
case 'findRecord':
return this.normalizeFindRecordResponse(...arguments);
case 'queryRecord':
return this.normalizeQueryRecordResponse(...arguments);
case 'findAll':
return this.normalizeFindAllResponse(...arguments);
case 'findBelongsTo':
return this.normalizeFindBelongsToResponse(...arguments);
case 'findHasMany':
return this.normalizeFindHasManyResponse(...arguments);
case 'findMany':
return this.normalizeFindManyResponse(...arguments);
case 'query':
return this.normalizeQueryResponse(...arguments);
case 'createRecord':
return this.normalizeCreateRecordResponse(...arguments);
case 'deleteRecord':
return this.normalizeDeleteRecordResponse(...arguments);
case 'updateRecord':
return this.normalizeUpdateRecordResponse(...arguments);
}
},
/**
@since 1.13.0
@method normalizeFindRecordResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeFindRecordResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeSingleResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeQueryRecordResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeQueryRecordResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeSingleResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeFindAllResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeFindAllResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeArrayResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeFindBelongsToResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeFindBelongsToResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeSingleResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeFindHasManyResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeFindHasManyResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeArrayResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeFindManyResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeFindManyResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeArrayResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeQueryResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeQueryResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeArrayResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeCreateRecordResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeCreateRecordResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeSaveResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeDeleteRecordResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeDeleteRecordResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeSaveResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeUpdateRecordResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeUpdateRecordResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeSaveResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeSaveResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeSaveResponse(store, primaryModelClass, payload, id, requestType) {
return this.normalizeSingleResponse(...arguments);
},
/**
@since 1.13.0
@method normalizeSingleResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeSingleResponse(store, primaryModelClass, payload, id, requestType) {
return this._normalizeResponse(store, primaryModelClass, payload, id, requestType, true);
},
/**
@since 1.13.0
@method normalizeArrayResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) {
return this._normalizeResponse(store, primaryModelClass, payload, id, requestType, false);
},
/**
@method _normalizeResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@param {Boolean} isSingle
@return {Object} JSON-API Document
@private
*/
_normalizeResponse(store, primaryModelClass, payload, id, requestType, isSingle) {
let documentHash = {
data: null,
included: []
};
let meta = this.extractMeta(store, primaryModelClass, payload);
if (meta) {
(true && Ember.assert('The `meta` returned from `extractMeta` has to be an object, not "' + Ember.typeOf(meta) + '".', Ember.typeOf(meta) === 'object'));
documentHash.meta = meta;
}
if (isSingle) {
let {
data,
included
} = this.normalize(primaryModelClass, payload);
documentHash.data = data;
if (included) {
documentHash.included = included;
}
} else {
let ret = new Array(payload.length);
for (let i = 0, l = payload.length; i < l; i++) {
let item = payload[i];
let {
data,
included
} = this.normalize(primaryModelClass, item);
if (included) {
documentHash.included.push(...included);
}
ret[i] = data;
}
documentHash.data = ret;
}
return documentHash;
},
/**
Normalizes a part of the JSON payload returned by
the server. You should override this method, munge the hash
and call super if you have generic normalization to do.
It takes the type of the record that is being normalized
(as a Model class), the property where the hash was
originally found, and the hash to normalize.
You can use this method, for example, to normalize underscored keys to camelized
or other general-purpose normalizations.
Example
```app/serializers/application.js
import JSONSerializer from '@ember-data/serializer/json';
import { underscore } from '@ember/string';
import { get } from '@ember/object';
export default JSONSerializer.extend({
normalize(typeClass, hash) {
var fields = get(typeClass, 'fields');
fields.forEach(function(type, field) {
var payloadField = underscore(field);
if (field === payloadField) { return; }
hash[field] = hash[payloadField];
delete hash[payloadField];
});
return this._super.apply(this, arguments);
}
});
```
@method normalize
@param {DS.Model} typeClass
@param {Object} hash
@return {Object}
*/
normalize(modelClass, resourceHash) {
let data = null;
if (resourceHash) {
this.normalizeUsingDeclaredMapping(modelClass, resourceHash);
if (Ember.typeOf(resourceHash.links) === 'object') {
this.normalizeUsingDeclaredMapping(modelClass, resourceHash.links);
}
data = {
id: this.extractId(modelClass, resourceHash),
type: modelClass.modelName,
attributes: this.extractAttributes(modelClass, resourceHash),
relationships: this.extractRelationships(modelClass, resourceHash)
};
this.applyTransforms(modelClass, data.attributes);
}
return {
data
};
},
/**
Returns the resource's ID.
@method extractId
@param {Object} modelClass
@param {Object} resourceHash
@return {String}
*/
extractId(modelClass, resourceHash) {
let primaryKey = Ember.get(this, 'primaryKey');
let id = resourceHash[primaryKey];
return (0, _private2.coerceId)(id);
},
/**
Returns the resource's attributes formatted as a JSON-API "attributes object".
http://jsonapi.org/format/#document-resource-object-attributes
@method extractAttributes
@param {Object} modelClass
@param {Object} resourceHash
@return {Object}
*/
extractAttributes(modelClass, resourceHash) {
let attributeKey;
let attributes = {};
modelClass.eachAttribute(key => {
attributeKey = this.keyForAttribute(key, 'deserialize');
if (resourceHash[attributeKey] !== undefined) {
attributes[key] = resourceHash[attributeKey];
}
});
return attributes;
},
/**
Returns a relationship formatted as a JSON-API "relationship object".
http://jsonapi.org/format/#document-resource-object-relationships
@method extractRelationship
@param {Object} relationshipModelName
@param {Object} relationshipHash
@return {Object}
*/
extractRelationship(relationshipModelName, relationshipHash) {
if (Ember.isNone(relationshipHash)) {
return null;
}
/*
When `relationshipHash` is an object it usually means that the relationship
is polymorphic. It could however also be embedded resources that the
EmbeddedRecordsMixin has be able to process.
*/
if (Ember.typeOf(relationshipHash) === 'object') {
if (relationshipHash.id) {
relationshipHash.id = (0, _private2.coerceId)(relationshipHash.id);
}
let modelClass = this.store.modelFor(relationshipModelName);
if (relationshipHash.type && !(0, _private.modelHasAttributeOrRelationshipNamedType)(modelClass)) {
relationshipHash.type = this.modelNameFromPayloadKey(relationshipHash.type);
}
return relationshipHash;
}
return {
id: (0, _private2.coerceId)(relationshipHash),
type: relationshipModelName
};
},
/**
Returns a polymorphic relationship formatted as a JSON-API "relationship object".
http://jsonapi.org/format/#document-resource-object-relationships
`relationshipOptions` is a hash which contains more information about the
polymorphic relationship which should be extracted:
- `resourceHash` complete hash of the resource the relationship should be
extracted from
- `relationshipKey` key under which the value for the relationship is
extracted from the resourceHash
- `relationshipMeta` meta information about the relationship
@method extractPolymorphicRelationship
@param {Object} relationshipModelName
@param {Object} relationshipHash
@param {Object} relationshipOptions
@return {Object}
*/
extractPolymorphicRelationship(relationshipModelName, relationshipHash, relationshipOptions) {
return this.extractRelationship(relationshipModelName, relationshipHash);
},
/**
Returns the resource's relationships formatted as a JSON-API "relationships object".
http://jsonapi.org/format/#document-resource-object-relationships
@method extractRelationships
@param {Object} modelClass
@param {Object} resourceHash
@return {Object}
*/
extractRelationships(modelClass, resourceHash) {
let relationships = {};
modelClass.eachRelationship((key, relationshipMeta) => {
let relationship = null;
let relationshipKey = this.keyForRelationship(key, relationshipMeta.kind, 'deserialize');
if (resourceHash[relationshipKey] !== undefined) {
let data = null;
let relationshipHash = resourceHash[relationshipKey];
if (relationshipMeta.kind === 'belongsTo') {
if (relationshipMeta.options.polymorphic) {
// extracting a polymorphic belongsTo may need more information
// than the type and the hash (which might only be an id) for the
// relationship, hence we pass the key, resource and
// relationshipMeta too
data = this.extractPolymorphicRelationship(relationshipMeta.type, relationshipHash, {
key,
resourceHash,
relationshipMeta
});
} else {
data = this.extractRelationship(relationshipMeta.type, relationshipHash);
}
} else if (relationshipMeta.kind === 'hasMany') {
if (!Ember.isNone(relationshipHash)) {
data = new Array(relationshipHash.length);
if (relationshipMeta.options.polymorphic) {
for (let i = 0, l = relationshipHash.length; i < l; i++) {
let item = relationshipHash[i];
data[i] = this.extractPolymorphicRelationship(relationshipMeta.type, item, {
key,
resourceHash,
relationshipMeta
});
}
} else {
for (let i = 0, l = relationshipHash.length; i < l; i++) {
let item = relationshipHash[i];
data[i] = this.extractRelationship(relationshipMeta.type, item);
}
}
}
}
relationship = {
data
};
}
let linkKey = this.keyForLink(key, relationshipMeta.kind);
if (resourceHash.links && resourceHash.links[linkKey] !== undefined) {
let related = resourceHash.links[linkKey];
relationship = relationship || {};
relationship.links = {
related
};
}
if (relationship) {
relationships[key] = relationship;
}
});
return relationships;
},
/**
@method modelNameFromPayloadKey
@param {String} key
@return {String} the model's modelName
*/
// TODO @deprecated Use modelNameFromPayloadType instead
modelNameFromPayloadKey(key) {
return (0, _store.normalizeModelName)(key);
},
/**
@method normalizeRelationships
@private
*/
normalizeRelationships(typeClass, hash) {
let payloadKey;
if (this.keyForRelationship) {
typeClass.eachRelationship((key, relationship) => {
payloadKey = this.keyForRelationship(key, relationship.kind, 'deserialize');
if (key === payloadKey) {
return;
}
if (hash[payloadKey] === undefined) {
return;
}
hash[key] = hash[payloadKey];
delete hash[payloadKey];
});
}
},
/**
@method normalizeUsingDeclaredMapping
@private
*/
normalizeUsingDeclaredMapping(modelClass, hash) {
let attrs = Ember.get(this, 'attrs');
let normalizedKey;
let payloadKey;
if (attrs) {
for (let key in attrs) {
normalizedKey = payloadKey = this._getMappedKey(key, modelClass);
if (hash[payloadKey] === undefined) {
continue;
}
if (Ember.get(modelClass, 'attributes').has(key)) {
normalizedKey = this.keyForAttribute(key);
}
if (Ember.get(modelClass, 'relationshipsByName').has(key)) {
normalizedKey = this.keyForRelationship(key);
}
if (payloadKey !== normalizedKey) {
hash[normalizedKey] = hash[payloadKey];
delete hash[payloadKey];
}
}
}
},
/**
Looks up the property key that was set by the custom `attr` mapping
passed to the serializer.
@method _getMappedKey
@private
@param {String} key
@return {String} key
*/
_getMappedKey(key, modelClass) {
(true && Ember.warn('There is no attribute or relationship with the name `' + key + '` on `' + modelClass.modelName + '`. Check your serializers attrs hash.', Ember.get(modelClass, 'attributes').has(key) || Ember.get(modelClass, 'relationshipsByName').has(key), {
id: 'ds.serializer.no-mapped-attrs-key'
}));
let attrs = Ember.get(this, 'attrs');
let mappedKey;
if (attrs && attrs[key]) {
mappedKey = attrs[key]; //We need to account for both the { title: 'post_title' } and
//{ title: { key: 'post_title' }} forms
if (mappedKey.key) {
mappedKey = mappedKey.key;
}
if (typeof mappedKey === 'string') {
key = mappedKey;
}
}
return key;
},
/**
Check attrs.key.serialize property to inform if the `key`
can be serialized
@method _canSerialize
@private
@param {String} key
@return {boolean} true if the key can be serialized
*/
_canSerialize(key) {
let attrs = Ember.get(this, 'attrs');
return !attrs || !attrs[key] || attrs[key].serialize !== false;
},
/**
When attrs.key.serialize is set to true then
it takes priority over the other checks and the related
attribute/relationship will be serialized
@method _mustSerialize
@private
@param {String} key
@return {boolean} true if the key must be serialized
*/
_mustSerialize(key) {
let attrs = Ember.get(this, 'attrs');
return attrs && attrs[key] && attrs[key].serialize === true;
},
/**
Check if the given hasMany relationship should be serialized
By default only many-to-many and many-to-none relationships are serialized.
This could be configured per relationship by Serializer's `attrs` object.
@method shouldSerializeHasMany
@param {DS.Snapshot} snapshot
@param {String} key
@param {String} relationshipType
@return {boolean} true if the hasMany relationship should be serialized
*/
shouldSerializeHasMany(snapshot, key, relationship) {
let relationshipType = snapshot.type.determineRelationshipType(relationship, this.store);
if (this._mustSerialize(key)) {
return true;
}
return this._canSerialize(key) && (relationshipType === 'manyToNone' || relationshipType === 'manyToMany');
},
// SERIALIZE
/**
Called when a record is saved in order to convert the
record into JSON.
By default, it creates a JSON object with a key for
each attribute and belongsTo relationship.
For example, consider this model:
```app/models/comment.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default Model.extend({
title: attr(),
body: attr(),
author: belongsTo('user')
});
```
The default serialization would create a JSON object like:
```javascript
{
"title": "Rails is unagi",
"body": "Rails? Omakase? O_O",
"author": 12
}
```
By default, attributes are passed through as-is, unless
you specified an attribute type (`attr('date')`). If
you specify a transform, the JavaScript value will be
serialized when inserted into the JSON hash.
By default, belongs-to relationships are converted into
IDs when inserted into the JSON hash.
## IDs
`serialize` takes an options hash with a single option:
`includeId`. If this option is `true`, `serialize` will,
by default include the ID in the JSON object it builds.
The adapter passes in `includeId: true` when serializing
a record for `createRecord`, but not for `updateRecord`.
## Customization
Your server may expect a different JSON format than the
built-in serialization format.
In that case, you can implement `serialize` yourself and
return a JSON hash of your choosing.
```app/serializers/post.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
serialize(snapshot, options) {
var json = {
POST_TTL: snapshot.attr('title'),
POST_BDY: snapshot.attr('body'),
POST_CMS: snapshot.hasMany('comments', { ids: true })
};
if (options.includeId) {
json.POST_ID_ = snapshot.id;
}
return json;
}
});
```
## Customizing an App-Wide Serializer
If you want to define a serializer for your entire
application, you'll probably want to use `eachAttribute`
and `eachRelationship` on the record.
```app/serializers/application.js
import JSONSerializer from '@ember-data/serializer/json';
import { singularize } from 'ember-inflector';
export default JSONSerializer.extend({
serialize(snapshot, options) {
var json = {};
snapshot.eachAttribute(function(name) {
json[serverAttributeName(name)] = snapshot.attr(name);
});
snapshot.eachRelationship(function(name, relationship) {
if (relationship.kind === 'hasMany') {
json[serverHasManyName(name)] = snapshot.hasMany(name, { ids: true });
}
});
if (options.includeId) {
json.ID_ = snapshot.id;
}
return json;
}
});
function serverAttributeName(attribute) {
return attribute.underscore().toUpperCase();
}
function serverHasManyName(name) {
return serverAttributeName(singularize(name)) + "_IDS";
}
```
This serializer will generate JSON that looks like this:
```javascript
{
"TITLE": "Rails is omakase",
"BODY": "Yep. Omakase.",
"COMMENT_IDS": [ 1, 2, 3 ]
}
```
## Tweaking the Default JSON
If you just want to do some small tweaks on the default JSON,
you can call super first and make the tweaks on the returned
JSON.
```app/serializers/post.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
serialize(snapshot, options) {
var json = this._super(...arguments);
json.subject = json.title;
delete json.title;
return json;
}
});
```
@method serialize
@param {DS.Snapshot} snapshot
@param {Object} options
@return {Object} json
*/
serialize(snapshot, options) {
let json = {};
if (options && options.includeId) {
const id = snapshot.id;
if (id) {
json[Ember.get(this, 'primaryKey')] = id;
}
}
snapshot.eachAttribute((key, attribute) => {
this.serializeAttribute(snapshot, json, key, attribute);
});
snapshot.eachRelationship((key, relationship) => {
if (relationship.kind === 'belongsTo') {
this.serializeBelongsTo(snapshot, json, relationship);
} else if (relationship.kind === 'hasMany') {
this.serializeHasMany(snapshot, json, relationship);
}
});
return json;
},
/**
You can use this method to customize how a serialized record is added to the complete
JSON hash to be sent to the server. By default the JSON Serializer does not namespace
the payload and just sends the raw serialized JSON object.
If your server expects namespaced keys, you should consider using the RESTSerializer.
Otherwise you can override this method to customize how the record is added to the hash.
The hash property should be modified by reference.
For example, your server may expect underscored root objects.
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
import { decamelize } from '@ember/string';
export default RESTSerializer.extend({
serializeIntoHash(data, type, snapshot, options) {
var root = decamelize(type.modelName);
data[root] = this.serialize(snapshot, options);
}
});
```
@method serializeIntoHash
@param {Object} hash
@param {DS.Model} typeClass
@param {DS.Snapshot} snapshot
@param {Object} options
*/
serializeIntoHash(hash, typeClass, snapshot, options) {
emberAssign(hash, this.serialize(snapshot, options));
},
/**
`serializeAttribute` can be used to customize how `attr`
properties are serialized
For example if you wanted to ensure all your attributes were always
serialized as properties on an `attributes` object you could
write:
```app/serializers/application.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
serializeAttribute(snapshot, json, key, attributes) {
json.attributes = json.attributes || {};
this._super(snapshot, json.attributes, key, attributes);
}
});
```
@method serializeAttribute
@param {DS.Snapshot} snapshot
@param {Object} json
@param {String} key
@param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
if (this._canSerialize(key)) {
let type = attribute.type;
let value = snapshot.attr(key);
if (type) {
let transform = this.transformFor(type);
value = transform.serialize(value, attribute.options);
} // if provided, use the mapping provided by `attrs` in
// the serializer
let payloadKey = this._getMappedKey(key, snapshot.type);
if (payloadKey === key && this.keyForAttribute) {
payloadKey = this.keyForAttribute(key, 'serialize');
}
json[payloadKey] = value;
}
},
/**
`serializeBelongsTo` can be used to customize how `belongsTo`
properties are serialized.
Example
```app/serializers/post.js
import JSONSerializer from '@ember-data/serializer/json';
import { isNone } from '@ember/utils';
export default JSONSerializer.extend({
serializeBelongsTo(snapshot, json, relationship) {
var key = relationship.key;
var belongsTo = snapshot.belongsTo(key);
key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo", "serialize") : key;
json[key] = isNone(belongsTo) ? belongsTo : belongsTo.record.toJSON();
}
});
```
@method serializeBelongsTo
@param {DS.Snapshot} snapshot
@param {Object} json
@param {Object} relationship
*/
serializeBelongsTo(snapshot, json, relationship) {
let key = relationship.key;
if (this._canSerialize(key)) {
let belongsToId = snapshot.belongsTo(key, {
id: true
}); // if provided, use the mapping provided by `attrs` in
// the serializer
let payloadKey = this._getMappedKey(key, snapshot.type);
if (payloadKey === key && this.keyForRelationship) {
payloadKey = this.keyForRelationship(key, 'belongsTo', 'serialize');
} //Need to check whether the id is there for new&async records
if (Ember.isNone(belongsToId)) {
json[payloadKey] = null;
} else {
json[payloadKey] = belongsToId;
}
if (relationship.options.polymorphic) {
this.serializePolymorphicType(snapshot, json, relationship);
}
}
},
/**
`serializeHasMany` can be used to customize how `hasMany`
properties are serialized.
Example
```app/serializers/post.js
import JSONSerializer from '@ember-data/serializer/json';
import DS from 'ember-data';
export default JSONSerializer.extend({
serializeHasMany(snapshot, json, relationship) {
var key = relationship.key;
if (key === 'comments') {
return;
} else {
this._super(...arguments);
}
}
});
```
@method serializeHasMany
@param {DS.Snapshot} snapshot
@param {Object} json
@param {Object} relationship
*/
serializeHasMany(snapshot, json, relationship) {
let key = relationship.key;
if (this.shouldSerializeHasMany(snapshot, key, relationship)) {
let hasMany = snapshot.hasMany(key, {
ids: true
});
if (hasMany !== undefined) {
// if provided, use the mapping provided by `attrs` in
// the serializer
let payloadKey = this._getMappedKey(key, snapshot.type);
if (payloadKey === key && this.keyForRelationship) {
payloadKey = this.keyForRelationship(key, 'hasMany', 'serialize');
}
json[payloadKey] = hasMany; // TODO support for polymorphic manyToNone and manyToMany relationships
}
}
},
/**
You can use this method to customize how polymorphic objects are
serialized. Objects are considered to be polymorphic if
`{ polymorphic: true }` is pass as the second argument to the
`belongsTo` function.
Example
```app/serializers/comment.js
import JSONSerializer from '@ember-data/serializer/json';
import { isNone } from '@ember/utils';
export default JSONSerializer.extend({
serializePolymorphicType(snapshot, json, relationship) {
var key = relationship.key;
var belongsTo = snapshot.belongsTo(key);
key = this.keyForAttribute ? this.keyForAttribute(key, 'serialize') : key;
if (isNone(belongsTo)) {
json[key + '_type'] = null;
} else {
json[key + '_type'] = belongsTo.modelName;
}
}
});
```
@method serializePolymorphicType
@param {DS.Snapshot} snapshot
@param {Object} json
@param {Object} relationship
*/
serializePolymorphicType() {},
/**
`extractMeta` is used to deserialize any meta information in the
adapter payload. By default Ember Data expects meta information to
be located on the `meta` property of the payload object.
Example
```app/serializers/post.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
extractMeta(store, typeClass, payload) {
if (payload && payload.hasOwnProperty('_pagination')) {
let meta = payload._pagination;
delete payload._pagination;
return meta;
}
}
});
```
@method extractMeta
@param {DS.Store} store
@param {DS.Model} modelClass
@param {Object} payload
*/
extractMeta(store, modelClass, payload) {
if (payload && payload['meta'] !== undefined) {
let meta = payload.meta;
delete payload.meta;
return meta;
}
},
/**
`extractErrors` is used to extract model errors when a call
to `Model#save` fails with an `InvalidError`. By default
Ember Data expects error information to be located on the `errors`
property of the payload object.
This serializer expects this `errors` object to be an Array similar
to the following, compliant with the https://jsonapi.org/format/#errors specification:
```js
{
"errors": [
{
"detail": "This username is already taken!",
"source": {
"pointer": "data/attributes/username"
}
}, {
"detail": "Doesn't look like a valid email.",
"source": {
"pointer": "data/attributes/email"
}
}
]
}
```
The key `detail` provides a textual description of the problem.
Alternatively, the key `title` can be used for the same purpose.
The nested keys `source.pointer` detail which specific element
of the request data was invalid.
Note that JSON-API also allows for object-level errors to be placed
in an object with pointer `data`, signifying that the problem
cannot be traced to a specific attribute:
```javascript
{
"errors": [
{
"detail": "Some generic non property error message",
"source": {
"pointer": "data"
}
}
]
}
```
When turn into a `Errors` object, you can read these errors
through the property `base`:
```handlebars
{{#each model.errors.base as |error|}}
{{error.message}}
{{/each}}
```
Example of alternative implementation, overriding the default
behavior to deal with a different format of errors:
```app/serializers/post.js
import DS from 'ember-data';
export default JSONSerializer.extend({
extractErrors(store, typeClass, payload, id) {
if (payload && typeof payload === 'object' && payload._problems) {
payload = payload._problems;
this.normalizeErrors(typeClass, payload);
}
return payload;
}
});
```
@method extractErrors
@param {DS.Store} store
@param {DS.Model} typeClass
@param {Object} payload
@param {(String|Number)} id
@return {Object} json The deserialized errors
*/
extractErrors(store, typeClass, payload, id) {
if (payload && typeof payload === 'object' && payload.errors) {
payload = (0, _error.errorsArrayToHash)(payload.errors);
this.normalizeUsingDeclaredMapping(typeClass, payload);
typeClass.eachAttribute(name => {
let key = this.keyForAttribute(name, 'deserialize');
if (key !== name && payload[key] !== undefined) {
payload[name] = payload[key];
delete payload[key];
}
});
typeClass.eachRelationship(name => {
let key = this.keyForRelationship(name, 'deserialize');
if (key !== name && payload[key] !== undefined) {
payload[name] = payload[key];
delete payload[key];
}
});
}
return payload;
},
/**
`keyForAttribute` can be used to define rules for how to convert an
attribute name in your model to a key in your JSON.
Example
```app/serializers/application.js
import JSONSerializer from '@ember-data/serializer/json';
import { underscore } from '@ember/string';
export default JSONSerializer.extend({
keyForAttribute(attr, method) {
return underscore(attr).toUpperCase();
}
});
```
@method keyForAttribute
@param {String} key
@param {String} method
@return {String} normalized key
*/
keyForAttribute(key, method) {
return key;
},
/**
`keyForRelationship` can be used to define a custom key when
serializing and deserializing relationship properties. By default
`JSONSerializer` does not provide an implementation of this method.
Example
```app/serializers/post.js
import JSONSerializer from '@ember-data/serializer/json';
import { underscore } from '@ember/string';
export default JSONSerializer.extend({
keyForRelationship(key, relationship, method) {
return `rel_${underscore(key)}`;
}
});
```
@method keyForRelationship
@param {String} key
@param {String} typeClass
@param {String} method
@return {String} normalized key
*/
keyForRelationship(key, typeClass, method) {
return key;
},
/**
`keyForLink` can be used to define a custom key when deserializing link
properties.
@method keyForLink
@param {String} key
@param {String} kind `belongsTo` or `hasMany`
@return {String} normalized key
*/
keyForLink(key, kind) {
return key;
},
// HELPERS
/**
@method transformFor
@private
@param {String} attributeType
@param {Boolean} skipAssertion
@return {DS.Transform} transform
*/
transformFor(attributeType, skipAssertion) {
let transform = Ember.getOwner(this).lookup('transform:' + attributeType);
(true && Ember.assert("Unable to find the transform for `attr('".concat(attributeType, "')`"), skipAssertion || !!transform));
return transform;
}
});
var _default = JSONSerializer;
_exports.default = _default;
});
define("@ember-data/serializer/rest", ["exports", "ember-inflector", "@ember-data/serializer/json", "@ember-data/store/-private", "@ember-data/serializer/-private", "@ember-data/store"], function (_exports, _emberInflector, _json, _private, _private2, _store) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "EmbeddedRecordsMixin", {
enumerable: true,
get: function () {
return _private2.EmbeddedRecordsMixin;
}
});
_exports.default = void 0;
/**
Normally, applications will use the `RESTSerializer` by implementing
the `normalize` method.
This allows you to do whatever kind of munging you need and is
especially useful if your server is inconsistent and you need to
do munging differently for many different kinds of responses.
See the `normalize` documentation for more information.
## Across the Board Normalization
There are also a number of hooks that you might find useful to define
across-the-board rules for your payload. These rules will be useful
if your server is consistent, or if you're building an adapter for
an infrastructure service, like Firebase, and want to encode service
conventions.
For example, if all of your keys are underscored and all-caps, but
otherwise consistent with the names you use in your models, you
can implement across-the-board rules for how to convert an attribute
name in your model to a key in your JSON.
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
import { underscore } from '@ember/string';
export default RESTSerializer.extend({
keyForAttribute(attr, method) {
return underscore(attr).toUpperCase();
}
});
```
You can also implement `keyForRelationship`, which takes the name
of the relationship as the first parameter, the kind of
relationship (`hasMany` or `belongsTo`) as the second parameter, and
the method (`serialize` or `deserialize`) as the third parameter.
@class RESTSerializer
@extends JSONSerializer
*/
const RESTSerializer = _json.default.extend({
/**
`keyForPolymorphicType` can be used to define a custom key when
serializing and deserializing a polymorphic type. By default, the
returned key is `${key}Type`.
Example
```app/serializers/post.js
import RESTSerializer from '@ember-data/serializer/rest';
export default RESTSerializer.extend({
keyForPolymorphicType(key, relationship) {
var relationshipKey = this.keyForRelationship(key);
return 'type-' + relationshipKey;
}
});
```
@method keyForPolymorphicType
@param {String} key
@param {String} typeClass
@param {String} method
@return {String} normalized key
*/
keyForPolymorphicType(key, typeClass, method) {
let relationshipKey = this.keyForRelationship(key);
return "".concat(relationshipKey, "Type");
},
/**
Normalizes a part of the JSON payload returned by
the server. You should override this method, munge the hash
and call super if you have generic normalization to do.
It takes the type of the record that is being normalized
(as a Model class), the property where the hash was
originally found, and the hash to normalize.
For example, if you have a payload that looks like this:
```js
{
"post": {
"id": 1,
"title": "Rails is omakase",
"comments": [ 1, 2 ]
},
"comments": [{
"id": 1,
"body": "FIRST"
}, {
"id": 2,
"body": "Rails is unagi"
}]
}
```
The `normalize` method will be called three times:
* With `App.Post`, `"posts"` and `{ id: 1, title: "Rails is omakase", ... }`
* With `App.Comment`, `"comments"` and `{ id: 1, body: "FIRST" }`
* With `App.Comment`, `"comments"` and `{ id: 2, body: "Rails is unagi" }`
You can use this method, for example, to normalize underscored keys to camelized
or other general-purpose normalizations. You will only need to implement
`normalize` and manipulate the payload as desired.
For example, if the `IDs` under `"comments"` are provided as `_id` instead of
`id`, you can specify how to normalize just the comments:
```app/serializers/post.js
import RESTSerializer from '@ember-data/serializer/rest';
export default RESTSerializer.extend({
normalize(model, hash, prop) {
if (prop === 'comments') {
hash.id = hash._id;
delete hash._id;
}
return this._super(...arguments);
}
});
```
On each call to the `normalize` method, the third parameter (`prop`) is always
one of the keys that were in the original payload or in the result of another
normalization as `normalizeResponse`.
@method normalize
@param {DS.Model} modelClass
@param {Object} resourceHash
@param {String} prop
@return {Object}
*/
/**
Normalizes an array of resource payloads and returns a JSON-API Document
with primary data and, if any, included data as `{ data, included }`.
@method _normalizeArray
@param {DS.Store} store
@param {String} modelName
@param {Object} arrayHash
@param {String} prop
@return {Object}
@private
*/
_normalizeArray(store, modelName, arrayHash, prop) {
let documentHash = {
data: [],
included: []
};
let modelClass = store.modelFor(modelName);
let serializer = store.serializerFor(modelName);
Ember.makeArray(arrayHash).forEach(hash => {
let {
data,
included
} = this._normalizePolymorphicRecord(store, hash, prop, modelClass, serializer);
documentHash.data.push(data);
if (included) {
documentHash.included.push(...included);
}
});
return documentHash;
},
_normalizePolymorphicRecord(store, hash, prop, primaryModelClass, primarySerializer) {
let serializer = primarySerializer;
let modelClass = primaryModelClass;
let primaryHasTypeAttribute = (0, _private2.modelHasAttributeOrRelationshipNamedType)(primaryModelClass);
if (!primaryHasTypeAttribute && hash.type) {
// Support polymorphic records in async relationships
let modelName = this.modelNameFromPayloadKey(hash.type);
if (store._hasModelFor(modelName)) {
serializer = store.serializerFor(modelName);
modelClass = store.modelFor(modelName);
}
}
return serializer.normalize(modelClass, hash, prop);
},
/*
@method _normalizeResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@param {Boolean} isSingle
@return {Object} JSON-API Document
@private
*/
_normalizeResponse(store, primaryModelClass, payload, id, requestType, isSingle) {
let documentHash = {
data: null,
included: []
};
let meta = this.extractMeta(store, primaryModelClass, payload);
if (meta) {
(true && Ember.assert('The `meta` returned from `extractMeta` has to be an object, not "' + Ember.typeOf(meta) + '".', Ember.typeOf(meta) === 'object'));
documentHash.meta = meta;
}
let keys = Object.keys(payload);
for (var i = 0, length = keys.length; i < length; i++) {
var prop = keys[i];
var modelName = prop;
var forcedSecondary = false;
/*
If you want to provide sideloaded records of the same type that the
primary data you can do that by prefixing the key with `_`.
Example
```
{
users: [
{ id: 1, title: 'Tom', manager: 3 },
{ id: 2, title: 'Yehuda', manager: 3 }
],
_users: [
{ id: 3, title: 'Tomster' }
]
}
```
This forces `_users` to be added to `included` instead of `data`.
*/
if (prop.charAt(0) === '_') {
forcedSecondary = true;
modelName = prop.substr(1);
}
var typeName = this.modelNameFromPayloadKey(modelName);
if (!store._hasModelFor(typeName)) {
(true && Ember.warn(this.warnMessageNoModelForKey(modelName, typeName), false, {
id: 'ds.serializer.model-for-key-missing'
}));
continue;
}
var isPrimary = !forcedSecondary && this.isPrimaryType(store, typeName, primaryModelClass);
var value = payload[prop];
if (value === null) {
continue;
}
if (true
/* DEBUG */
) {
let isQueryRecordAnArray = requestType === 'queryRecord' && isPrimary && Array.isArray(value);
let message = 'The adapter returned an array for the primary data of a `queryRecord` response. This is deprecated as `queryRecord` should return a single record.';
(true && !(!isQueryRecordAnArray) && Ember.deprecate(message, !isQueryRecordAnArray, {
id: 'ds.serializer.rest.queryRecord-array-response',
until: '3.0'
}));
}
/*
Support primary data as an object instead of an array.
Example
```
{
user: { id: 1, title: 'Tom', manager: 3 }
}
```
*/
if (isPrimary && !Array.isArray(value)) {
let {
data,
included
} = this._normalizePolymorphicRecord(store, value, prop, primaryModelClass, this);
documentHash.data = data;
if (included) {
documentHash.included.push(...included);
}
continue;
}
let {
data,
included
} = this._normalizeArray(store, typeName, value, prop);
if (included) {
documentHash.included.push(...included);
}
if (isSingle) {
data.forEach(resource => {
/*
Figures out if this is the primary record or not.
It's either:
1. The record with the same ID as the original request
2. If it's a newly created record without an ID, the first record
in the array
*/
let isUpdatedRecord = isPrimary && (0, _private.coerceId)(resource.id) === id;
let isFirstCreatedRecord = isPrimary && !id && !documentHash.data;
if (isFirstCreatedRecord || isUpdatedRecord) {
documentHash.data = resource;
} else {
documentHash.included.push(resource);
}
});
} else {
if (isPrimary) {
documentHash.data = data;
} else {
if (data) {
documentHash.included.push(...data);
}
}
}
}
return documentHash;
},
isPrimaryType(store, typeName, primaryTypeClass) {
return store.modelFor(typeName) === primaryTypeClass;
},
/**
This method allows you to push a payload containing top-level
collections of records organized per type.
```js
{
"posts": [{
"id": "1",
"title": "Rails is omakase",
"author", "1",
"comments": [ "1" ]
}],
"comments": [{
"id": "1",
"body": "FIRST"
}],
"users": [{
"id": "1",
"name": "@d2h"
}]
}
```
It will first normalize the payload, so you can use this to push
in data streaming in from your server structured the same way
that fetches and saves are structured.
@method pushPayload
@param {DS.Store} store
@param {Object} payload
*/
pushPayload(store, payload) {
let documentHash = {
data: [],
included: []
};
for (var prop in payload) {
var modelName = this.modelNameFromPayloadKey(prop);
if (!store._hasModelFor(modelName)) {
(true && Ember.warn(this.warnMessageNoModelForKey(prop, modelName), false, {
id: 'ds.serializer.model-for-key-missing'
}));
continue;
}
var type = store.modelFor(modelName);
var typeSerializer = store.serializerFor(type.modelName);
Ember.makeArray(payload[prop]).forEach(hash => {
let {
data,
included
} = typeSerializer.normalize(type, hash, prop);
documentHash.data.push(data);
if (included) {
documentHash.included.push(...included);
}
});
}
store.push(documentHash);
},
/**
This method is used to convert each JSON root key in the payload
into a modelName that it can use to look up the appropriate model for
that part of the payload.
For example, your server may send a model name that does not correspond with
the name of the model in your app. Let's take a look at an example model,
and an example payload:
```app/models/post.js
import Model from '@ember-data/model';
export default Model.extend({
});
```
```javascript
{
"blog/post": {
"id": "1
}
}
```
Ember Data is going to normalize the payload's root key for the modelName. As a result,
it will try to look up the "blog/post" model. Since we don't have a model called "blog/post"
(or a file called app/models/blog/post.js in ember-cli), Ember Data will throw an error
because it cannot find the "blog/post" model.
Since we want to remove this namespace, we can define a serializer for the application that will
remove "blog/" from the payload key whenver it's encountered by Ember Data:
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
export default RESTSerializer.extend({
modelNameFromPayloadKey(payloadKey) {
if (payloadKey === 'blog/post') {
return this._super(payloadKey.replace('blog/', ''));
} else {
return this._super(payloadKey);
}
}
});
```
After refreshing, Ember Data will appropriately look up the "post" model.
By default the modelName for a model is its
name in dasherized form. This means that a payload key like "blogPost" would be
normalized to "blog-post" when Ember Data looks up the model. Usually, Ember Data
can use the correct inflection to do this for you. Most of the time, you won't
need to override `modelNameFromPayloadKey` for this purpose.
@method modelNameFromPayloadKey
@param {String} key
@return {String} the model's modelName
*/
modelNameFromPayloadKey(key) {
return (0, _emberInflector.singularize)((0, _store.normalizeModelName)(key));
},
// SERIALIZE
/**
Called when a record is saved in order to convert the
record into JSON.
By default, it creates a JSON object with a key for
each attribute and belongsTo relationship.
For example, consider this model:
```app/models/comment.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default Model.extend({
title: attr(),
body: attr(),
author: belongsTo('user')
});
```
The default serialization would create a JSON object like:
```js
{
"title": "Rails is unagi",
"body": "Rails? Omakase? O_O",
"author": 12
}
```
By default, attributes are passed through as-is, unless
you specified an attribute type (`attr('date')`). If
you specify a transform, the JavaScript value will be
serialized when inserted into the JSON hash.
By default, belongs-to relationships are converted into
IDs when inserted into the JSON hash.
## IDs
`serialize` takes an options hash with a single option:
`includeId`. If this option is `true`, `serialize` will,
by default include the ID in the JSON object it builds.
The adapter passes in `includeId: true` when serializing
a record for `createRecord`, but not for `updateRecord`.
## Customization
Your server may expect a different JSON format than the
built-in serialization format.
In that case, you can implement `serialize` yourself and
return a JSON hash of your choosing.
```app/serializers/post.js
import RESTSerializer from '@ember-data/serializer/rest';
export default RESTSerializer.extend({
serialize(snapshot, options) {
var json = {
POST_TTL: snapshot.attr('title'),
POST_BDY: snapshot.attr('body'),
POST_CMS: snapshot.hasMany('comments', { ids: true })
};
if (options.includeId) {
json.POST_ID_ = snapshot.id;
}
return json;
}
});
```
## Customizing an App-Wide Serializer
If you want to define a serializer for your entire
application, you'll probably want to use `eachAttribute`
and `eachRelationship` on the record.
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
import { pluralize } from 'ember-inflector';
export default RESTSerializer.extend({
serialize(snapshot, options) {
var json = {};
snapshot.eachAttribute(function(name) {
json[serverAttributeName(name)] = snapshot.attr(name);
});
snapshot.eachRelationship(function(name, relationship) {
if (relationship.kind === 'hasMany') {
json[serverHasManyName(name)] = snapshot.hasMany(name, { ids: true });
}
});
if (options.includeId) {
json.ID_ = snapshot.id;
}
return json;
}
});
function serverAttributeName(attribute) {
return attribute.underscore().toUpperCase();
}
function serverHasManyName(name) {
return serverAttributeName(singularize(name)) + "_IDS";
}
```
This serializer will generate JSON that looks like this:
```js
{
"TITLE": "Rails is omakase",
"BODY": "Yep. Omakase.",
"COMMENT_IDS": [ 1, 2, 3 ]
}
```
## Tweaking the Default JSON
If you just want to do some small tweaks on the default JSON,
you can call super first and make the tweaks on the returned
JSON.
```app/serializers/post.js
import RESTSerializer from '@ember-data/serializer/rest';
export default RESTSerializer.extend({
serialize(snapshot, options) {
var json = this._super(snapshot, options);
json.subject = json.title;
delete json.title;
return json;
}
});
```
@method serialize
@param {DS.Snapshot} snapshot
@param {Object} options
@return {Object} json
*/
serialize(snapshot, options) {
return this._super(...arguments);
},
/**
You can use this method to customize the root keys serialized into the JSON.
The hash property should be modified by reference (possibly using something like _.extend)
By default the REST Serializer sends the modelName of a model, which is a camelized
version of the name.
For example, your server may expect underscored root objects.
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
import { decamelize } from '@ember/string';
export default RESTSerializer.extend({
serializeIntoHash(data, type, record, options) {
var root = decamelize(type.modelName);
data[root] = this.serialize(record, options);
}
});
```
@method serializeIntoHash
@param {Object} hash
@param {DS.Model} typeClass
@param {DS.Snapshot} snapshot
@param {Object} options
*/
serializeIntoHash(hash, typeClass, snapshot, options) {
let normalizedRootKey = this.payloadKeyFromModelName(typeClass.modelName);
hash[normalizedRootKey] = this.serialize(snapshot, options);
},
/**
You can use `payloadKeyFromModelName` to override the root key for an outgoing
request. By default, the RESTSerializer returns a camelized version of the
model's name.
For a model called TacoParty, its `modelName` would be the string `taco-party`. The RESTSerializer
will send it to the server with `tacoParty` as the root key in the JSON payload:
```js
{
"tacoParty": {
"id": "1",
"location": "Matthew Beale's House"
}
}
```
For example, your server may expect dasherized root objects:
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
import { dasherize } from '@ember/string';
export default RESTSerializer.extend({
payloadKeyFromModelName(modelName) {
return dasherize(modelName);
}
});
```
Given a `TacoParty` model, calling `save` on it would produce an outgoing
request like:
```js
{
"taco-party": {
"id": "1",
"location": "Matthew Beale's House"
}
}
```
@method payloadKeyFromModelName
@param {String} modelName
@return {String}
*/
payloadKeyFromModelName(modelName) {
return Ember.String.camelize(modelName);
},
/**
You can use this method to customize how polymorphic objects are serialized.
By default the REST Serializer creates the key by appending `Type` to
the attribute and value from the model's camelcased model name.
@method serializePolymorphicType
@param {DS.Snapshot} snapshot
@param {Object} json
@param {Object} relationship
*/
serializePolymorphicType(snapshot, json, relationship) {
let key = relationship.key;
let typeKey = this.keyForPolymorphicType(key, relationship.type, 'serialize');
let belongsTo = snapshot.belongsTo(key);
if (Ember.isNone(belongsTo)) {
json[typeKey] = null;
} else {
json[typeKey] = Ember.String.camelize(belongsTo.modelName);
}
},
/**
You can use this method to customize how a polymorphic relationship should
be extracted.
@method extractPolymorphicRelationship
@param {Object} relationshipType
@param {Object} relationshipHash
@param {Object} relationshipOptions
@return {Object}
*/
extractPolymorphicRelationship(relationshipType, relationshipHash, relationshipOptions) {
let {
key,
resourceHash,
relationshipMeta
} = relationshipOptions; // A polymorphic belongsTo relationship can be present in the payload
// either in the form where the `id` and the `type` are given:
//
// {
// message: { id: 1, type: 'post' }
// }
//
// or by the `id` and a `Type` attribute:
//
// {
// message: 1,
// messageType: 'post'
// }
//
// The next code checks if the latter case is present and returns the
// corresponding JSON-API representation. The former case is handled within
// the base class JSONSerializer.
let isPolymorphic = relationshipMeta.options.polymorphic;
let typeProperty = this.keyForPolymorphicType(key, relationshipType, 'deserialize');
if (isPolymorphic && resourceHash[typeProperty] !== undefined && typeof relationshipHash !== 'object') {
let type = this.modelNameFromPayloadKey(resourceHash[typeProperty]);
return {
id: relationshipHash,
type: type
};
}
return this._super(...arguments);
}
});
if (true
/* DEBUG */
) {
RESTSerializer.reopen({
warnMessageNoModelForKey(prop, typeKey) {
return 'Encountered "' + prop + '" in payload, but no model was found for model name "' + typeKey + '" (resolved model name using ' + this.constructor.toString() + '.modelNameFromPayloadKey("' + prop + '"))';
}
});
}
var _default = RESTSerializer;
_exports.default = _default;
});
define("@ember-data/serializer/serializer", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
`Serializer` is an abstract base class that you should override in your
application to customize it for your backend. The minimum set of methods
that you should implement is:
* `normalizeResponse()`
* `serialize()`
And you can optionally override the following methods:
* `normalize()`
For an example implementation, see
[DS.JSONSerializer](DS.JSONSerializer), the included JSON serializer.
@class Serializer
@extends EmberObject
*/
var _default = Ember.Object.extend({
/**
The `store` property is the application's `store` that contains
all records. It can be used to look up serializers for other model
types that may be nested inside the payload response.
Example:
```js
Serializer.extend({
extractRelationship(relationshipModelName, relationshipHash) {
var modelClass = this.store.modelFor(relationshipModelName);
var relationshipSerializer = this.store.serializerFor(relationshipModelName);
return relationshipSerializer.normalize(modelClass, relationshipHash);
}
});
```
@property store
@type {DS.Store}
@public
*/
/**
The `normalizeResponse` method is used to normalize a payload from the
server to a JSON-API Document.
http://jsonapi.org/format/#document-structure
Example:
```js
Serializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
if (requestType === 'findRecord') {
return this.normalize(primaryModelClass, payload);
} else {
return payload.reduce(function(documentHash, item) {
let { data, included } = this.normalize(primaryModelClass, item);
documentHash.included.push(...included);
documentHash.data.push(data);
return documentHash;
}, { data: [], included: [] })
}
}
});
```
@since 1.13.0
@method normalizeResponse
@param {DS.Store} store
@param {DS.Model} primaryModelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
normalizeResponse: null,
/**
The `serialize` method is used when a record is saved in order to convert
the record into the form that your external data source expects.
`serialize` takes an optional `options` hash with a single option:
- `includeId`: If this is `true`, `serialize` should include the ID
in the serialized object it builds.
Example:
```js
Serializer.extend({
serialize(snapshot, options) {
var json = {
id: snapshot.id
};
snapshot.eachAttribute((key, attribute) => {
json[key] = snapshot.attr(key);
});
snapshot.eachRelationship((key, relationship) => {
if (relationship.kind === 'belongsTo') {
json[key] = snapshot.belongsTo(key, { id: true });
} else if (relationship.kind === 'hasMany') {
json[key] = snapshot.hasMany(key, { ids: true });
}
});
return json;
},
});
```
@method serialize
@param {DS.Snapshot} snapshot
@param {Object} [options]
@return {Object}
*/
serialize: null,
/**
The `normalize` method is used to convert a payload received from your
external data source into the normalized form `store.push()` expects. You
should override this method, munge the hash and return the normalized
payload.
Example:
```js
Serializer.extend({
normalize(modelClass, resourceHash) {
var data = {
id: resourceHash.id,
type: modelClass.modelName,
attributes: resourceHash
};
return { data: data };
}
})
```
@method normalize
@param {DS.Model} typeClass
@param {Object} hash
@return {Object}
*/
normalize(typeClass, hash) {
return hash;
}
});
_exports.default = _default;
});
define("@ember-data/serializer/transform", ["exports", "@ember-data/serializer/-private"], function (_exports, _private) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
var _default = _private.Transform;
_exports.default = _default;
});
define("@ember-data/serializer/-private/embedded-records-mixin", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
/**
## Using Embedded Records
`EmbeddedRecordsMixin` supports serializing embedded records.
To set up embedded records, include the mixin when extending a serializer,
then define and configure embedded (model) relationships.
Note that embedded records will serialize with the serializer for their model instead of the serializer in which they are defined.
Below is an example of a per-type serializer (`post` type).
```app/serializers/post.js
import RESTSerializer, { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
author: { embedded: 'always' },
comments: { serialize: 'ids' }
}
});
```
Note that this use of `{ embedded: 'always' }` is unrelated to
the `{ embedded: 'always' }` that is defined as an option on `attr` as part of
defining a model while working with the `ActiveModelSerializer`. Nevertheless,
using `{ embedded: 'always' }` as an option to `attr` is not a valid way to set up
embedded records.
The `attrs` option for a resource `{ embedded: 'always' }` is shorthand for:
```js
{
serialize: 'records',
deserialize: 'records'
}
```
### Configuring Attrs
A resource's `attrs` option may be set to use `ids`, `records` or false for the
`serialize` and `deserialize` settings.
The `attrs` property can be set on the `ApplicationSerializer` or a per-type
serializer.
In the case where embedded JSON is expected while extracting a payload (reading)
the setting is `deserialize: 'records'`, there is no need to use `ids` when
extracting as that is the default behaviour without this mixin if you are using
the vanilla `EmbeddedRecordsMixin`. Likewise, to embed JSON in the payload while
serializing `serialize: 'records'` is the setting to use. There is an option of
not embedding JSON in the serialized payload by using `serialize: 'ids'`. If you
do not want the relationship sent at all, you can use `serialize: false`.
### EmbeddedRecordsMixin defaults
If you do not overwrite `attrs` for a specific relationship, the `EmbeddedRecordsMixin`
will behave in the following way:
BelongsTo: `{ serialize: 'id', deserialize: 'id' }`
HasMany: `{ serialize: false, deserialize: 'ids' }`
### Model Relationships
Embedded records must have a model defined to be extracted and serialized. Note that
when defining any relationships on your model such as `belongsTo` and `hasMany`, you
should not both specify `async: true` and also indicate through the serializer's
`attrs` attribute that the related model should be embedded for deserialization.
If a model is declared embedded for deserialization (`embedded: 'always'` or `deserialize: 'records'`),
then do not use `async: true`.
To successfully extract and serialize embedded records the model relationships
must be set up correctly. See the
[defining relationships](https://guides.emberjs.com/current/models/relationships)
section of the **Defining Models** guide page.
Records without an `id` property are not considered embedded records, model
instances must have an `id` property to be used with Ember Data.
### Example JSON payloads, Models and Serializers
**When customizing a serializer it is important to grok what the customizations
are. Please read the docs for the methods this mixin provides, in case you need
to modify it to fit your specific needs.**
For example, review the docs for each method of this mixin:
* [normalize](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_normalize)
* [serializeBelongsTo](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_serializeBelongsTo)
* [serializeHasMany](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_serializeHasMany)
@class EmbeddedRecordsMixin
*/
var _default = Ember.Mixin.create({
/**
Normalize the record and recursively normalize/extract all the embedded records
while pushing them into the store as they are encountered
A payload with an attr configured for embedded records needs to be extracted:
```js
{
"post": {
"id": "1"
"title": "Rails is omakase",
"comments": [{
"id": "1",
"body": "Rails is unagi"
}, {
"id": "2",
"body": "Omakase O_o"
}]
}
}
```
@method normalize
@param {DS.Model} typeClass
@param {Object} hash to be normalized
@param {String} prop the hash has been referenced by
@return {Object} the normalized hash
**/
normalize(typeClass, hash, prop) {
let normalizedHash = this._super(typeClass, hash, prop);
return this._extractEmbeddedRecords(this, this.store, typeClass, normalizedHash);
},
keyForRelationship(key, typeClass, method) {
if (method === 'serialize' && this.hasSerializeRecordsOption(key) || method === 'deserialize' && this.hasDeserializeRecordsOption(key)) {
return this.keyForAttribute(key, method);
} else {
return this._super(key, typeClass, method) || key;
}
},
/**
Serialize `belongsTo` relationship when it is configured as an embedded object.
This example of an author model belongs to a post model:
```js
import Model, { attr, belongsTo } from '@ember-data/model';
Post = Model.extend({
title: attr('string'),
body: attr('string'),
author: belongsTo('author')
});
Author = Model.extend({
name: attr('string'),
post: belongsTo('post')
});
```
Use a custom (type) serializer for the post model to configure embedded author
```app/serializers/post.js
import RESTSerializer, { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
author: { embedded: 'always' }
}
})
```
A payload with an attribute configured for embedded records can serialize
the records together under the root attribute's payload:
```js
{
"post": {
"id": "1"
"title": "Rails is omakase",
"author": {
"id": "2"
"name": "dhh"
}
}
}
```
@method serializeBelongsTo
@param {DS.Snapshot} snapshot
@param {Object} json
@param {Object} relationship
*/
serializeBelongsTo(snapshot, json, relationship) {
let attr = relationship.key;
if (this.noSerializeOptionSpecified(attr)) {
this._super(snapshot, json, relationship);
return;
}
let includeIds = this.hasSerializeIdsOption(attr);
let includeRecords = this.hasSerializeRecordsOption(attr);
let embeddedSnapshot = snapshot.belongsTo(attr);
if (includeIds) {
let serializedKey = this._getMappedKey(relationship.key, snapshot.type);
if (serializedKey === relationship.key && this.keyForRelationship) {
serializedKey = this.keyForRelationship(relationship.key, relationship.kind, 'serialize');
}
if (!embeddedSnapshot) {
json[serializedKey] = null;
} else {
json[serializedKey] = embeddedSnapshot.id;
if (relationship.options.polymorphic) {
this.serializePolymorphicType(snapshot, json, relationship);
}
}
} else if (includeRecords) {
this._serializeEmbeddedBelongsTo(snapshot, json, relationship);
}
},
_serializeEmbeddedBelongsTo(snapshot, json, relationship) {
let embeddedSnapshot = snapshot.belongsTo(relationship.key);
let serializedKey = this._getMappedKey(relationship.key, snapshot.type);
if (serializedKey === relationship.key && this.keyForRelationship) {
serializedKey = this.keyForRelationship(relationship.key, relationship.kind, 'serialize');
}
if (!embeddedSnapshot) {
json[serializedKey] = null;
} else {
json[serializedKey] = embeddedSnapshot.serialize({
includeId: true
});
this.removeEmbeddedForeignKey(snapshot, embeddedSnapshot, relationship, json[serializedKey]);
if (relationship.options.polymorphic) {
this.serializePolymorphicType(snapshot, json, relationship);
}
}
},
/**
Serializes `hasMany` relationships when it is configured as embedded objects.
This example of a post model has many comments:
```js
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
Post = Model.extend({
title: attr('string'),
body: attr('string'),
comments: hasMany('comment')
});
Comment = Model.extend({
body: attr('string'),
post: belongsTo('post')
});
```
Use a custom (type) serializer for the post model to configure embedded comments
```app/serializers/post.js
import RESTSerializer, { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
comments: { embedded: 'always' }
}
})
```
A payload with an attribute configured for embedded records can serialize
the records together under the root attribute's payload:
```js
{
"post": {
"id": "1"
"title": "Rails is omakase",
"body": "I want this for my ORM, I want that for my template language..."
"comments": [{
"id": "1",
"body": "Rails is unagi"
}, {
"id": "2",
"body": "Omakase O_o"
}]
}
}
```
The attrs options object can use more specific instruction for extracting and
serializing. When serializing, an option to embed `ids`, `ids-and-types` or `records` can be set.
When extracting the only option is `records`.
So `{ embedded: 'always' }` is shorthand for:
`{ serialize: 'records', deserialize: 'records' }`
To embed the `ids` for a related object (using a hasMany relationship):
```app/serializers/post.js
import RESTSerializer, { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
comments: { serialize: 'ids', deserialize: 'records' }
}
})
```
```js
{
"post": {
"id": "1"
"title": "Rails is omakase",
"body": "I want this for my ORM, I want that for my template language..."
"comments": ["1", "2"]
}
}
```
To embed the relationship as a collection of objects with `id` and `type` keys, set
`ids-and-types` for the related object.
This is particularly useful for polymorphic relationships where records don't share
the same table and the `id` is not enough information.
For example having a user that has many pets:
```js
User = DS.Model.extend({
name: DS.attr('string'),
pets: DS.hasMany('pet', { polymorphic: true })
});
Pet = DS.Model.extend({
name: DS.attr('string'),
});
Cat = Pet.extend({
// ...
});
Parrot = Pet.extend({
// ...
});
```
```app/serializers/user.js
import RESTSerializer, { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
pets: { serialize: 'ids-and-types', deserialize: 'records' }
}
});
```
```js
{
"user": {
"id": "1"
"name": "Bertin Osborne",
"pets": [
{ "id": "1", "type": "Cat" },
{ "id": "1", "type": "Parrot"}
]
}
}
```
@method serializeHasMany
@param {DS.Snapshot} snapshot
@param {Object} json
@param {Object} relationship
*/
serializeHasMany(snapshot, json, relationship) {
let attr = relationship.key;
if (this.noSerializeOptionSpecified(attr)) {
this._super(snapshot, json, relationship);
return;
}
if (this.hasSerializeIdsOption(attr)) {
let serializedKey = this._getMappedKey(relationship.key, snapshot.type);
if (serializedKey === relationship.key && this.keyForRelationship) {
serializedKey = this.keyForRelationship(relationship.key, relationship.kind, 'serialize');
}
json[serializedKey] = snapshot.hasMany(attr, {
ids: true
});
} else if (this.hasSerializeRecordsOption(attr)) {
this._serializeEmbeddedHasMany(snapshot, json, relationship);
} else {
if (this.hasSerializeIdsAndTypesOption(attr)) {
this._serializeHasManyAsIdsAndTypes(snapshot, json, relationship);
}
}
},
/*
Serializes a hasMany relationship as an array of objects containing only `id` and `type`
keys.
This has its use case on polymorphic hasMany relationships where the server is not storing
all records in the same table using STI, and therefore the `id` is not enough information
TODO: Make the default in Ember-data 3.0??
*/
_serializeHasManyAsIdsAndTypes(snapshot, json, relationship) {
let serializedKey = this.keyForAttribute(relationship.key, 'serialize');
let hasMany = snapshot.hasMany(relationship.key);
json[serializedKey] = Ember.A(hasMany).map(function (recordSnapshot) {
//
// I'm sure I'm being utterly naive here. Propably id is a configurate property and
// type too, and the modelName has to be normalized somehow.
//
return {
id: recordSnapshot.id,
type: recordSnapshot.modelName
};
});
},
_serializeEmbeddedHasMany(snapshot, json, relationship) {
let serializedKey = this._getMappedKey(relationship.key, snapshot.type);
if (serializedKey === relationship.key && this.keyForRelationship) {
serializedKey = this.keyForRelationship(relationship.key, relationship.kind, 'serialize');
}
(true && Ember.warn("The embedded relationship '".concat(serializedKey, "' is undefined for '").concat(snapshot.modelName, "' with id '").concat(snapshot.id, "'. Please include it in your original payload."), Ember.typeOf(snapshot.hasMany(relationship.key)) !== 'undefined', {
id: 'ds.serializer.embedded-relationship-undefined'
}));
json[serializedKey] = this._generateSerializedHasMany(snapshot, relationship);
},
/*
Returns an array of embedded records serialized to JSON
*/
_generateSerializedHasMany(snapshot, relationship) {
let hasMany = snapshot.hasMany(relationship.key);
let manyArray = Ember.A(hasMany);
let ret = new Array(manyArray.length);
for (let i = 0; i < manyArray.length; i++) {
let embeddedSnapshot = manyArray[i];
let embeddedJson = embeddedSnapshot.serialize({
includeId: true
});
this.removeEmbeddedForeignKey(snapshot, embeddedSnapshot, relationship, embeddedJson);
ret[i] = embeddedJson;
}
return ret;
},
/**
When serializing an embedded record, modify the property (in the `JSON` payload)
that refers to the parent record (foreign key for the relationship).
Serializing a `belongsTo` relationship removes the property that refers to the
parent record
Serializing a `hasMany` relationship does not remove the property that refers to
the parent record.
@method removeEmbeddedForeignKey
@param {DS.Snapshot} snapshot
@param {DS.Snapshot} embeddedSnapshot
@param {Object} relationship
@param {Object} json
*/
removeEmbeddedForeignKey(snapshot, embeddedSnapshot, relationship, json) {
if (relationship.kind === 'belongsTo') {
let parentRecord = snapshot.type.inverseFor(relationship.key, this.store);
if (parentRecord) {
let name = parentRecord.name;
let embeddedSerializer = this.store.serializerFor(embeddedSnapshot.modelName);
let parentKey = embeddedSerializer.keyForRelationship(name, parentRecord.kind, 'deserialize');
if (parentKey) {
delete json[parentKey];
}
}
}
/*else if (relationship.kind === 'hasMany') {
return;
}*/
},
// checks config for attrs option to embedded (always) - serialize and deserialize
hasEmbeddedAlwaysOption(attr) {
let option = this.attrsOption(attr);
return option && option.embedded === 'always';
},
// checks config for attrs option to serialize ids
hasSerializeRecordsOption(attr) {
let alwaysEmbed = this.hasEmbeddedAlwaysOption(attr);
let option = this.attrsOption(attr);
return alwaysEmbed || option && option.serialize === 'records';
},
// checks config for attrs option to serialize records
hasSerializeIdsOption(attr) {
let option = this.attrsOption(attr);
return option && (option.serialize === 'ids' || option.serialize === 'id');
},
// checks config for attrs option to serialize records as objects containing id and types
hasSerializeIdsAndTypesOption(attr) {
let option = this.attrsOption(attr);
return option && (option.serialize === 'ids-and-types' || option.serialize === 'id-and-type');
},
// checks config for attrs option to serialize records
noSerializeOptionSpecified(attr) {
let option = this.attrsOption(attr);
return !(option && (option.serialize || option.embedded));
},
// checks config for attrs option to deserialize records
// a defined option object for a resource is treated the same as
// `deserialize: 'records'`
hasDeserializeRecordsOption(attr) {
let alwaysEmbed = this.hasEmbeddedAlwaysOption(attr);
let option = this.attrsOption(attr);
return alwaysEmbed || option && option.deserialize === 'records';
},
attrsOption(attr) {
let attrs = this.get('attrs');
return attrs && (attrs[Ember.String.camelize(attr)] || attrs[attr]);
},
/**
@method _extractEmbeddedRecords
@private
*/
_extractEmbeddedRecords(serializer, store, typeClass, partial) {
typeClass.eachRelationship((key, relationship) => {
if (serializer.hasDeserializeRecordsOption(key)) {
if (relationship.kind === 'hasMany') {
this._extractEmbeddedHasMany(store, key, partial, relationship);
}
if (relationship.kind === 'belongsTo') {
this._extractEmbeddedBelongsTo(store, key, partial, relationship);
}
}
});
return partial;
},
/**
@method _extractEmbeddedHasMany
@private
*/
_extractEmbeddedHasMany(store, key, hash, relationshipMeta) {
let relationshipHash = Ember.get(hash, "data.relationships.".concat(key, ".data"));
if (!relationshipHash) {
return;
}
let hasMany = new Array(relationshipHash.length);
for (let i = 0; i < relationshipHash.length; i++) {
let item = relationshipHash[i];
let {
data,
included
} = this._normalizeEmbeddedRelationship(store, relationshipMeta, item);
hash.included = hash.included || [];
hash.included.push(data);
if (included) {
hash.included.push(...included);
}
hasMany[i] = {
id: data.id,
type: data.type
};
}
let relationship = {
data: hasMany
};
Ember.set(hash, "data.relationships.".concat(key), relationship);
},
/**
@method _extractEmbeddedBelongsTo
@private
*/
_extractEmbeddedBelongsTo(store, key, hash, relationshipMeta) {
let relationshipHash = Ember.get(hash, "data.relationships.".concat(key, ".data"));
if (!relationshipHash) {
return;
}
let {
data,
included
} = this._normalizeEmbeddedRelationship(store, relationshipMeta, relationshipHash);
hash.included = hash.included || [];
hash.included.push(data);
if (included) {
hash.included.push(...included);
}
let belongsTo = {
id: data.id,
type: data.type
};
let relationship = {
data: belongsTo
};
Ember.set(hash, "data.relationships.".concat(key), relationship);
},
/**
@method _normalizeEmbeddedRelationship
@private
*/
_normalizeEmbeddedRelationship(store, relationshipMeta, relationshipHash) {
let modelName = relationshipMeta.type;
if (relationshipMeta.options.polymorphic) {
modelName = relationshipHash.type;
}
let modelClass = store.modelFor(modelName);
let serializer = store.serializerFor(modelName);
return serializer.normalize(modelClass, relationshipHash, null);
},
isEmbeddedRecordsMixin: true
});
_exports.default = _default;
});
define("@ember-data/serializer/-private/index", ["exports", "@ember-data/serializer/-private/embedded-records-mixin", "@ember-data/serializer/-private/utils", "@ember-data/serializer/-private/transforms/transform", "@ember-data/serializer/-private/transforms/boolean", "@ember-data/serializer/-private/transforms/date", "@ember-data/serializer/-private/transforms/number", "@ember-data/serializer/-private/transforms/string"], function (_exports, _embeddedRecordsMixin, _utils, _transform, _boolean, _date, _number, _string) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "EmbeddedRecordsMixin", {
enumerable: true,
get: function () {
return _embeddedRecordsMixin.default;
}
});
Object.defineProperty(_exports, "modelHasAttributeOrRelationshipNamedType", {
enumerable: true,
get: function () {
return _utils.modelHasAttributeOrRelationshipNamedType;
}
});
Object.defineProperty(_exports, "Transform", {
enumerable: true,
get: function () {
return _transform.default;
}
});
Object.defineProperty(_exports, "BooleanTransform", {
enumerable: true,
get: function () {
return _boolean.default;
}
});
Object.defineProperty(_exports, "DateTransform", {
enumerable: true,
get: function () {
return _date.default;
}
});
Object.defineProperty(_exports, "NumberTransform", {
enumerable: true,
get: function () {
return _number.default;
}
});
Object.defineProperty(_exports, "StringTransform", {
enumerable: true,
get: function () {
return _string.default;
}
});
});
define("@ember-data/serializer/-private/utils", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.modelHasAttributeOrRelationshipNamedType = modelHasAttributeOrRelationshipNamedType;
/**
@module @ember-data/serializer
*/
/*
Check if the passed model has a `type` attribute or a relationship named `type`.
@method modelHasAttributeOrRelationshipNamedType
@param modelClass
*/
function modelHasAttributeOrRelationshipNamedType(modelClass) {
return Ember.get(modelClass, 'attributes').has('type') || Ember.get(modelClass, 'relationshipsByName').has('type');
}
});
define("@ember-data/serializer/-private/transforms/boolean", ["exports", "@ember-data/serializer/-private/transforms/transform"], function (_exports, _transform) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
/**
The `BooleanTransform` class is used to serialize and deserialize
boolean attributes on Ember Data record objects. This transform is
used when `boolean` is passed as the type parameter to the
[DS.attr](../../data#method_attr) function.
Usage
```app/models/user.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
isAdmin: attr('boolean'),
name: attr('string'),
email: attr('string')
});
```
By default, the boolean transform only allows for values of `true` or
`false`. You can opt into allowing `null` values for
boolean attributes via `attr('boolean', { allowNull: true })`
```app/models/user.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
email: attr('string'),
username: attr('string'),
wantsWeeklyEmail: attr('boolean', { allowNull: true })
});
```
@class BooleanTransform
@extends Transform
*/
var _default = _transform.default.extend({
deserialize(serialized, options) {
if (Ember.isNone(serialized) && options.allowNull === true) {
return null;
}
let type = typeof serialized;
if (type === 'boolean') {
return serialized;
} else if (type === 'string') {
return /^(true|t|1)$/i.test(serialized);
} else if (type === 'number') {
return serialized === 1;
} else {
return false;
}
},
serialize(deserialized, options) {
if (Ember.isNone(deserialized) && options.allowNull === true) {
return null;
}
return Boolean(deserialized);
}
});
_exports.default = _default;
});
define("@ember-data/serializer/-private/transforms/date", ["exports", "@ember-data/serializer/-private/transforms/transform"], function (_exports, _transform) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
/**
The `DateTransform` class is used to serialize and deserialize
date attributes on Ember Data record objects. This transform is used
when `date` is passed as the type parameter to the
[DS.attr](../../data#method_attr) function. It uses the [`ISO 8601`](https://en.wikipedia.org/wiki/ISO_8601)
standard.
```app/models/score.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default Model.extend({
value: attr('number'),
player: belongsTo('player'),
date: attr('date')
});
```
@class DateTransform
@extends Transform
*/
var _default = _transform.default.extend({
deserialize(serialized) {
let type = typeof serialized;
if (type === 'string') {
let offset = serialized.indexOf('+');
if (offset !== -1 && serialized.length - 5 === offset) {
offset += 3;
return new Date(serialized.slice(0, offset) + ':' + serialized.slice(offset));
}
return new Date(serialized);
} else if (type === 'number') {
return new Date(serialized);
} else if (serialized === null || serialized === undefined) {
// if the value is null return null
// if the value is not present in the data return undefined
return serialized;
} else {
return null;
}
},
serialize(date) {
if (date instanceof Date && !isNaN(date)) {
return date.toISOString();
} else {
return null;
}
}
});
_exports.default = _default;
});
define("@ember-data/serializer/-private/transforms/number", ["exports", "@ember-data/serializer/-private/transforms/transform"], function (_exports, _transform) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
function isNumber(value) {
return value === value && value !== Infinity && value !== -Infinity;
}
/**
The `NumberTransform` class is used to serialize and deserialize
numeric attributes on Ember Data record objects. This transform is
used when `number` is passed as the type parameter to the
[DS.attr](../../data#method_attr) function.
Usage
```app/models/score.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default Model.extend({
value: attr('number'),
player: belongsTo('player'),
date: attr('date')
});
```
@class NumberTransform
@extends Transform
*/
var _default = _transform.default.extend({
deserialize(serialized) {
let transformed;
if (serialized === '' || serialized === null || serialized === undefined) {
return null;
} else {
transformed = Number(serialized);
return isNumber(transformed) ? transformed : null;
}
},
serialize(deserialized) {
let transformed;
if (deserialized === '' || deserialized === null || deserialized === undefined) {
return null;
} else {
transformed = Number(deserialized);
return isNumber(transformed) ? transformed : null;
}
}
});
_exports.default = _default;
});
define("@ember-data/serializer/-private/transforms/string", ["exports", "@ember-data/serializer/-private/transforms/transform"], function (_exports, _transform) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
/**
The `StringTransform` class is used to serialize and deserialize
string attributes on Ember Data record objects. This transform is
used when `string` is passed as the type parameter to the
[DS.attr](./DS/methods/attr?anchor=attr) function.
Usage
```app/models/user.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default Model.extend({
isAdmin: attr('boolean'),
name: attr('string'),
email: attr('string')
});
```
@class StringTransform
@extends Transform
*/
var _default = _transform.default.extend({
deserialize(serialized) {
return Ember.isNone(serialized) ? null : String(serialized);
},
serialize(deserialized) {
return Ember.isNone(deserialized) ? null : String(deserialized);
}
});
_exports.default = _default;
});
define("@ember-data/serializer/-private/transforms/transform", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/serializer
*/
/**
The `Transform` class is used to serialize and deserialize model
attributes when they are saved or loaded from an
adapter. Subclassing `Transform` is useful for creating custom
attributes. All subclasses of `Transform` must implement a
`serialize` and a `deserialize` method.
Example
```app/transforms/temperature.js
import Transform from '@ember-data/serializer/transform';
// Converts centigrade in the JSON to fahrenheit in the app
export default Transform.extend({
deserialize(serialized, options) {
return (serialized * 1.8) + 32;
},
serialize(deserialized, options) {
return (deserialized - 32) / 1.8;
}
});
```
Usage
```app/models/requirement.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
name: attr('string'),
temperature: attr('temperature')
});
```
The options passed into the `attr` function when the attribute is
declared on the model is also available in the transform.
```app/models/post.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
title: attr('string'),
markdown: attr('markdown', {
markdown: {
gfm: false,
sanitize: true
}
})
});
```
```app/transforms/markdown.js
import Transform from '@ember-data/serializer/transform';
export default Transform.extend({
serialize(deserialized, options) {
return deserialized.raw;
},
deserialize(serialized, options) {
var markdownOptions = options.markdown || {};
return marked(serialized, markdownOptions);
}
});
```
@class Transform
*/
var _default = Ember.Object.extend({
/**
When given a deserialized value from a record attribute this
method must return the serialized value.
Example
```javascript
import { isEmpty } from '@ember/utils';
serialize(deserialized, options) {
return isEmpty(deserialized) ? null : Number(deserialized);
}
```
@method serialize
@param deserialized The deserialized value
@param options hash of options passed to `attr`
@return The serialized value
*/
serialize: null,
/**
When given a serialized value from a JSON object this method must
return the deserialized value for the record attribute.
Example
```javascript
deserialize(serialized, options) {
return empty(serialized) ? null : Number(serialized);
}
```
@method deserialize
@param serialized The serialized value
@param options hash of options passed to `attr`
@return The deserialized value
*/
deserialize: null
});
_exports.default = _default;
});
define("@ember-data/store/index", ["exports", "@ember-data/store/-private"], function (_exports, _private) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _private.Store;
}
});
Object.defineProperty(_exports, "normalizeModelName", {
enumerable: true,
get: function () {
return _private.normalizeModelName;
}
});
Object.defineProperty(_exports, "setIdentifierGenerationMethod", {
enumerable: true,
get: function () {
return _private.setIdentifierGenerationMethod;
}
});
Object.defineProperty(_exports, "setIdentifierUpdateMethod", {
enumerable: true,
get: function () {
return _private.setIdentifierUpdateMethod;
}
});
Object.defineProperty(_exports, "setIdentifierForgetMethod", {
enumerable: true,
get: function () {
return _private.setIdentifierForgetMethod;
}
});
Object.defineProperty(_exports, "setIdentifierResetMethod", {
enumerable: true,
get: function () {
return _private.setIdentifierResetMethod;
}
});
Object.defineProperty(_exports, "recordIdentifierFor", {
enumerable: true,
get: function () {
return _private.recordIdentifierFor;
}
});
});
define("@ember-data/store/-private/index", ["exports", "@ember-data/store/-private/system/model/model", "@ember-data/store/-private/system/model/errors", "@ember-data/store/-private/system/store", "@ember-data/store/-private/system/store/internal-model-factory", "@ember-data/store/-private/system/snapshot", "@ember-data/store/-private/identifiers/cache", "@ember-data/store/-private/system/record-data-for", "@ember-data/store/-private/system/normalize-model-name", "@ember-data/store/-private/system/coerce-id", "@ember-data/store/-private/system/errors-utils", "@ember-data/store/-private/system/model/states", "@ember-data/store/-private/system/model/internal-model", "@ember-data/store/-private/system/model/record-data", "@ember-data/store/-private/system/promise-proxies", "@ember-data/store/-private/system/record-arrays", "@ember-data/store/-private/system/many-array", "@ember-data/store/-private/system/record-array-manager", "@ember-data/store/-private/system/relationships/state/relationship", "@ember-data/store/-private/system/diff-array", "@ember-data/store/-private/system/snapshot-record-array", "@ember-data/store/-private/system/ordered-set", "@ember-data/store/-private/system/store/common"], function (_exports, _model, _errors, _store, _internalModelFactory, _snapshot, _cache, _recordDataFor, _normalizeModelName, _coerceId, _errorsUtils, _states, _internalModel, _recordData, _promiseProxies, _recordArrays, _manyArray, _recordArrayManager, _relationship, _diffArray, _snapshotRecordArray, _orderedSet, _common) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "Model", {
enumerable: true,
get: function () {
return _model.default;
}
});
Object.defineProperty(_exports, "Errors", {
enumerable: true,
get: function () {
return _errors.default;
}
});
Object.defineProperty(_exports, "Store", {
enumerable: true,
get: function () {
return _store.default;
}
});
Object.defineProperty(_exports, "recordIdentifierFor", {
enumerable: true,
get: function () {
return _internalModelFactory.recordIdentifierFor;
}
});
Object.defineProperty(_exports, "Snapshot", {
enumerable: true,
get: function () {
return _snapshot.default;
}
});
Object.defineProperty(_exports, "identifierCacheFor", {
enumerable: true,
get: function () {
return _cache.identifierCacheFor;
}
});
Object.defineProperty(_exports, "setIdentifierGenerationMethod", {
enumerable: true,
get: function () {
return _cache.setIdentifierGenerationMethod;
}
});
Object.defineProperty(_exports, "setIdentifierUpdateMethod", {
enumerable: true,
get: function () {
return _cache.setIdentifierUpdateMethod;
}
});
Object.defineProperty(_exports, "setIdentifierForgetMethod", {
enumerable: true,
get: function () {
return _cache.setIdentifierForgetMethod;
}
});
Object.defineProperty(_exports, "setIdentifierResetMethod", {
enumerable: true,
get: function () {
return _cache.setIdentifierResetMethod;
}
});
Object.defineProperty(_exports, "recordDataFor", {
enumerable: true,
get: function () {
return _recordDataFor.default;
}
});
Object.defineProperty(_exports, "relationshipStateFor", {
enumerable: true,
get: function () {
return _recordDataFor.relationshipStateFor;
}
});
Object.defineProperty(_exports, "relationshipsFor", {
enumerable: true,
get: function () {
return _recordDataFor.relationshipsFor;
}
});
Object.defineProperty(_exports, "normalizeModelName", {
enumerable: true,
get: function () {
return _normalizeModelName.default;
}
});
Object.defineProperty(_exports, "coerceId", {
enumerable: true,
get: function () {
return _coerceId.default;
}
});
Object.defineProperty(_exports, "errorsHashToArray", {
enumerable: true,
get: function () {
return _errorsUtils.errorsHashToArray;
}
});
Object.defineProperty(_exports, "errorsArrayToHash", {
enumerable: true,
get: function () {
return _errorsUtils.errorsArrayToHash;
}
});
Object.defineProperty(_exports, "RootState", {
enumerable: true,
get: function () {
return _states.default;
}
});
Object.defineProperty(_exports, "InternalModel", {
enumerable: true,
get: function () {
return _internalModel.default;
}
});
Object.defineProperty(_exports, "RecordData", {
enumerable: true,
get: function () {
return _recordData.default;
}
});
Object.defineProperty(_exports, "PromiseArray", {
enumerable: true,
get: function () {
return _promiseProxies.PromiseArray;
}
});
Object.defineProperty(_exports, "PromiseObject", {
enumerable: true,
get: function () {
return _promiseProxies.PromiseObject;
}
});
Object.defineProperty(_exports, "PromiseManyArray", {
enumerable: true,
get: function () {
return _promiseProxies.PromiseManyArray;
}
});
Object.defineProperty(_exports, "RecordArray", {
enumerable: true,
get: function () {
return _recordArrays.RecordArray;
}
});
Object.defineProperty(_exports, "AdapterPopulatedRecordArray", {
enumerable: true,
get: function () {
return _recordArrays.AdapterPopulatedRecordArray;
}
});
Object.defineProperty(_exports, "ManyArray", {
enumerable: true,
get: function () {
return _manyArray.default;
}
});
Object.defineProperty(_exports, "RecordArrayManager", {
enumerable: true,
get: function () {
return _recordArrayManager.default;
}
});
Object.defineProperty(_exports, "Relationship", {
enumerable: true,
get: function () {
return _relationship.default;
}
});
Object.defineProperty(_exports, "diffArray", {
enumerable: true,
get: function () {
return _diffArray.default;
}
});
Object.defineProperty(_exports, "SnapshotRecordArray", {
enumerable: true,
get: function () {
return _snapshotRecordArray.default;
}
});
Object.defineProperty(_exports, "OrderedSet", {
enumerable: true,
get: function () {
return _orderedSet.default;
}
});
Object.defineProperty(_exports, "_bind", {
enumerable: true,
get: function () {
return _common._bind;
}
});
Object.defineProperty(_exports, "_guard", {
enumerable: true,
get: function () {
return _common._guard;
}
});
Object.defineProperty(_exports, "_objectIsAlive", {
enumerable: true,
get: function () {
return _common._objectIsAlive;
}
});
Object.defineProperty(_exports, "guardDestroyedStore", {
enumerable: true,
get: function () {
return _common.guardDestroyedStore;
}
});
});
define("@ember-data/store/-private/identifiers/cache", ["exports", "@ember-data/store/-private/ts-interfaces/identifier", "@ember-data/store/-private/system/coerce-id", "@ember-data/store/-private/identifiers/utils/uuid-v4", "@ember-data/store/-private/system/normalize-model-name", "@ember-data/store/-private/identifiers/is-stable-identifier", "@ember-data/store/-private/utils/is-non-empty-string"], function (_exports, _identifier, _coerceId, _uuidV, _normalizeModelName, _isStableIdentifier, _isNonEmptyString) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.setIdentifierGenerationMethod = setIdentifierGenerationMethod;
_exports.setIdentifierUpdateMethod = setIdentifierUpdateMethod;
_exports.setIdentifierForgetMethod = setIdentifierForgetMethod;
_exports.setIdentifierResetMethod = setIdentifierResetMethod;
_exports.identifierCacheFor = identifierCacheFor;
_exports.IdentifierCache = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
let configuredForgetMethod;
let configuredGenerationMethod;
let configuredResetMethod;
let configuredUpdateMethod;
function setIdentifierGenerationMethod(method) {
configuredGenerationMethod = method;
}
function setIdentifierUpdateMethod(method) {
configuredUpdateMethod = method;
}
function setIdentifierForgetMethod(method) {
configuredForgetMethod = method;
}
function setIdentifierResetMethod(method) {
configuredResetMethod = method;
}
function defaultGenerationMethod(data, bucket) {
if ((0, _isNonEmptyString.default)(data.lid)) {
return data.lid;
}
let {
type,
id
} = data;
if ((0, _isNonEmptyString.default)(id)) {
return "@ember-data:lid-".concat((0, _normalizeModelName.default)(type), "-").concat(id);
}
return (0, _uuidV.default)();
}
const IdentifierCaches = new WeakMap();
function identifierCacheFor(store) {
let cache = IdentifierCaches.get(store);
if (cache === undefined) {
cache = new IdentifierCache();
IdentifierCaches.set(store, cache);
}
return cache;
}
function defaultEmptyCallback(...args) {}
let DEBUG_MAP;
if (true
/* DEBUG */
) {
DEBUG_MAP = new WeakMap();
}
class IdentifierCache {
// Typescript still leaks private properties in the final
// compiled class, so we may want to move these from _underscore
// to a WeakMap to avoid leaking
// currently we leak this for test purposes
constructor() {
_defineProperty(this, "_cache", {
lids: Object.create(null),
types: Object.create(null)
});
_defineProperty(this, "_generate", void 0);
_defineProperty(this, "_update", void 0);
_defineProperty(this, "_forget", void 0);
_defineProperty(this, "_reset", void 0);
_defineProperty(this, "_merge", void 0);
// we cache the user configuredGenerationMethod at init because it must
// be configured prior and is not allowed to be changed
this._generate = configuredGenerationMethod || defaultGenerationMethod;
this._update = configuredUpdateMethod || defaultEmptyCallback;
this._forget = configuredForgetMethod || defaultEmptyCallback;
this._reset = configuredResetMethod || defaultEmptyCallback;
this._merge = defaultEmptyCallback;
}
/**
* hook to allow management of merge conflicts with identifiers.
*
* we allow late binding of this private internal merge so that `internalModelFactory`
* can insert itself here to handle elimination of duplicates
*
* @internal
*/
__configureMerge(method) {
this._merge = method || defaultEmptyCallback;
}
/**
* @internal
*/
_getRecordIdentifier(resource, shouldGenerate = false) {
// short circuit if we're already the stable version
if ((0, _isStableIdentifier.default)(resource)) {
if (true
/* DEBUG */
) {
// TODO should we instead just treat this case as a new generation skipping the short circuit?
if (!(resource.lid in this._cache.lids) || this._cache.lids[resource.lid] !== resource) {
throw new Error("The supplied identifier ".concat(resource, " does not belong to this store instance"));
}
}
return resource;
} // `type` must always be present
if (true
/* DEBUG */
) {
if (!(0, _isNonEmptyString.default)(resource.type)) {
throw new Error('resource.type needs to be a string');
}
}
let type = (0, _normalizeModelName.default)(resource.type);
let keyOptions = getTypeIndex(this._cache.types, type);
let identifier;
let lid = (0, _coerceId.default)(resource.lid);
let id = (0, _coerceId.default)(resource.id); // go straight for the stable RecordIdentifier key'd to `lid`
if (lid !== null) {
identifier = keyOptions.lid[lid];
} // we may have not seen this resource before
// but just in case we check our own secondary lookup (`id`)
if (identifier === undefined && id !== null) {
identifier = keyOptions.id[id];
}
if (identifier === undefined) {
// we have definitely not seen this resource before
// so we allow the user configured `GenerationMethod` to tell us
let newLid = this._generate(resource, 'record'); // we do this _even_ when `lid` is present because secondary lookups
// may need to be populated, but we enforce not giving us something
// different than expected
if (lid !== null && newLid !== lid) {
throw new Error("You should not change the of a RecordIdentifier");
} else if (lid === null) {
// allow configuration to tell us that we have
// seen this `lid` before. E.g. a secondary lookup
// connects this resource to a previously seen
// resource.
identifier = keyOptions.lid[newLid];
}
if (shouldGenerate === true) {
if (identifier === undefined) {
// if we still don't have an identifier, time to generate one
identifier = makeStableRecordIdentifier(id, type, newLid, 'record', false); // populate our unique table
if (true
/* DEBUG */
) {
// realistically if you hit this it means you changed `type` :/
// TODO consider how to handle type change assertions more gracefully
if (identifier.lid in this._cache.lids) {
throw new Error("You should not change the of a RecordIdentifier");
}
}
this._cache.lids[identifier.lid] = identifier; // populate our primary lookup table
// TODO consider having the `lid` cache be
// one level up
keyOptions.lid[identifier.lid] = identifier; // TODO exists temporarily to support `peekAll`
// but likely to move
keyOptions._allIdentifiers.push(identifier);
} // populate our own secondary lookup table
// even for the "successful" secondary lookup
// by `_generate()`, since we missed the cache
// previously
// we use identifier.id instead of id here
// because they may not match and we prefer
// what we've set via resource data
if (identifier.id !== null) {
keyOptions.id[identifier.id] = identifier; // TODO allow filling out of `id` here
// for the `username` non-client created
// case.
}
}
}
return identifier;
}
/**
* allows us to peek without generating when needed
* useful for the "create" case when we need to see if
* we are accidentally overwritting something
*
* @internal
*/
peekRecordIdentifier(resource) {
return this._getRecordIdentifier(resource, false);
}
/*
Returns the Identifier for the given Resource, creates one if it does not yet exist.
Specifically this means that we:
- validate the `id` `type` and `lid` combo against known identifiers
- return an object with an `lid` that is stable (repeated calls with the same
`id` + `type` or `lid` will return the same `lid` value)
- this referential stability of the object itself is guaranteed
*/
getOrCreateRecordIdentifier(resource) {
return this._getRecordIdentifier(resource, true);
}
/*
Returns a new Identifier for the supplied data. Call this method to generate
an identifier when a new resource is being created local to the client and
potentially does not have an `id`.
Delegates generation to the user supplied `GenerateMethod` if one has been provided
with the signature `generateMethod({ type }, 'record')`.
*/
createIdentifierForNewRecord(data) {
let newLid = this._generate(data, 'record');
let identifier = makeStableRecordIdentifier(data.id || null, data.type, newLid, 'record', true);
let keyOptions = getTypeIndex(this._cache.types, data.type); // populate our unique table
if (true
/* DEBUG */
) {
if (identifier.lid in this._cache.lids) {
throw new Error("The lid generated for the new record is not unique as it matches an existing identifier");
}
}
this._cache.lids[identifier.lid] = identifier; // populate the type+lid cache
keyOptions.lid[newLid] = identifier; // ensure a peekAll sees our new identifier too
// TODO move this outta here?
keyOptions._allIdentifiers.push(identifier);
return identifier;
}
/*
Provides the opportunity to update secondary lookup tables for existing identifiers
Called after an identifier created with `createIdentifierForNewRecord` has been
committed.
Assigned `id` to an `Identifier` if `id` has not previously existed; however,
attempting to change the `id` or calling update without providing an `id` when
one is missing will throw an error.
- sets `id` (if `id` was previously `null`)
- `lid` and `type` MUST NOT be altered post creation
If a merge occurs, it is possible the returned identifier does not match the originally
provided identifier. In this case the abandoned identifier will go through the usual
`forgetRecordIdentifier` codepaths.
*/
updateRecordIdentifier(identifierObject, data) {
let identifier = this.getOrCreateRecordIdentifier(identifierObject);
let id = identifier.id;
let newId = (0, _coerceId.default)(data.id);
const keyOptions = getTypeIndex(this._cache.types, identifier.type);
let existingIdentifier = detectMerge(keyOptions, identifier, newId);
if (existingIdentifier) {
identifier = this._mergeRecordIdentifiers(keyOptions, identifier, existingIdentifier, data, newId);
}
id = identifier.id;
performRecordIdentifierUpdate(identifier, data, this._update);
newId = identifier.id; // add to our own secondary lookup table
if (id !== newId && newId !== null) {
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
keyOptions.id[newId] = identifier;
if (id !== null) {
delete keyOptions.id[id];
}
}
return identifier;
}
_mergeRecordIdentifiers(keyOptions, identifier, existingIdentifier, data, newId) {
// delegate determining which identifier to keep to the configured MergeMethod
let kept = this._merge(identifier, existingIdentifier, data);
let abandoned = kept === identifier ? existingIdentifier : identifier; // cleanup the identifier we no longer need
this.forgetRecordIdentifier(abandoned); // ensure a secondary cache entry for this id for the identifier we do keep
keyOptions.id[newId] = kept; // make sure that the `lid` on the data we are processing matches the lid we kept
data.lid = kept.lid;
return kept;
}
/*
Provides the opportunity to eliminate an identifier from secondary lookup tables
as well as eliminates it from ember-data's own lookup tables and book keeping.
Useful when a record has been deleted and the deletion has been persisted and
we do not care about the record anymore. Especially useful when an `id` of a
deleted record might be reused later for a new record.
*/
forgetRecordIdentifier(identifierObject) {
let identifier = this.getOrCreateRecordIdentifier(identifierObject);
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
if (identifier.id !== null) {
delete keyOptions.id[identifier.id];
}
delete this._cache.lids[identifier.lid];
delete keyOptions.lid[identifier.lid];
let index = keyOptions._allIdentifiers.indexOf(identifier);
keyOptions._allIdentifiers.splice(index, 1);
this._forget(identifier, 'record');
}
destroy() {
this._reset();
}
}
_exports.IdentifierCache = IdentifierCache;
function getTypeIndex(typeMap, type) {
let typeIndex = typeMap[type];
if (typeIndex === undefined) {
typeIndex = {
lid: Object.create(null),
id: Object.create(null),
_allIdentifiers: []
};
typeMap[type] = typeIndex;
}
return typeIndex;
}
function makeStableRecordIdentifier(id, type, lid, bucket, clientOriginated = false) {
let recordIdentifier = {
[_identifier.IS_IDENTIFIER]: true,
lid,
id,
type
};
if (true
/* DEBUG */
) {
// we enforce immutability in dev
// but preserve our ability to do controlled updates to the reference
let wrapper = Object.freeze({
[_identifier.IS_IDENTIFIER]: true,
[_identifier.DEBUG_CLIENT_ORIGINATED]: clientOriginated,
[_identifier.DEBUG_IDENTIFIER_BUCKET]: bucket,
get lid() {
return recordIdentifier.lid;
},
get id() {
return recordIdentifier.id;
},
get type() {
return recordIdentifier.type;
},
toString() {
let {
type,
id,
lid
} = recordIdentifier;
return "".concat(clientOriginated ? '[CLIENT_ORIGINATED] ' : '').concat(type, ":").concat(id, " (").concat(lid, ")");
}
});
DEBUG_MAP.set(wrapper, recordIdentifier);
return wrapper;
}
return recordIdentifier;
}
function performRecordIdentifierUpdate(identifier, data, updateFn) {
let {
id,
lid
} = data;
let type = (0, _normalizeModelName.default)(data.type);
if (true
/* DEBUG */
) {
// get the mutable instance behind our proxy wrapper
let wrapper = identifier;
identifier = DEBUG_MAP.get(wrapper);
if (lid !== undefined) {
let newLid = (0, _coerceId.default)(lid);
if (newLid !== identifier.lid) {
throw new Error("The 'lid' for a RecordIdentifier cannot be updated once it has been created. Attempted to set lid for '".concat(wrapper, "' to '").concat(lid, "'."));
}
}
if (id !== undefined) {
let newId = (0, _coerceId.default)(id);
if (identifier.id !== null && identifier.id !== newId) {
// here we warn and ignore, as this may be a mistake, but we allow the user
// to have multiple cache-keys pointing at a single lid so we cannot error
(true && Ember.warn("The 'id' for a RecordIdentifier should not be updated once it has been set. Attempted to set id for '".concat(wrapper, "' to '").concat(newId, "'."), false, {
id: 'ember-data:multiple-ids-for-identifier'
}));
}
} // TODO consider just ignoring here to allow flexible polymorphic support
if (type !== identifier.type) {
throw new Error("The 'type' for a RecordIdentifier cannot be updated once it has been set. Attempted to set type for '".concat(wrapper, "' to '").concat(type, "'."));
}
updateFn(wrapper, data, 'record');
} else {
updateFn(identifier, data, 'record');
} // upgrade the ID, this is a "one time only" ability
// for the multiple-cache-key scenario we "could"
// use a heuristic to guess the best id for display
// (usually when `data.id` is available and `data.attributes` is not)
if (id !== undefined) {
identifier.id = (0, _coerceId.default)(id);
}
}
function detectMerge(keyOptions, identifier, newId) {
const {
id
} = identifier;
if (id !== null && id !== newId && newId !== null) {
const existingIdentifier = keyOptions.id[newId];
return existingIdentifier !== undefined ? existingIdentifier : false;
}
return false;
}
});
define("@ember-data/store/-private/identifiers/is-stable-identifier", ["exports", "@ember-data/store/-private/ts-interfaces/identifier"], function (_exports, _identifier) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = isStableIdentifier;
/**
@module @ember-data/store
*/
function isStableIdentifier(identifier) {
return identifier[_identifier.IS_IDENTIFIER] === true;
}
});
define("@ember-data/store/-private/system/backburner", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
const backburner = new Ember._Backburner(['normalizeRelationships', 'syncRelationships', 'finished']);
if (true
/* DEBUG */
) {
Ember.Test.registerWaiter(() => {
return !backburner.currentInstance && !backburner.hasTimers();
});
}
var _default = backburner;
_exports.default = _default;
});
define("@ember-data/store/-private/system/clone-null", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = cloneNull;
/**
@module @ember-data/store
*/
function cloneNull(source) {
let clone = Object.create(null);
for (let key in source) {
clone[key] = source[key];
}
return clone;
}
});
define("@ember-data/store/-private/system/coerce-id", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.ensureStringId = ensureStringId;
_exports.default = void 0;
/**
@module @ember-data/store
*/
// Used by the store to normalize IDs entering the store. Despite the fact
// that developers may provide IDs as numbers (e.g., `store.findRecord('person', 1)`),
// it is important that internally we use strings, since IDs may be serialized
// and lose type information. For example, Ember's router may put a record's
// ID into the URL, and if we later try to deserialize that URL and find the
// corresponding record, we will not know if it is a string or a number.
function coerceId(id) {
if (id === null || id === undefined || id === '') {
return null;
}
if (typeof id === 'string') {
return id;
}
if (typeof id === 'symbol') {
return id.toString();
}
return '' + id;
}
function ensureStringId(id) {
let normalized = null;
if (typeof id === 'string') {
normalized = id.length > 0 ? id : null;
} else if (typeof id === 'number' && !isNaN(id)) {
normalized = '' + id;
}
if (true
/* DEBUG */
&& normalized === null) {
throw new Error("Expected id to be a string or number, recieved ".concat(String(id)));
}
return normalized;
}
var _default = coerceId;
_exports.default = _default;
});
define("@ember-data/store/-private/system/deprecated-evented", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
let INSTANCE_DEPRECATIONS;
let lookupDeprecations;
let DeprecatedEvented;
if (true
/* DEBUG */
) {
INSTANCE_DEPRECATIONS = new WeakMap();
lookupDeprecations = function lookupInstanceDrecations(instance) {
let deprecations = INSTANCE_DEPRECATIONS.get(instance);
if (!deprecations) {
deprecations = {};
INSTANCE_DEPRECATIONS.set(instance, deprecations);
}
return deprecations;
};
DeprecatedEvented = Ember.Mixin.create(Ember.Evented, {
/**
* Provides a way to call Evented without logging deprecation warnings
* @param {String} name
*/
_has(name) {
return Ember.Evented.mixins[0].properties.has.call(this, name);
},
_on() {
return Ember.Evented.mixins[0].properties.on.call(this, ...arguments);
},
_deprecateEvented(eventName) {
let deprecations = lookupDeprecations(this);
const _deprecationData = this._getDeprecatedEventedInfo ? "on ".concat(this._getDeprecatedEventedInfo()) : '';
const deprecationMessage = _deprecationData ? "Called ".concat(eventName, " ").concat(_deprecationData) : eventName;
(true && !(deprecations[eventName]) && Ember.deprecate(deprecationMessage, deprecations[eventName], {
id: 'ember-data:evented-api-usage',
until: '4.0'
}));
deprecations[eventName] = true;
},
has(name) {
this._deprecateEvented(name);
return this._super(...arguments);
},
off(name, target, method) {
this._deprecateEvented(name);
return this._super(...arguments);
},
on(name, target, method) {
this._deprecateEvented(name);
return this._super(...arguments);
},
one(name, target, method) {
this._deprecateEvented(name);
return this._super(...arguments);
},
trigger(name) {
this._deprecateEvented(name);
return this._super(...arguments);
}
});
}
var _default = true
/* DEBUG */
? DeprecatedEvented : Ember.Evented;
_exports.default = _default;
});
define("@ember-data/store/-private/system/diff-array", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = diffArray;
/**
@module @ember-data/store
*/
/*
@method diffArray
@private
@param {Array} oldArray the old array
@param {Array} newArray the new array
@return {hash} {
firstChangeIndex: , // null if no change
addedCount: , // 0 if no change
removedCount: // 0 if no change
}
*/
function diffArray(oldArray, newArray) {
const oldLength = oldArray.length;
const newLength = newArray.length;
const shortestLength = Math.min(oldLength, newLength);
let firstChangeIndex = null; // null signifies no changes
// find the first change
for (let i = 0; i < shortestLength; i++) {
// compare each item in the array
if (oldArray[i] !== newArray[i]) {
firstChangeIndex = i;
break;
}
}
if (firstChangeIndex === null && newLength !== oldLength) {
// no change found in the overlapping block
// and array lengths differ,
// so change starts at end of overlap
firstChangeIndex = shortestLength;
}
let addedCount = 0;
let removedCount = 0;
if (firstChangeIndex !== null) {
// we found a change, find the end of the change
let unchangedEndBlockLength = shortestLength - firstChangeIndex; // walk back from the end of both arrays until we find a change
for (let i = 1; i <= shortestLength; i++) {
// compare each item in the array
if (oldArray[oldLength - i] !== newArray[newLength - i]) {
unchangedEndBlockLength = i - 1;
break;
}
}
addedCount = newLength - unchangedEndBlockLength - firstChangeIndex;
removedCount = oldLength - unchangedEndBlockLength - firstChangeIndex;
}
return {
firstChangeIndex,
addedCount,
removedCount
};
}
});
define("@ember-data/store/-private/system/errors-utils", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.errorsHashToArray = errorsHashToArray;
_exports.errorsArrayToHash = errorsArrayToHash;
/**
@module @ember-data/store
*/
const SOURCE_POINTER_REGEXP = /^\/?data\/(attributes|relationships)\/(.*)/;
const SOURCE_POINTER_PRIMARY_REGEXP = /^\/?data/;
const PRIMARY_ATTRIBUTE_KEY = 'base';
/**
Convert an hash of errors into an array with errors in JSON-API format.
```javascript
import DS from 'ember-data';
const { errorsHashToArray } = DS;
let errors = {
base: 'Invalid attributes on saving this record',
name: 'Must be present',
age: ['Must be present', 'Must be a number']
};
let errorsArray = errorsHashToArray(errors);
// [
// {
// title: "Invalid Document",
// detail: "Invalid attributes on saving this record",
// source: { pointer: "/data" }
// },
// {
// title: "Invalid Attribute",
// detail: "Must be present",
// source: { pointer: "/data/attributes/name" }
// },
// {
// title: "Invalid Attribute",
// detail: "Must be present",
// source: { pointer: "/data/attributes/age" }
// },
// {
// title: "Invalid Attribute",
// detail: "Must be a number",
// source: { pointer: "/data/attributes/age" }
// }
// ]
```
@method errorsHashToArray
@public
@param {Object} errors hash with errors as properties
@return {Array} array of errors in JSON-API format
*/
function errorsHashToArray(errors) {
let out = [];
if (Ember.isPresent(errors)) {
Object.keys(errors).forEach(key => {
let messages = Ember.makeArray(errors[key]);
for (let i = 0; i < messages.length; i++) {
let title = 'Invalid Attribute';
let pointer = "/data/attributes/".concat(key);
if (key === PRIMARY_ATTRIBUTE_KEY) {
title = 'Invalid Document';
pointer = "/data";
}
out.push({
title: title,
detail: messages[i],
source: {
pointer: pointer
}
});
}
});
}
return out;
}
/**
Convert an array of errors in JSON-API format into an object.
```javascript
import DS from 'ember-data';
const { errorsArrayToHash } = DS;
let errorsArray = [
{
title: 'Invalid Attribute',
detail: 'Must be present',
source: { pointer: '/data/attributes/name' }
},
{
title: 'Invalid Attribute',
detail: 'Must be present',
source: { pointer: '/data/attributes/age' }
},
{
title: 'Invalid Attribute',
detail: 'Must be a number',
source: { pointer: '/data/attributes/age' }
}
];
let errors = errorsArrayToHash(errorsArray);
// {
// "name": ["Must be present"],
// "age": ["Must be present", "must be a number"]
// }
```
@method errorsArrayToHash
@public
@param {Array} errors array of errors in JSON-API format
@return {Object}
*/
function errorsArrayToHash(errors) {
let out = {};
if (Ember.isPresent(errors)) {
errors.forEach(error => {
if (error.source && error.source.pointer) {
let key = error.source.pointer.match(SOURCE_POINTER_REGEXP);
if (key) {
key = key[2];
} else if (error.source.pointer.search(SOURCE_POINTER_PRIMARY_REGEXP) !== -1) {
key = PRIMARY_ATTRIBUTE_KEY;
}
if (key) {
out[key] = out[key] || [];
out[key].push(error.detail || error.title);
}
}
});
}
return out;
}
});
define("@ember-data/store/-private/system/fetch-manager", ["exports", "@ember-data/store/-private/system/snapshot", "@ember-data/store/-private/system/store/common", "@ember-data/store/-private/system/store/serializer-response", "@ember-data/store/-private/system/store/serializers", "@ember-data/adapter/error", "@ember-data/store/-private/system/coerce-id", "@ember-data/store/-private/system/request-cache", "@ember-data/store/-private/ts-interfaces/utils/symbol", "@ember-data/store/-private/system/record-data-for"], function (_exports, _snapshot, _common, _serializerResponse, _serializers, _error, _coerceId, _requestCache, _symbol, _recordDataFor) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = _exports.SaveOp = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function payloadIsNotBlank(adapterPayload) {
if (Array.isArray(adapterPayload)) {
return true;
} else {
return Object.keys(adapterPayload || {}).length !== 0;
}
}
const emberRun = Ember.run.backburner;
const SaveOp = (0, _symbol.symbol)('SaveOp');
_exports.SaveOp = SaveOp;
class FetchManager {
// saves which are pending in the runloop
// fetches pending in the runloop, waiting to be coalesced
constructor(_store) {
this._store = _store;
_defineProperty(this, "isDestroyed", void 0);
_defineProperty(this, "requestCache", void 0);
_defineProperty(this, "_pendingSave", void 0);
_defineProperty(this, "_pendingFetch", void 0);
// used to keep track of all the find requests that need to be coalesced
this._pendingFetch = new Map();
this._pendingSave = [];
this.requestCache = new _requestCache.default();
}
/**
This method is called by `record.save`, and gets passed a
resolver for the promise that `record.save` returns.
It schedules saving to happen at the end of the run loop.
*/
scheduleSave(identifier, options = {}) {
let promiseLabel = 'DS: Model#save ' + this;
let resolver = Ember.RSVP.defer(promiseLabel);
let query = {
op: 'saveRecord',
recordIdentifier: identifier,
options
};
let queryRequest = {
data: [query]
};
let snapshot = new _snapshot.default(options, identifier, this._store);
let pendingSaveItem = {
snapshot: snapshot,
resolver: resolver,
identifier,
options,
queryRequest
};
this._pendingSave.push(pendingSaveItem);
emberRun.scheduleOnce('actions', this, this._flushPendingSaves);
this.requestCache.enqueue(resolver.promise, pendingSaveItem.queryRequest);
return resolver.promise;
}
_flushPendingSave(pending) {
let {
snapshot,
resolver,
identifier,
options
} = pending;
let adapter = this._store.adapterFor(identifier.type);
let operation = options[SaveOp];
let recordData = (0, _recordDataFor.default)(this._store._internalModelForResource(identifier));
let internalModel = snapshot._internalModel;
let modelName = snapshot.modelName;
let store = this._store;
let modelClass = store.modelFor(modelName);
(true && Ember.assert("You tried to update a record but you have no adapter (for ".concat(modelName, ")"), adapter));
(true && Ember.assert("You tried to update a record but your adapter (for ".concat(modelName, ") does not implement '").concat(operation, "'"), typeof adapter[operation] === 'function'));
let promise = Ember.RSVP.Promise.resolve().then(() => adapter[operation](store, modelClass, snapshot));
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let label = "DS: Extract and notify about ".concat(operation, " completion of ").concat(internalModel);
(true && Ember.assert("Your adapter's '".concat(operation, "' method must return a value, but it returned 'undefined'"), promise !== undefined));
promise = (0, _common.guardDestroyedStore)(promise, store, label);
promise = (0, _common._guard)(promise, (0, _common._bind)(_common._objectIsAlive, internalModel));
promise = promise.then(adapterPayload => {
let payload, data, sideloaded;
if (adapterPayload) {
payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, snapshot.id, operation);
return payload;
}
}, function (error) {
if (error instanceof _error.InvalidError) {
let parsedErrors = error.errors;
if (typeof serializer.extractErrors === 'function') {
parsedErrors = serializer.extractErrors(store, modelClass, error, snapshot.id);
} else {
parsedErrors = errorsArrayToHash(error.errors);
}
throw {
error,
parsedErrors
};
} else {
throw {
error
};
}
}, label);
resolver.resolve(promise);
}
/**
This method is called at the end of the run loop, and
flushes any records passed into `scheduleSave`
@method flushPendingSave
@private
*/
_flushPendingSaves() {
let pending = this._pendingSave.slice();
this._pendingSave = [];
for (let i = 0, j = pending.length; i < j; i++) {
let pendingItem = pending[i];
this._flushPendingSave(pendingItem);
}
}
scheduleFetch(identifier, options, shouldTrace) {
// TODO Probably the store should pass in the query object
let query = {
op: 'findRecord',
recordIdentifier: identifier,
options
};
let queryRequest = {
data: [query]
};
let pendingFetches = this._pendingFetch.get(identifier.type); // We already have a pending fetch for this
if (pendingFetches) {
let matchingPendingFetch = pendingFetches.find(fetch => fetch.identifier.id === identifier.id);
if (matchingPendingFetch) {
return matchingPendingFetch.resolver.promise;
}
}
let id = identifier.id;
let modelName = identifier.type;
let resolver = Ember.RSVP.defer("Fetching ".concat(modelName, "' with id: ").concat(id));
let pendingFetchItem = {
identifier,
resolver,
options,
queryRequest
};
if (true
/* DEBUG */
) {
if (shouldTrace) {
let trace;
try {
throw new Error("Trace Origin for scheduled fetch for ".concat(modelName, ":").concat(id, "."));
} catch (e) {
trace = e;
} // enable folks to discover the origin of this findRecord call when
// debugging. Ideally we would have a tracked queue for requests with
// labels or local IDs that could be used to merge this trace with
// the trace made available when we detect an async leak
pendingFetchItem.trace = trace;
}
}
let promise = resolver.promise;
if (this._pendingFetch.size === 0) {
emberRun.schedule('actions', this, this.flushAllPendingFetches);
}
let fetches = this._pendingFetch;
if (!fetches.has(modelName)) {
fetches.set(modelName, []);
}
fetches.get(modelName).push(pendingFetchItem);
this.requestCache.enqueue(promise, pendingFetchItem.queryRequest);
return promise;
}
_fetchRecord(fetchItem) {
let identifier = fetchItem.identifier;
let modelName = identifier.type;
let adapter = this._store.adapterFor(modelName);
(true && Ember.assert("You tried to find a record but you have no adapter (for ".concat(modelName, ")"), adapter));
(true && Ember.assert("You tried to find a record but your adapter (for ".concat(modelName, ") does not implement 'findRecord'"), typeof adapter.findRecord === 'function'));
let snapshot = new _snapshot.default(fetchItem.options, identifier, this._store);
let klass = this._store.modelFor(identifier.type);
let promise = Ember.RSVP.Promise.resolve().then(() => {
return adapter.findRecord(this._store, klass, identifier.id, snapshot);
});
let id = identifier.id;
let label = "DS: Handle Adapter#findRecord of '".concat(modelName, "' with id: '").concat(id, "'");
promise = (0, _common.guardDestroyedStore)(promise, this._store, label);
promise = promise.then(adapterPayload => {
(true && Ember.assert("You made a 'findRecord' request for a '".concat(modelName, "' with id '").concat(id, "', but the adapter's response did not have any data"), !!payloadIsNotBlank(adapterPayload)));
let serializer = (0, _serializers.serializerForAdapter)(this._store, adapter, modelName);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, this._store, klass, adapterPayload, id, 'findRecord');
(true && Ember.assert("Ember Data expected the primary data returned from a 'findRecord' response to be an object but instead it found an array.", !Array.isArray(payload.data)));
(true && Ember.warn("You requested a record of type '".concat(modelName, "' with id '").concat(id, "' but the adapter returned a payload with primary data having an id of '").concat(payload.data.id, "'. Use 'store.findRecord()' when the requested id is the same as the one returned by the adapter. In other cases use 'store.queryRecord()' instead https://emberjs.com/api/data/classes/DS.Store.html#method_queryRecord"), (0, _coerceId.default)(payload.data.id) === (0, _coerceId.default)(id), {
id: 'ds.store.findRecord.id-mismatch'
}));
return payload;
}, error => {
throw error;
}, "DS: Extract payload of '".concat(modelName, "'"));
fetchItem.resolver.resolve(promise);
} // TODO should probably refactor expectedSnapshots to be identifiers
handleFoundRecords(seeking, coalescedPayload, expectedSnapshots) {
// resolve found records
let found = Object.create(null);
let payloads = coalescedPayload.data;
let coalescedIncluded = coalescedPayload.included || [];
for (let i = 0, l = payloads.length; i < l; i++) {
let payload = payloads[i];
let pair = seeking[payload.id];
found[payload.id] = payload;
let included = coalescedIncluded.concat(payloads); // TODO remove original data from included
if (pair) {
let resolver = pair.resolver;
resolver.resolve({
data: payload,
included
});
}
} // reject missing records
// TODO NOW clean this up to refer to payloads
let missingSnapshots = [];
for (let i = 0, l = expectedSnapshots.length; i < l; i++) {
let snapshot = expectedSnapshots[i];
if (!found[snapshot.id]) {
missingSnapshots.push(snapshot);
}
}
if (missingSnapshots.length) {
(true && Ember.warn('Ember Data expected to find records with the following ids in the adapter response but they were missing: [ "' + missingSnapshots.map(r => r.id).join('", "') + '" ]', false, {
id: 'ds.store.missing-records-from-adapter'
}));
this.rejectFetchedItems(seeking, missingSnapshots);
}
}
rejectFetchedItems(seeking, snapshots, error) {
for (let i = 0, l = snapshots.length; i < l; i++) {
let identifier = snapshots[i];
let pair = seeking[identifier.id];
if (pair) {
pair.resolver.reject(error || new Error("Expected: '<".concat(identifier.modelName, ":").concat(identifier.id, ">' to be present in the adapter provided payload, but it was not found.")));
}
}
}
_findMany(adapter, store, modelName, snapshots, identifiers, optionsMap) {
let modelClass = store.modelFor(modelName); // `adapter.findMany` gets the modelClass still
let ids = snapshots.map(s => s.id);
let promise = adapter.findMany(store, modelClass, ids, Ember.A(snapshots));
let label = "DS: Handle Adapter#findMany of '".concat(modelName, "'");
if (promise === undefined) {
throw new Error('adapter.findMany returned undefined, this was very likely a mistake');
}
promise = (0, _common.guardDestroyedStore)(promise, store, label);
return promise.then(adapterPayload => {
(true && Ember.assert("You made a 'findMany' request for '".concat(modelName, "' records with ids '[").concat(ids, "]', but the adapter's response did not have any data"), !!payloadIsNotBlank(adapterPayload)));
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, null, 'findMany');
return payload;
}, null, "DS: Extract payload of ".concat(modelName));
}
_processCoalescedGroup(seeking, group, adapter, optionsMap, modelName) {
//TODO check what happened with identifiers here
let totalInGroup = group.length;
let ids = new Array(totalInGroup);
let groupedSnapshots = new Array(totalInGroup);
for (let j = 0; j < totalInGroup; j++) {
groupedSnapshots[j] = group[j];
ids[j] = groupedSnapshots[j].id;
}
let store = this._store;
if (totalInGroup > 1) {
this._findMany(adapter, store, modelName, group, groupedSnapshots, optionsMap).then(payloads => {
this.handleFoundRecords(seeking, payloads, groupedSnapshots);
}).catch(error => {
this.rejectFetchedItems(seeking, groupedSnapshots, error);
});
} else if (ids.length === 1) {
let pair = seeking[groupedSnapshots[0].id];
this._fetchRecord(pair);
} else {
(true && Ember.assert("You cannot return an empty array from adapter's method groupRecordsForFindMany", false));
}
}
_flushPendingFetchForType(pendingFetchItems, modelName) {
let adapter = this._store.adapterFor(modelName);
let shouldCoalesce = !!adapter.findMany && adapter.coalesceFindRequests;
let totalItems = pendingFetchItems.length;
let identifiers = new Array(totalItems);
let seeking = Object.create(null);
let optionsMap = new WeakMap();
for (let i = 0; i < totalItems; i++) {
let pendingItem = pendingFetchItems[i];
let identifier = pendingItem.identifier;
identifiers[i] = identifier;
optionsMap.set(identifier, pendingItem.options);
seeking[identifier.id] = pendingItem;
}
if (shouldCoalesce) {
// TODO: Improve records => snapshots => records => snapshots
//
// We want to provide records to all store methods and snapshots to all
// adapter methods. To make sure we're doing that we're providing an array
// of snapshots to adapter.groupRecordsForFindMany(), which in turn will
// return grouped snapshots instead of grouped records.
//
// But since the _findMany() finder is a store method we need to get the
// records from the grouped snapshots even though the _findMany() finder
// will once again convert the records to snapshots for adapter.findMany()
let snapshots = new Array(totalItems);
for (let i = 0; i < totalItems; i++) {
let options = optionsMap.get(identifiers[i]);
snapshots[i] = new _snapshot.default(options, identifiers[i], this._store);
}
let groups = adapter.groupRecordsForFindMany(this, snapshots);
for (let i = 0, l = groups.length; i < l; i++) {
this._processCoalescedGroup(seeking, groups[i], adapter, optionsMap, modelName);
}
} else {
for (let i = 0; i < totalItems; i++) {
this._fetchRecord(pendingFetchItems[i]);
}
}
}
flushAllPendingFetches() {
if (this.isDestroyed) {
return;
}
this._pendingFetch.forEach(this._flushPendingFetchForType, this);
this._pendingFetch.clear();
}
destroy() {
this.isDestroyed = true;
}
}
_exports.default = FetchManager;
});
define("@ember-data/store/-private/system/identity-map", ["exports", "@ember-data/store/-private/system/internal-model-map"], function (_exports, _internalModelMap) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
/**
`IdentityMap` is a custom storage map for records by modelName
used by `Store`.
@class IdentityMap
@private
*/
class IdentityMap {
constructor() {
_defineProperty(this, "_map", Object.create(null));
}
/**
Retrieves the `InternalModelMap` for a given modelName,
creating one if one did not already exist. This is
similar to `getWithDefault` or `get` on a `MapWithDefault`
@method retrieve
@param modelName a previously normalized modelName
@return {InternalModelMap} the InternalModelMap for the given modelName
*/
retrieve(modelName) {
let map = this._map[modelName];
if (map === undefined) {
map = this._map[modelName] = new _internalModelMap.default(modelName);
}
return map;
}
/**
Clears the contents of all known `RecordMaps`, but does
not remove the InternalModelMap instances.
@method clear
*/
clear() {
let map = this._map;
let keys = Object.keys(map);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
map[key].clear();
}
}
}
_exports.default = IdentityMap;
});
define("@ember-data/store/-private/system/internal-model-map", ["exports", "@ember-data/store/-private/system/model/internal-model"], function (_exports, _internalModel) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
/**
`InternalModelMap` is a custom storage map for internalModels of a given modelName
used by `IdentityMap`.
It was extracted from an implicit pojo based "internalModel map" and preserves
that interface while we work towards a more official API.
@class InternalModelMap
@private
*/
class InternalModelMap {
constructor(modelName) {
this.modelName = modelName;
_defineProperty(this, "_idToModel", Object.create(null));
_defineProperty(this, "_models", []);
_defineProperty(this, "_metadata", null);
}
/**
* @method get
* @param id {String}
* @return {InternalModel}
*/
get(id) {
return this._idToModel[id] || null;
}
has(id) {
return !!this._idToModel[id];
}
get length() {
return this._models.length;
}
set(id, internalModel) {
(true && Ember.assert("You cannot index an internalModel by an empty id'", typeof id === 'string' && id.length > 0));
(true && Ember.assert("You cannot set an index for an internalModel to something other than an internalModel", internalModel instanceof _internalModel.default));
(true && Ember.assert("You cannot set an index for an internalModel that is not in the InternalModelMap", this.contains(internalModel)));
(true && Ember.assert("You cannot update the id index of an InternalModel once set. Attempted to update ".concat(id, "."), !this.has(id) || this.get(id) === internalModel));
this._idToModel[id] = internalModel;
}
add(internalModel, id) {
(true && Ember.assert("You cannot re-add an already present InternalModel to the InternalModelMap.", !this.contains(internalModel)));
if (id) {
(true && Ember.assert("Duplicate InternalModel for ".concat(this.modelName, ":").concat(id, " detected."), !this.has(id) || this.get(id) === internalModel));
this._idToModel[id] = internalModel;
}
this._models.push(internalModel);
}
remove(internalModel, id) {
delete this._idToModel[id];
let loc = this._models.indexOf(internalModel);
if (loc !== -1) {
this._models.splice(loc, 1);
}
}
contains(internalModel) {
return this._models.indexOf(internalModel) !== -1;
}
/**
An array of all models of this modelName
@property models
@type Array
*/
get models() {
return this._models;
}
/**
* meta information about internalModels
* @property metadata
* @type Object
*/
get metadata() {
return this._metadata || (this._metadata = Object.create(null));
}
/**
Destroy all models in the internalModelTest and wipe metadata.
@method clear
*/
clear() {
let internalModels = this._models;
this._models = [];
for (let i = 0; i < internalModels.length; i++) {
let internalModel = internalModels[i];
internalModel.unloadRecord();
}
this._metadata = null;
}
}
_exports.default = InternalModelMap;
});
define("@ember-data/store/-private/system/many-array", ["exports", "@ember-data/store/-private/system/deprecated-evented", "@ember-data/store/-private/system/promise-proxies", "@ember-data/store/-private/system/store/common", "@ember-data/store/-private/system/diff-array", "@ember-data/store/-private/system/record-data-for"], function (_exports, _deprecatedEvented, _promiseProxies, _common, _diffArray, _recordDataFor) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
//import Evented from '@ember/object/evented';
/**
A `ManyArray` is a `MutableArray` that represents the contents of a has-many
relationship.
The `ManyArray` is instantiated lazily the first time the relationship is
requested.
### Inverses
Often, the relationships in Ember Data applications will have
an inverse. For example, imagine the following models are
defined:
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany('comment')
});
```
```app/models/comment.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
post: belongsTo('post')
});
```
If you created a new instance of `Post` and added
a `Comment` record to its `comments` has-many
relationship, you would expect the comment's `post`
property to be set to the post that contained
the has-many.
We call the record to which a relationship belongs-to the
relationship's _owner_.
@class ManyArray
@extends EmberObject
@uses Ember.MutableArray, EmberData.DeprecatedEvent
*/
var _default = Ember.Object.extend(Ember.MutableArray, _deprecatedEvented.default, {
// here to make TS happy
_inverseIsAsync: false,
isLoaded: false,
init() {
this._super(...arguments);
/**
The loading state of this array
@property {Boolean} isLoaded
*/
this.isLoaded = this.isLoaded || false;
this.length = 0;
/**
Used for async `hasMany` arrays
to keep track of when they will resolve.
@property {Ember.RSVP.Promise} promise
@private
*/
this.promise = null;
/**
Metadata associated with the request for async hasMany relationships.
Example
Given that the server returns the following JSON payload when fetching a
hasMany relationship:
```js
{
"comments": [{
"id": 1,
"comment": "This is the first comment",
}, {
// ...
}],
"meta": {
"page": 1,
"total": 5
}
}
```
You can then access the metadata via the `meta` property:
```js
post.get('comments').then(function(comments) {
var meta = comments.get('meta');
// meta.page => 1
// meta.total => 5
});
```
@property {Object} meta
@public
*/
// TODO this is likely broken in our refactor
this.meta = this.meta || null;
/**
`true` if the relationship is polymorphic, `false` otherwise.
@property {Boolean} isPolymorphic
@private
*/
this.isPolymorphic = this.isPolymorphic || false;
/**
The relationship which manages this array.
@property {ManyRelationship} relationship
@private
*/
this.currentState = [];
this.flushCanonical(this.initialState, false);
},
// TODO: if(DEBUG)
anyUnloaded() {
// Use `filter[0]` as opposed to `find` because of IE11
let unloaded = this.currentState.filter(im => im._isDematerializing || !im.isLoaded())[0];
return !!unloaded;
},
removeUnloadedInternalModel() {
for (let i = 0; i < this.currentState.length; ++i) {
let internalModel = this.currentState[i];
if (internalModel._isDematerializing || !internalModel.isLoaded()) {
this.arrayContentWillChange(i, 1, 0);
this.currentState.splice(i, 1);
this.set('length', this.currentState.length);
this.arrayContentDidChange(i, 1, 0);
return true;
}
}
return false;
},
objectAt(index) {
// TODO we likely need to force flush here
/*
if (this.relationship._willUpdateManyArray) {
this.relationship._flushPendingManyArrayUpdates();
}
*/
let internalModel = this.currentState[index];
if (internalModel === undefined) {
return;
}
return internalModel.getRecord();
},
flushCanonical(toSet, isInitialized = true) {
// It’s possible the parent side of the relationship may have been unloaded by this point
if (!(0, _common._objectIsAlive)(this)) {
return;
} // diff to find changes
let diff = (0, _diffArray.default)(this.currentState, toSet);
if (diff.firstChangeIndex !== null) {
// it's null if no change found
// we found a change
this.arrayContentWillChange(diff.firstChangeIndex, diff.removedCount, diff.addedCount);
this.set('length', toSet.length);
this.currentState = toSet.slice();
this.arrayContentDidChange(diff.firstChangeIndex, diff.removedCount, diff.addedCount);
if (isInitialized && diff.addedCount > 0) {
//notify only on additions
//TODO only notify if unloaded
this.internalModel.manyArrayRecordAdded(this.get('key'));
}
}
},
replace(idx, amt, objects) {
let internalModels;
if (amt > 0) {
internalModels = this.currentState.slice(idx, idx + amt);
this.get('recordData').removeFromHasMany(this.get('key'), internalModels.map(im => (0, _recordDataFor.default)(im)));
}
if (objects) {
(true && Ember.assert('The third argument to replace needs to be an array.', Array.isArray(objects) || Ember.Array.detect(objects)));
this.get('recordData').addToHasMany(this.get('key'), objects.map(obj => (0, _recordDataFor.default)(obj)), idx);
}
this.retrieveLatest();
},
// Ok this is kinda funky because if buggy we might lose positions, etc.
// but current code is this way so shouldn't be too big of a problem
retrieveLatest() {
let jsonApi = this.get('recordData').getHasMany(this.get('key')); // TODO this is odd, why should ManyArray ever tell itself to resync?
let internalModels = this.store._getHasManyByJsonApiResource(jsonApi);
if (jsonApi.meta) {
this.set('meta', jsonApi.meta);
}
this.flushCanonical(internalModels, true);
},
/**
Reloads all of the records in the manyArray. If the manyArray
holds a relationship that was originally fetched using a links url
Ember Data will revisit the original links url to repopulate the
relationship.
If the manyArray holds the result of a `store.query()` reload will
re-run the original query.
Example
```javascript
var user = store.peekRecord('user', 1)
user.login().then(function() {
user.get('permissions').then(function(permissions) {
return permissions.reload();
});
});
```
@method reload
@public
*/
reload(options) {
// TODO this is odd, we don't ask the store for anything else like this?
return this.get('store').reloadManyArray(this, this.get('internalModel'), this.get('key'), options);
},
/**
Saves all of the records in the `ManyArray`.
Example
```javascript
store.findRecord('inbox', 1).then(function(inbox) {
inbox.get('messages').then(function(messages) {
messages.forEach(function(message) {
message.set('isRead', true);
});
messages.save()
});
});
```
@method save
@return {DS.PromiseArray} promise
*/
save() {
let manyArray = this;
let promiseLabel = 'DS: ManyArray#save ' + Ember.get(this, 'type');
let promise = Ember.RSVP.all(this.invoke('save'), promiseLabel).then(() => manyArray, null, 'DS: ManyArray#save return ManyArray');
return _promiseProxies.PromiseArray.create({
promise
});
},
/**
Create a child record within the owner
@method createRecord
@private
@param {Object} hash
@return {DS.Model} record
*/
createRecord(hash) {
const store = Ember.get(this, 'store');
const type = Ember.get(this, 'type');
(true && Ember.assert("You cannot add '".concat(type.modelName, "' records to this polymorphic relationship."), !Ember.get(this, 'isPolymorphic')));
let record = store.createRecord(type.modelName, hash);
this.pushObject(record);
return record;
}
});
_exports.default = _default;
});
define("@ember-data/store/-private/system/normalize-link", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = _normalizeLink;
/**
@module @ember-data/store
*/
/*
This method normalizes a link to an "links object". If the passed link is
already an object it's returned without any modifications.
See http://jsonapi.org/format/#document-links for more information.
@method _normalizeLink
@private
@param {String} link
@return {Object|null}
*/
function _normalizeLink(link) {
switch (typeof link) {
case 'object':
return link;
case 'string':
return {
href: link
};
}
return null;
}
});
define("@ember-data/store/-private/system/normalize-model-name", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = normalizeModelName;
/**
@module @ember-data/store
*/
// All modelNames are dasherized internally. Changing this function may
// require changes to other normalization hooks (such as typeForRoot).
/**
This method normalizes a modelName into the format Ember Data uses
internally.
@method normalizeModelName
@public
@param {String} modelName
@return {String} normalizedModelName
*/
function normalizeModelName(modelName) {
return Ember.String.dasherize(modelName);
}
});
define("@ember-data/store/-private/system/ordered-set", ["exports", "@ember/ordered-set"], function (_exports, _orderedSet) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
class EmberDataOrderedSet extends _orderedSet.default {
static create() {
return new this();
}
addWithIndex(obj, idx) {
let guid = Ember.guidFor(obj);
let presenceSet = this.presenceSet;
let list = this.list;
if (presenceSet[guid] === true) {
return;
}
presenceSet[guid] = true;
if (idx === undefined || idx === null) {
list.push(obj);
} else {
list.splice(idx, 0, obj);
}
this.size += 1;
return this;
}
}
_exports.default = EmberDataOrderedSet;
});
define("@ember-data/store/-private/system/promise-proxies", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.promiseObject = promiseObject;
_exports.promiseArray = promiseArray;
_exports.proxyToContent = proxyToContent;
_exports.promiseManyArray = promiseManyArray;
_exports.PromiseManyArray = _exports.PromiseBelongsTo = _exports.PromiseObject = _exports.PromiseArray = void 0;
/**
@module @ember-data/store
*/
/*
A `PromiseArray` is an object that acts like both an `Ember.Array`
and a promise. When the promise is resolved the resulting value
will be set to the `PromiseArray`'s `content` property. This makes
it easy to create data bindings with the `PromiseArray` that will be
updated when the promise resolves.
For more information see the [Ember.PromiseProxyMixin
documentation](/api/classes/Ember.PromiseProxyMixin.html).
Example
```javascript
let promiseArray = PromiseArray.create({
promise: $.getJSON('/some/remote/data.json')
});
promiseArray.get('length'); // 0
promiseArray.then(function() {
promiseArray.get('length'); // 100
});
```
@class PromiseArray
@extends Ember.ArrayProxy
@uses Ember.PromiseProxyMixin
*/
const PromiseArray = Ember.ArrayProxy.extend(Ember.PromiseProxyMixin, {
meta: Ember.computed.reads('content.meta')
});
/*
A `PromiseObject` is an object that acts like both an `EmberObject`
and a promise. When the promise is resolved, then the resulting value
will be set to the `PromiseObject`'s `content` property. This makes
it easy to create data bindings with the `PromiseObject` that will
be updated when the promise resolves.
For more information see the [Ember.PromiseProxyMixin
documentation](/api/classes/Ember.PromiseProxyMixin.html).
Example
```javascript
let promiseObject = PromiseObject.create({
promise: $.getJSON('/some/remote/data.json')
});
promiseObject.get('name'); // null
promiseObject.then(function() {
promiseObject.get('name'); // 'Tomster'
});
```
@class PromiseObject
@extends Ember.ObjectProxy
@uses Ember.PromiseProxyMixin
*/
_exports.PromiseArray = PromiseArray;
let PromiseObject = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin);
_exports.PromiseObject = PromiseObject;
function promiseObject(promise, label) {
return PromiseObject.create({
promise: Ember.RSVP.Promise.resolve(promise, label)
});
}
function promiseArray(promise, label) {
return PromiseArray.create({
promise: Ember.RSVP.Promise.resolve(promise, label)
});
}
const PromiseBelongsTo = PromiseObject.extend({
// we don't proxy meta because we would need to proxy it to the relationship state container
// however, meta on relationships does not trigger change notifications.
// if you need relationship meta, you should do `record.belongsTo(relationshipName).meta()`
meta: Ember.computed(function () {
(true && Ember.assert('You attempted to access meta on the promise for the async belongsTo relationship ' + "".concat(this.get('_belongsToState').internalModel.modelName, ":").concat(this.get('_belongsToState').key, "'.") + '\nUse `record.belongsTo(relationshipName).meta()` instead.', false));
}),
reload(options) {
(true && Ember.assert('You are trying to reload an async belongsTo before it has been created', this.get('content') !== undefined));
let {
key,
store,
originatingInternalModel
} = this._belongsToState;
return store.reloadBelongsTo(this, originatingInternalModel, key, options).then(() => this);
}
});
_exports.PromiseBelongsTo = PromiseBelongsTo;
function proxyToContent(method) {
return function () {
return Ember.get(this, 'content')[method](...arguments);
};
}
/*
A PromiseManyArray is a PromiseArray that also proxies certain method calls
to the underlying manyArray.
Right now we proxy:
* `reload()`
* `createRecord()`
* `on()`
* `one()`
* `trigger()`
* `off()`
* `has()`
@class PromiseManyArray
@extends Ember.ArrayProxy
*/
const PromiseManyArray = PromiseArray.extend({
reload(options) {
(true && Ember.assert('You are trying to reload an async manyArray before it has been created', Ember.get(this, 'content')));
this.set('promise', this.get('content').reload(options));
return this;
},
createRecord: proxyToContent('createRecord'),
on: proxyToContent('on'),
one: proxyToContent('one'),
trigger: proxyToContent('trigger'),
off: proxyToContent('off'),
has: proxyToContent('has')
});
_exports.PromiseManyArray = PromiseManyArray;
function promiseManyArray(promise, label) {
return PromiseManyArray.create({
promise: Ember.RSVP.Promise.resolve(promise, label)
});
}
});
define("@ember-data/store/-private/system/record-array-manager", ["exports", "@ember-data/store/-private/system/clone-null", "@ember-data/store/-private/system/record-arrays", "@ember-data/store/-private/system/store/internal-model-factory"], function (_exports, _cloneNull, _recordArrays, _internalModelFactory) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.associateWithRecordArray = associateWithRecordArray;
_exports.default = void 0;
const emberRun = Ember.run.backburner;
class RecordArrayManager {
constructor(options) {
this.store = options.store;
this.isDestroying = false;
this.isDestroyed = false;
this._liveRecordArrays = Object.create(null);
this._pending = Object.create(null);
this._adapterPopulatedRecordArrays = [];
}
recordDidChange(internalModel) {
// TODO: change name
// TODO: track that it was also a change
this.internalModelDidChange(internalModel);
}
recordWasLoaded(internalModel) {
// TODO: change name
// TODO: track that it was also that it was first loaded
this.internalModelDidChange(internalModel);
}
internalModelDidChange(internalModel) {
let modelName = internalModel.modelName;
if (internalModel._pendingRecordArrayManagerFlush) {
return;
}
internalModel._pendingRecordArrayManagerFlush = true;
let pending = this._pending;
let models = pending[modelName] = pending[modelName] || [];
if (models.push(internalModel) !== 1) {
return;
}
emberRun.schedule('actions', this, this._flush);
}
_flushPendingInternalModelsForModelName(modelName, internalModels) {
let modelsToRemove = [];
for (let j = 0; j < internalModels.length; j++) {
let internalModel = internalModels[j]; // mark internalModels, so they can once again be processed by the
// recordArrayManager
internalModel._pendingRecordArrayManagerFlush = false; // build up a set of models to ensure we have purged correctly;
if (internalModel.isHiddenFromRecordArrays()) {
modelsToRemove.push(internalModel);
}
}
let array = this._liveRecordArrays[modelName];
if (array) {
// TODO: skip if it only changed
// process liveRecordArrays
this.updateLiveRecordArray(array, internalModels);
} // process adapterPopulatedRecordArrays
if (modelsToRemove.length > 0) {
removeFromAdapterPopulatedRecordArrays(modelsToRemove);
}
}
_flush() {
let pending = this._pending;
this._pending = Object.create(null);
for (let modelName in pending) {
this._flushPendingInternalModelsForModelName(modelName, pending[modelName]);
}
}
updateLiveRecordArray(array, internalModels) {
return updateLiveRecordArray(array, internalModels);
}
_syncLiveRecordArray(array, modelName) {
(true && Ember.assert("recordArrayManger.syncLiveRecordArray expects modelName not modelClass as the second param", typeof modelName === 'string'));
let pending = this._pending[modelName];
let hasPendingChanges = Array.isArray(pending);
let hasNoPotentialDeletions = !hasPendingChanges || pending.length === 0;
let map = (0, _internalModelFactory.internalModelFactoryFor)(this.store).modelMapFor(modelName);
let hasNoInsertionsOrRemovals = Ember.get(map, 'length') === Ember.get(array, 'length');
/*
Ideally the recordArrayManager has knowledge of the changes to be applied to
liveRecordArrays, and is capable of strategically flushing those changes and applying
small diffs if desired. However, until we've refactored recordArrayManager, this dirty
check prevents us from unnecessarily wiping out live record arrays returned by peekAll.
*/
if (hasNoPotentialDeletions && hasNoInsertionsOrRemovals) {
return;
}
if (hasPendingChanges) {
this._flushPendingInternalModelsForModelName(modelName, pending);
delete this._pending[modelName];
}
let internalModels = this._visibleInternalModelsByType(modelName);
let modelsToAdd = [];
for (let i = 0; i < internalModels.length; i++) {
let internalModel = internalModels[i];
let recordArrays = internalModel._recordArrays;
if (recordArrays.has(array) === false) {
recordArrays.add(array);
modelsToAdd.push(internalModel);
}
}
if (modelsToAdd.length) {
array._pushInternalModels(modelsToAdd);
}
}
_didUpdateAll(modelName) {
let recordArray = this._liveRecordArrays[modelName];
if (recordArray) {
Ember.set(recordArray, 'isUpdating', false);
}
}
/*
Get the `RecordArray` for a modelName, which contains all loaded records of
given modelName.
@method liveRecordArrayFor
@param {String} modelName
@return {RecordArray}
*/
liveRecordArrayFor(modelName) {
(true && Ember.assert("recordArrayManger.liveRecordArrayFor expects modelName not modelClass as the param", typeof modelName === 'string'));
let array = this._liveRecordArrays[modelName];
if (array) {
// if the array already exists, synchronize
this._syncLiveRecordArray(array, modelName);
} else {
// if the array is being newly created merely create it with its initial
// content already set. This prevents unneeded change events.
let internalModels = this._visibleInternalModelsByType(modelName);
array = this.createRecordArray(modelName, internalModels);
this._liveRecordArrays[modelName] = array;
}
return array;
}
_visibleInternalModelsByType(modelName) {
let all = (0, _internalModelFactory.internalModelFactoryFor)(this.store).modelMapFor(modelName)._models;
let visible = [];
for (let i = 0; i < all.length; i++) {
let model = all[i];
if (model.isHiddenFromRecordArrays() === false) {
visible.push(model);
}
}
return visible;
}
/*
Create a `RecordArray` for a modelName.
@method createRecordArray
@param {String} modelName
@param {Array} _content (optional|private)
@return {RecordArray}
*/
createRecordArray(modelName, content) {
(true && Ember.assert("recordArrayManger.createRecordArray expects modelName not modelClass as the param", typeof modelName === 'string'));
let array = _recordArrays.RecordArray.create({
modelName,
content: Ember.A(content || []),
store: this.store,
isLoaded: true,
manager: this
});
if (Array.isArray(content)) {
associateWithRecordArray(content, array);
}
return array;
}
/*
Create a `AdapterPopulatedRecordArray` for a modelName with given query.
@method createAdapterPopulatedRecordArray
@param {String} modelName
@param {Object} query
@return {AdapterPopulatedRecordArray}
*/
createAdapterPopulatedRecordArray(modelName, query, internalModels, payload) {
(true && Ember.assert("recordArrayManger.createAdapterPopulatedRecordArray expects modelName not modelClass as the first param, received ".concat(modelName), typeof modelName === 'string'));
let array;
if (Array.isArray(internalModels)) {
array = _recordArrays.AdapterPopulatedRecordArray.create({
modelName,
query: query,
content: Ember.A(internalModels),
store: this.store,
manager: this,
isLoaded: true,
isUpdating: false,
meta: (0, _cloneNull.default)(payload.meta),
links: (0, _cloneNull.default)(payload.links)
});
associateWithRecordArray(internalModels, array);
} else {
array = _recordArrays.AdapterPopulatedRecordArray.create({
modelName,
query: query,
content: Ember.A(),
store: this.store,
manager: this
});
}
this._adapterPopulatedRecordArrays.push(array);
return array;
}
/*
Unregister a RecordArray.
So manager will not update this array.
@method unregisterRecordArray
@param {RecordArray} array
*/
unregisterRecordArray(array) {
let modelName = array.modelName; // remove from adapter populated record array
let removedFromAdapterPopulated = remove(this._adapterPopulatedRecordArrays, array);
if (!removedFromAdapterPopulated) {
let liveRecordArrayForType = this._liveRecordArrays[modelName]; // unregister live record array
if (liveRecordArrayForType) {
if (array === liveRecordArrayForType) {
delete this._liveRecordArrays[modelName];
}
}
}
}
_associateWithRecordArray(internalModels, array) {
associateWithRecordArray(internalModels, array);
}
willDestroy() {
Object.keys(this._liveRecordArrays).forEach(modelName => this._liveRecordArrays[modelName].destroy());
this._adapterPopulatedRecordArrays.forEach(destroy);
this.isDestroyed = true;
}
destroy() {
this.isDestroying = true;
emberRun.schedule('actions', this, this.willDestroy);
}
}
_exports.default = RecordArrayManager;
function destroy(entry) {
entry.destroy();
}
function remove(array, item) {
let index = array.indexOf(item);
if (index !== -1) {
array.splice(index, 1);
return true;
}
return false;
}
function updateLiveRecordArray(array, internalModels) {
let modelsToAdd = [];
let modelsToRemove = [];
for (let i = 0; i < internalModels.length; i++) {
let internalModel = internalModels[i];
let isDeleted = internalModel.isHiddenFromRecordArrays();
let recordArrays = internalModel._recordArrays;
if (!isDeleted && !internalModel.isEmpty()) {
if (!recordArrays.has(array)) {
modelsToAdd.push(internalModel);
recordArrays.add(array);
}
}
if (isDeleted) {
modelsToRemove.push(internalModel);
recordArrays.delete(array);
}
}
if (modelsToAdd.length > 0) {
array._pushInternalModels(modelsToAdd);
}
if (modelsToRemove.length > 0) {
array._removeInternalModels(modelsToRemove);
} // return whether we performed an update.
// Necessary until 3.5 allows us to finish off ember-data-filter support.
return (modelsToAdd.length || modelsToRemove.length) > 0;
}
function removeFromAdapterPopulatedRecordArrays(internalModels) {
for (let i = 0; i < internalModels.length; i++) {
let internalModel = internalModels[i];
let list = internalModel._recordArrays.list;
for (let j = 0; j < list.length; j++) {
// TODO: group by arrays, so we can batch remove
list[j]._removeInternalModels([internalModel]);
}
internalModel._recordArrays.clear();
}
}
function associateWithRecordArray(internalModels, array) {
for (let i = 0, l = internalModels.length; i < l; i++) {
let internalModel = internalModels[i];
internalModel._recordArrays.add(array);
}
}
});
define("@ember-data/store/-private/system/record-arrays", ["exports", "@ember-data/store/-private/system/record-arrays/record-array", "@ember-data/store/-private/system/record-arrays/adapter-populated-record-array"], function (_exports, _recordArray, _adapterPopulatedRecordArray) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "RecordArray", {
enumerable: true,
get: function () {
return _recordArray.default;
}
});
Object.defineProperty(_exports, "AdapterPopulatedRecordArray", {
enumerable: true,
get: function () {
return _adapterPopulatedRecordArray.default;
}
});
});
define("@ember-data/store/-private/system/record-data-for", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = recordDataFor;
_exports.relationshipsFor = relationshipsFor;
_exports.relationshipStateFor = relationshipStateFor;
/**
@module @ember-data/store
*/
/*
* Returns the RecordData instance associated with a given
* Model or InternalModel.
*
* Intentionally "loose" to allow anything with an _internalModel
* property until InternalModel is eliminated.
*
* Intentionally not typed to `InternalModel` due to circular dependency
* which that creates.
*
* Overtime, this should shift to a "weakmap" based lookup in the
* "Ember.getOwner(obj)" style.
*/
function recordDataFor(instance) {
let internalModel = instance._internalModel || instance.internalModel || instance;
return internalModel._recordData || null;
}
function relationshipsFor(instance) {
let recordData = recordDataFor(instance) || instance;
return recordData._relationships;
}
function relationshipStateFor(instance, propertyName) {
return relationshipsFor(instance).get(propertyName);
}
});
define("@ember-data/store/-private/system/references", ["exports", "@ember-data/store/-private/system/references/record", "@ember-data/store/-private/system/references/belongs-to", "@ember-data/store/-private/system/references/has-many"], function (_exports, _record, _belongsTo, _hasMany) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "RecordReference", {
enumerable: true,
get: function () {
return _record.default;
}
});
Object.defineProperty(_exports, "BelongsToReference", {
enumerable: true,
get: function () {
return _belongsTo.default;
}
});
Object.defineProperty(_exports, "HasManyReference", {
enumerable: true,
get: function () {
return _hasMany.default;
}
});
});
define("@ember-data/store/-private/system/relationship-meta", ["exports", "ember-inflector", "@ember-data/store/-private/system/normalize-model-name", "@ember-data/store/-private/ts-interfaces/utils/brand"], function (_exports, _emberInflector, _normalizeModelName, _brand) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.typeForRelationshipMeta = typeForRelationshipMeta;
_exports.relationshipFromMeta = relationshipFromMeta;
_exports.RelationshipDefinition = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
function typeForRelationshipMeta(meta) {
let modelName;
modelName = meta.type || meta.key;
modelName = (0, _normalizeModelName.default)(modelName);
if (meta.kind === 'hasMany') {
modelName = (0, _emberInflector.singularize)(modelName);
}
return modelName;
}
function shouldFindInverse(relationshipMeta) {
let options = relationshipMeta.options;
return !(options && options.inverse === null);
}
class RelationshipDefinition {
constructor(meta) {
this.meta = meta;
_defineProperty(this, _brand.BRAND_SYMBOL, void 0);
_defineProperty(this, "_type", '');
_defineProperty(this, "__inverseKey", '');
_defineProperty(this, "__inverseIsAsync", true);
_defineProperty(this, "__hasCalculatedInverse", false);
_defineProperty(this, "parentModelName", void 0);
this.parentModelName = meta.parentModelName;
}
get key() {
return this.meta.key;
}
get kind() {
return this.meta.kind;
}
get type() {
if (this._type) {
return this._type;
}
this._type = typeForRelationshipMeta(this.meta);
return this._type;
}
get options() {
return this.meta.options;
}
get name() {
return this.meta.name;
}
_inverseKey(store, modelClass) {
if (this.__hasCalculatedInverse === false) {
this._calculateInverse(store, modelClass);
}
return this.__inverseKey;
}
_inverseIsAsync(store, modelClass) {
if (this.__hasCalculatedInverse === false) {
this._calculateInverse(store, modelClass);
}
return this.__inverseIsAsync;
}
_calculateInverse(store, modelClass) {
this.__hasCalculatedInverse = true;
let inverseKey, inverseIsAsync;
let inverse = null;
if (shouldFindInverse(this.meta)) {
inverse = modelClass.inverseFor(this.key, store);
} else if (true
/* DEBUG */
) {
modelClass.typeForRelationship(this.key, store);
}
if (inverse) {
inverseKey = inverse.name;
inverseIsAsync = isInverseAsync(inverse);
} else {
inverseKey = null;
inverseIsAsync = false;
}
this.__inverseKey = inverseKey;
this.__inverseIsAsync = inverseIsAsync;
}
}
_exports.RelationshipDefinition = RelationshipDefinition;
function isInverseAsync(meta) {
let inverseAsync = meta.options && meta.options.async;
return typeof inverseAsync === 'undefined' ? true : inverseAsync;
}
function relationshipFromMeta(meta) {
return new RelationshipDefinition(meta);
}
});
define("@ember-data/store/-private/system/request-cache", ["exports", "@ember-data/store/-private/ts-interfaces/fetch-manager", "@ember-data/store/-private/ts-interfaces/utils/symbol"], function (_exports, _fetchManager, _symbol) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = _exports.RequestPromise = _exports.Touching = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const Touching = (0, _symbol.symbol)('touching');
_exports.Touching = Touching;
const RequestPromise = (0, _symbol.symbol)('promise');
_exports.RequestPromise = RequestPromise;
function hasRecordIdentifier(op) {
return 'recordIdentifier' in op;
}
class RequestCache {
constructor() {
_defineProperty(this, "_pending", Object.create(null));
_defineProperty(this, "_done", Object.create(null));
_defineProperty(this, "_subscriptions", Object.create(null));
}
enqueue(promise, queryRequest) {
let query = queryRequest.data[0];
if (hasRecordIdentifier(query)) {
let lid = query.recordIdentifier.lid;
let type = query.op === 'saveRecord' ? 'mutation' : 'query';
if (!this._pending[lid]) {
this._pending[lid] = [];
}
let request = {
state: _fetchManager.RequestStateEnum.pending,
request: queryRequest,
type,
[Touching]: [query.recordIdentifier],
[RequestPromise]: promise
};
this._pending[lid].push(request);
this._triggerSubscriptions(request);
promise.then(result => {
this._dequeue(lid, request);
let finalizedRequest = {
state: _fetchManager.RequestStateEnum.fulfilled,
request: queryRequest,
type,
[Touching]: request[Touching],
response: {
data: result
}
};
this._addDone(finalizedRequest);
this._triggerSubscriptions(finalizedRequest);
}, error => {
this._dequeue(lid, request);
let finalizedRequest = {
state: _fetchManager.RequestStateEnum.rejected,
request: queryRequest,
type,
[Touching]: request[Touching],
response: {
data: error && error.error
}
};
this._addDone(finalizedRequest);
this._triggerSubscriptions(finalizedRequest);
});
}
}
_triggerSubscriptions(req) {
req[Touching].forEach(identifier => {
if (this._subscriptions[identifier.lid]) {
this._subscriptions[identifier.lid].forEach(callback => callback(req));
}
});
}
_dequeue(lid, request) {
this._pending[lid] = this._pending[lid].filter(req => req !== request);
}
_addDone(request) {
request[Touching].forEach(identifier => {
if (!this._done[identifier.lid]) {
this._done[identifier.lid] = [];
} // TODO add support for multiple
let requestDataOp = request.request.data[0].op;
this._done[identifier.lid] = this._done[identifier.lid].filter(req => {
// TODO add support for multiple
let data;
if (req.request.data instanceof Array) {
data = req.request.data[0];
} else {
data = req.request.data;
}
return data.op !== requestDataOp;
});
this._done[identifier.lid].push(request);
});
}
subscribeForRecord(identifier, callback) {
if (!this._subscriptions[identifier.lid]) {
this._subscriptions[identifier.lid] = [];
}
this._subscriptions[identifier.lid].push(callback);
}
getPendingRequestsForRecord(identifier) {
if (this._pending[identifier.lid]) {
return this._pending[identifier.lid];
}
return [];
}
getLastRequestForRecord(identifier) {
let requests = this._done[identifier.lid];
if (requests) {
return requests[requests.length - 1];
}
return null;
}
}
_exports.default = RequestCache;
});
define("@ember-data/store/-private/system/snapshot-record-array", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
/**
@class SnapshotRecordArray
@private
@constructor
@param {Array} snapshots An array of snapshots
@param {Object} meta
*/
class SnapshotRecordArray {
constructor(recordArray, meta, options = {}) {
/**
An array of snapshots
@private
@property _snapshots
@type {Array}
*/
this._snapshots = null;
/**
An array of records
@private
@property _recordArray
@type {Array}
*/
this._recordArray = recordArray;
/**
Number of records in the array
Example
```app/adapters/post.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
shouldReloadAll(store, snapshotRecordArray) {
return !snapshotRecordArray.length;
},
});
```
@property length
@type {Number}
*/
this.length = recordArray.get('length');
this._type = null;
/**
Meta objects for the record array.
Example
```app/adapters/post.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
shouldReloadAll(store, snapshotRecordArray) {
var lastRequestTime = snapshotRecordArray.meta.lastRequestTime;
var twentyMinutes = 20 * 60 * 1000;
return Date.now() > lastRequestTime + twentyMinutes;
},
});
```
@property meta
@type {Object}
*/
this.meta = meta;
/**
A hash of adapter options passed into the store method for this request.
Example
```app/adapters/post.js
import MyCustomAdapter from './custom-adapter';
export default MyCustomAdapter.extend({
findAll(store, type, sinceToken, snapshotRecordArray) {
if (snapshotRecordArray.adapterOptions.subscribe) {
// ...
}
// ...
}
});
```
@property adapterOptions
@type {Object}
*/
this.adapterOptions = options.adapterOptions;
/**
The relationships to include for this request.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
findAll(store, type, snapshotRecordArray) {
var url = `/${type.modelName}?include=${encodeURIComponent(snapshotRecordArray.include)}`;
return fetch(url).then((response) => response.json())
}
});
@property include
@type {String|Array}
*/
this.include = options.include;
}
/**
The type of the underlying records for the snapshots in the array, as a Model
@property type
@type {Model}
*/
get type() {
return this._type || (this._type = this._recordArray.get('type'));
}
/**
Get snapshots of the underlying record array
Example
```app/adapters/post.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default JSONAPIAdapter.extend({
shouldReloadAll(store, snapshotArray) {
var snapshots = snapshotArray.snapshots();
return snapshots.any(function(ticketSnapshot) {
var timeDiff = moment().diff(ticketSnapshot.attr('lastAccessedAt'), 'minutes');
if (timeDiff > 20) {
return true;
} else {
return false;
}
});
}
});
```
@method snapshots
@return {Array} Array of snapshots
*/
snapshots() {
if (this._snapshots !== null) {
return this._snapshots;
}
this._snapshots = this._recordArray._takeSnapshot();
return this._snapshots;
}
}
_exports.default = SnapshotRecordArray;
});
define("@ember-data/store/-private/system/snapshot", ["exports", "@ember-data/store/-private/system/record-data-for"], function (_exports, _recordDataFor) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@class Snapshot
@private
@constructor
@param {Model} internalModel The model to create a snapshot from
*/
class Snapshot {
constructor(options, identifier, store) {
this.__attributes = null;
this._belongsToRelationships = Object.create(null);
this._belongsToIds = Object.create(null);
this._hasManyRelationships = Object.create(null);
this._hasManyIds = Object.create(null);
let internalModel = this._internalModel = store._internalModelForResource(identifier);
this._store = store;
/*
If the internalModel does not yet have a record, then we are
likely a snapshot being provided to a find request, so we
populate __attributes lazily. Else, to preserve the "moment
in time" in which a snapshot is created, we greedily grab
the values.
*/
if (internalModel.hasRecord) {
this._attributes;
}
/**O
The id of the snapshot's underlying record
Example
```javascript
// store.push('post', { id: 1, author: 'Tomster', title: 'Ember.js rocks' });
postSnapshot.id; // => '1'
```
@property id
@type {String}
*/
this.id = internalModel.id;
/**
A hash of adapter options
@property adapterOptions
@type {Object}
*/
this.adapterOptions = options.adapterOptions;
this.include = options.include;
/**
The name of the type of the underlying record for this snapshot, as a string.
@property modelName
@type {String}
*/
this.modelName = internalModel.modelName;
this._changedAttributes = internalModel.changedAttributes();
}
/**
The underlying record for this snapshot. Can be used to access methods and
properties defined on the record.
Example
```javascript
let json = snapshot.record.toJSON();
```
@property record
@type {Model}
*/
get record() {
return this._internalModel.getRecord();
}
get _attributes() {
let attributes = this.__attributes;
if (attributes === null) {
let record = this.record;
attributes = this.__attributes = Object.create(null);
record.eachAttribute(keyName => attributes[keyName] = Ember.get(record, keyName));
}
return attributes;
}
/**
The type of the underlying record for this snapshot, as a Model.
@property type
@type {Model}
*/
get type() {
// TODO @runspired we should deprecate this in favor of modelClass but only once
// we've cleaned up the internals enough that a public change to follow suite is
// uncontroversial.
return this._internalModel.modelClass;
}
/**
Returns the value of an attribute.
Example
```javascript
// store.push('post', { id: 1, author: 'Tomster', title: 'Ember.js rocks' });
postSnapshot.attr('author'); // => 'Tomster'
postSnapshot.attr('title'); // => 'Ember.js rocks'
```
Note: Values are loaded eagerly and cached when the snapshot is created.
@method attr
@param {String} keyName
@return {Object} The attribute value or undefined
*/
attr(keyName) {
if (keyName in this._attributes) {
return this._attributes[keyName];
}
throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no attribute named '" + keyName + "' defined.");
}
/**
Returns all attributes and their corresponding values.
Example
```javascript
// store.push('post', { id: 1, author: 'Tomster', title: 'Ember.js rocks' });
postSnapshot.attributes(); // => { author: 'Tomster', title: 'Ember.js rocks' }
```
@method attributes
@return {Object} All attributes of the current snapshot
*/
attributes() {
return Ember.assign({}, this._attributes);
}
/**
Returns all changed attributes and their old and new values.
Example
```javascript
// store.push('post', { id: 1, author: 'Tomster', title: 'Ember.js rocks' });
postModel.set('title', 'Ember.js rocks!');
postSnapshot.changedAttributes(); // => { title: ['Ember.js rocks', 'Ember.js rocks!'] }
```
@method changedAttributes
@return {Object} All changed attributes of the current snapshot
*/
changedAttributes() {
let changedAttributes = Object.create(null);
let changedAttributeKeys = Object.keys(this._changedAttributes);
for (let i = 0, length = changedAttributeKeys.length; i < length; i++) {
let key = changedAttributeKeys[i];
changedAttributes[key] = this._changedAttributes[key].slice();
}
return changedAttributes;
}
/**
Returns the current value of a belongsTo relationship.
`belongsTo` takes an optional hash of options as a second parameter,
currently supported options are:
- `id`: set to `true` if you only want the ID of the related record to be
returned.
Example
```javascript
// store.push('post', { id: 1, title: 'Hello World' });
// store.createRecord('comment', { body: 'Lorem ipsum', post: post });
commentSnapshot.belongsTo('post'); // => Snapshot
commentSnapshot.belongsTo('post', { id: true }); // => '1'
// store.push('comment', { id: 1, body: 'Lorem ipsum' });
commentSnapshot.belongsTo('post'); // => undefined
```
Calling `belongsTo` will return a new Snapshot as long as there's any known
data for the relationship available, such as an ID. If the relationship is
known but unset, `belongsTo` will return `null`. If the contents of the
relationship is unknown `belongsTo` will return `undefined`.
Note: Relationships are loaded lazily and cached upon first access.
@method belongsTo
@param {String} keyName
@param {Object} [options]
@return {(Snapshot|String|null|undefined)} A snapshot or ID of a known
relationship or null if the relationship is known but unset. undefined
will be returned if the contents of the relationship is unknown.
*/
belongsTo(keyName, options) {
let id = options && options.id;
let relationship;
let inverseInternalModel;
let result;
let store = this._internalModel.store;
if (id && keyName in this._belongsToIds) {
return this._belongsToIds[keyName];
}
if (!id && keyName in this._belongsToRelationships) {
return this._belongsToRelationships[keyName];
}
let relationshipMeta = store._relationshipMetaFor(this.modelName, null, keyName);
if (!(relationshipMeta && relationshipMeta.kind === 'belongsTo')) {
throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no belongsTo relationship named '" + keyName + "' defined.");
}
relationship = (0, _recordDataFor.relationshipStateFor)(this, keyName);
let value = relationship.getData();
let data = value && value.data;
inverseInternalModel = data && store._internalModelForResource(data);
if (value && value.data !== undefined) {
if (inverseInternalModel && !inverseInternalModel.isDeleted()) {
if (id) {
result = Ember.get(inverseInternalModel, 'id');
} else {
result = inverseInternalModel.createSnapshot();
}
} else {
result = null;
}
}
if (id) {
this._belongsToIds[keyName] = result;
} else {
this._belongsToRelationships[keyName] = result;
}
return result;
}
/**
Returns the current value of a hasMany relationship.
`hasMany` takes an optional hash of options as a second parameter,
currently supported options are:
- `ids`: set to `true` if you only want the IDs of the related records to be
returned.
Example
```javascript
// store.push('post', { id: 1, title: 'Hello World', comments: [2, 3] });
postSnapshot.hasMany('comments'); // => [Snapshot, Snapshot]
postSnapshot.hasMany('comments', { ids: true }); // => ['2', '3']
// store.push('post', { id: 1, title: 'Hello World' });
postSnapshot.hasMany('comments'); // => undefined
```
Note: Relationships are loaded lazily and cached upon first access.
@method hasMany
@param {String} keyName
@param {Object} [options]
@return {(Array|undefined)} An array of snapshots or IDs of a known
relationship or an empty array if the relationship is known but unset.
undefined will be returned if the contents of the relationship is unknown.
*/
hasMany(keyName, options) {
let ids = options && options.ids;
let relationship;
let results;
if (ids && keyName in this._hasManyIds) {
return this._hasManyIds[keyName];
}
if (!ids && keyName in this._hasManyRelationships) {
return this._hasManyRelationships[keyName];
}
let store = this._internalModel.store;
let relationshipMeta = store._relationshipMetaFor(this.modelName, null, keyName);
if (!(relationshipMeta && relationshipMeta.kind === 'hasMany')) {
throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no hasMany relationship named '" + keyName + "' defined.");
}
relationship = (0, _recordDataFor.relationshipStateFor)(this, keyName);
let value = relationship.getData();
if (value.data) {
results = [];
value.data.forEach(member => {
let internalModel = store._internalModelForResource(member);
if (!internalModel.isDeleted()) {
if (ids) {
results.push(member.id);
} else {
results.push(internalModel.createSnapshot());
}
}
});
}
if (ids) {
this._hasManyIds[keyName] = results;
} else {
this._hasManyRelationships[keyName] = results;
}
return results;
}
/**
Iterates through all the attributes of the model, calling the passed
function on each attribute.
Example
```javascript
snapshot.eachAttribute(function(name, meta) {
// ...
});
```
@method eachAttribute
@param {Function} callback the callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
*/
eachAttribute(callback, binding) {
this.record.eachAttribute(callback, binding);
}
/**
Iterates through all the relationships of the model, calling the passed
function on each relationship.
Example
```javascript
snapshot.eachRelationship(function(name, relationship) {
// ...
});
```
@method eachRelationship
@param {Function} callback the callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
*/
eachRelationship(callback, binding) {
this.record.eachRelationship(callback, binding);
}
/**
Serializes the snapshot using the serializer for the model.
Example
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
createRecord(store, type, snapshot) {
var data = snapshot.serialize({ includeId: true });
var url = `/${type.modelName}`;
return fetch(url, {
method: 'POST',
body: data,
}).then((response) => response.json())
}
});
```
@method serialize
@param {Object} options
@return {Object} an object whose values are primitive JSON values only
*/
serialize(options) {
return this.record.store.serializerFor(this.modelName).serialize(this, options);
}
}
_exports.default = Snapshot;
});
define("@ember-data/store/-private/system/store", ["exports", "@ember-data/adapter/error", "@ember-data/store/-private/system/model/model", "@ember-data/store/-private/system/normalize-model-name", "@ember-data/store/-private/system/store/record-data-store-wrapper", "@ember-data/store/-private/system/promise-proxies", "@ember-data/store/-private/system/store/common", "@ember-data/store/-private/system/store/serializer-response", "@ember-data/store/-private/system/store/serializers", "@ember-data/store/-private/system/record-data-for", "@ember-data/store/-private/system/fetch-manager", "@ember-data/store/-private/system/store/finders", "@ember-data/store/-private/system/coerce-id", "@ember-data/store/-private/system/record-array-manager", "@ember-data/store/-private/system/model/record-data", "@ember-data/store/-private/system/backburner", "@ember-data/canary-features", "@ember-data/store/-private/utils/promise-record", "@ember-data/store/-private/identifiers/cache", "@ember-data/store/-private/system/store/internal-model-factory", "@ember-data/store/-private/utils/has-valid-id", "@ember-data/store/-private/system/request-cache"], function (_exports, _error, _model, _normalizeModelName, _recordDataStoreWrapper, _promiseProxies, _common, _serializerResponse, _serializers, _recordDataFor, _fetchManager, _finders, _coerceId, _recordArrayManager, _recordData, _backburner, _canaryFeatures, _promiseRecord, _cache, _internalModelFactory, _hasValidId, _requestCache) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const emberRun = Ember.run.backburner;
const {
ENV
} = Ember;
let globalClientIdCounter = 1; // Implementors Note:
//
// The variables in this file are consistently named according to the following
// scheme:
//
// * +id+ means an identifier managed by an external source, provided inside
// the data provided by that source. These are always coerced to be strings
// before being used internally.
// * +clientId+ means a transient numerical identifier generated at runtime by
// the data store. It is important primarily because newly created objects may
// not yet have an externally generated id.
// * +internalModel+ means a record internalModel object, which holds metadata about a
// record, even if it has not yet been fully materialized.
// * +type+ means a Model.
/**
The store contains all of the data for records loaded from the server.
It is also responsible for creating instances of `Model` that wrap
the individual data for a record, so that they can be bound to in your
Handlebars templates.
Define your application's store like this:
```app/services/store.js
import Store from '@ember-data/store';
export default Store.extend({
});
```
Most Ember.js applications will only have a single `Store` that is
automatically created by their `Ember.Application`.
You can retrieve models from the store in several ways. To retrieve a record
for a specific id, use `Store`'s `findRecord()` method:
```javascript
store.findRecord('person', 123).then(function (person) {
});
```
By default, the store will talk to your backend using a standard
REST mechanism. You can customize how the store talks to your
backend by specifying a custom adapter:
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
});
```
You can learn more about writing a custom adapter by reading the `Adapter`
documentation.
### Store createRecord() vs. push() vs. pushPayload()
The store provides multiple ways to create new record objects. They have
some subtle differences in their use which are detailed below:
[createRecord](#method_createRecord) is used for creating new
records on the client side. This will return a new record in the
`created.uncommitted` state. In order to persist this record to the
backend, you will need to call `record.save()`.
[push](#method_push) is used to notify Ember Data's store of new or
updated records that exist in the backend. This will return a record
in the `loaded.saved` state. The primary use-case for `store#push` is
to notify Ember Data about record updates (full or partial) that happen
outside of the normal adapter methods (for example
[SSE](http://dev.w3.org/html5/eventsource/) or [Web
Sockets](http://www.w3.org/TR/2009/WD-websockets-20091222/)).
[pushPayload](#method_pushPayload) is a convenience wrapper for
`store#push` that will deserialize payloads if the
Serializer implements a `pushPayload` method.
Note: When creating a new record using any of the above methods
Ember Data will update `RecordArray`s such as those returned by
`store#peekAll()` or `store#findAll()`. This means any
data bindings or computed properties that depend on the RecordArray
will automatically be synced to include the new or updated record
values.
@class Store
@extends Ember.Service
*/
class Store extends Ember.Service {
/**
* EmberData specific backburner instance
*/
/*
Ember Data uses several specialized micro-queues for organizing
and coalescing similar async work.
These queues are currently controlled by a flush scheduled into
ember-data's custom backburner instance.
*/
// used for coalescing record save requests
// used for coalescing relationship updates
// used for coalescing internal model updates
// used to keep track of all the find requests that need to be coalesced
// DEBUG-only properties
/**
The default adapter to use to communicate to a backend server or
other persistence layer. This will be overridden by an application
adapter if present.
If you want to specify `app/adapters/custom.js` as a string, do:
```js
import Store from '@ember-data/store';
export default Store.extend({
init() {
this._super(...arguments);
this.adapter = 'custom';
}
});
```
@property adapter
@default '-json-api'
@type {String}
*/
/**
This property returns the adapter, after resolving a possible
string key.
If the supplied `adapter` was a class, or a String property
path resolved to a class, this property will instantiate the
class.
This property is cacheable, so the same instance of a specified
adapter class should be used for the lifetime of the store.
@property defaultAdapter
@private
@return Adapter
*/
/**
@method init
@private
*/
constructor() {
super(...arguments);
_defineProperty(this, "_backburner", _backburner.default);
_defineProperty(this, "recordArrayManager", new _recordArrayManager.default({
store: this
}));
_defineProperty(this, "_modelFactoryCache", Object.create(null));
_defineProperty(this, "_relationshipsDefCache", Object.create(null));
_defineProperty(this, "_attributesDefCache", Object.create(null));
_defineProperty(this, "_adapterCache", Object.create(null));
_defineProperty(this, "_serializerCache", Object.create(null));
_defineProperty(this, "_storeWrapper", new _recordDataStoreWrapper.default(this));
_defineProperty(this, "_pendingSave", []);
_defineProperty(this, "_updatedRelationships", []);
_defineProperty(this, "_updatedInternalModels", []);
_defineProperty(this, "_pendingFetch", new Map());
_defineProperty(this, "_fetchManager", void 0);
_defineProperty(this, "_trackedAsyncRequests", void 0);
_defineProperty(this, "shouldAssertMethodCallsOnDestroyedStore", false);
_defineProperty(this, "shouldTrackAsyncRequests", false);
_defineProperty(this, "generateStackTracesForTrackedRequests", false);
_defineProperty(this, "_trackAsyncRequestStart", void 0);
_defineProperty(this, "_trackAsyncRequestEnd", void 0);
_defineProperty(this, "__asyncWaiter", void 0);
if (_canaryFeatures.REQUEST_SERVICE) {
this._fetchManager = new _fetchManager.default(this);
}
if (true
/* DEBUG */
) {
this.shouldAssertMethodCallsOnDestroyedStore = this.shouldAssertMethodCallsOnDestroyedStore || false;
if (this.shouldTrackAsyncRequests === undefined) {
this.shouldTrackAsyncRequests = false;
}
if (this.generateStackTracesForTrackedRequests === undefined) {
this.generateStackTracesForTrackedRequests = false;
}
this._trackedAsyncRequests = [];
this._trackAsyncRequestStart = label => {
let trace = 'set `store.generateStackTracesForTrackedRequests = true;` to get a detailed trace for where this request originated';
if (this.generateStackTracesForTrackedRequests) {
try {
throw new Error("EmberData TrackedRequest: ".concat(label));
} catch (e) {
trace = e;
}
}
let token = Object.freeze({
label,
trace
});
this._trackedAsyncRequests.push(token);
return token;
};
this._trackAsyncRequestEnd = token => {
let index = this._trackedAsyncRequests.indexOf(token);
if (index === -1) {
throw new Error("Attempted to end tracking for the following request but it was not being tracked:\n".concat(token));
}
this._trackedAsyncRequests.splice(index, 1);
};
this.__asyncWaiter = () => {
let shouldTrack = this.shouldTrackAsyncRequests;
let tracked = this._trackedAsyncRequests;
let isSettled = tracked.length === 0;
return shouldTrack !== true || isSettled;
};
Ember.Test.registerWaiter(this.__asyncWaiter);
}
}
getRequestStateService() {
if (_canaryFeatures.REQUEST_SERVICE) {
return this._fetchManager.requestCache;
}
throw 'RequestService is not available unless the feature flag is on and running on a canary build';
}
get identifierCache() {
if (!_canaryFeatures.IDENTIFIERS) {
throw new Error("Store.identifierCache is unavailable in this build of EmberData");
}
return (0, _cache.identifierCacheFor)(this);
} // .....................
// . CREATE NEW RECORD .
// .....................
/**
Create a new record in the current store. The properties passed
to this method are set on the newly created record.
To create a new instance of a `Post`:
```js
store.createRecord('post', {
title: 'Ember is awesome!'
});
```
To create a new instance of a `Post` that has a relationship with a `User` record:
```js
let user = this.store.peekRecord('user', 1);
store.createRecord('post', {
title: 'Ember is awesome!',
user: user
});
```
@method createRecord
@param {String} modelName
@param {Object} inputProperties a hash of properties to set on the
newly created record.
@return {Model} record
*/
createRecord(modelName, inputProperties) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'createRecord');
}
(true && Ember.assert("You need to pass a model name to the store's createRecord method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string')); // This is wrapped in a `run.join` so that in test environments users do not need to manually wrap
// calls to `createRecord`. The run loop usage here is because we batch the joining and updating
// of record-arrays via ember's run loop, not our own.
//
// to remove this, we would need to move to a new `async` API.
return emberRun.join(() => {
return this._backburner.join(() => {
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let properties = Ember.assign({}, inputProperties); // If the passed properties do not include a primary key,
// give the adapter an opportunity to generate one. Typically,
// client-side ID generators will use something like uuid.js
// to avoid conflicts.
if (Ember.isNone(properties.id)) {
properties.id = this._generateId(normalizedModelName, properties);
} // Coerce ID to a string
properties.id = (0, _coerceId.default)(properties.id);
const factory = (0, _internalModelFactory.internalModelFactoryFor)(this);
const internalModel = factory.build(normalizedModelName, properties.id);
internalModel.loadedData(); // TODO this exists just to proxy `isNew` to RecordData which is weird
internalModel.didCreateRecord();
return internalModel.getRecord(properties);
});
});
}
/**
If possible, this method asks the adapter to generate an ID for
a newly created record.
@method _generateId
@private
@param {String} modelName
@param {Object} properties from the new record
@return {String} if the adapter can generate one, an ID
*/
_generateId(modelName, properties) {
let adapter = this.adapterFor(modelName);
if (adapter && adapter.generateIdForRecord) {
return adapter.generateIdForRecord(this, modelName, properties);
}
return null;
} // .................
// . DELETE RECORD .
// .................
/**
For symmetry, a record can be deleted via the store.
Example
```javascript
let post = store.createRecord('post', {
title: 'Ember is awesome!'
});
store.deleteRecord(post);
```
@method deleteRecord
@param {Model} record
*/
deleteRecord(record) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'deleteRecord');
}
record.deleteRecord();
}
/**
For symmetry, a record can be unloaded via the store.
This will cause the record to be destroyed and freed up for garbage collection.
Example
```javascript
store.findRecord('post', 1).then(function(post) {
store.unloadRecord(post);
});
```
@method unloadRecord
@param {Model} record
*/
unloadRecord(record) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'unloadRecord');
}
record.unloadRecord();
} // ................
// . FIND RECORDS .
// ................
/**
@method find
@param {String} modelName
@param {String|Integer} id
@param {Object} options
@return {Promise} promise
@private
*/
find(modelName, id, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'find');
} // The default `model` hook in Route calls `find(modelName, id)`,
// that's why we have to keep this method around even though `findRecord` is
// the public way to get a record by modelName and id.
(true && Ember.assert("Using store.find(type) has been removed. Use store.findAll(modelName) to retrieve all records for a given type.", arguments.length !== 1));
(true && Ember.assert("Calling store.find(modelName, id, { preload: preload }) is no longer supported. Use store.findRecord(modelName, id, { preload: preload }) instead.", !options));
(true && Ember.assert("You need to pass the model name and id to the store's find method", arguments.length === 2));
(true && Ember.assert("You cannot pass '".concat(id, "' as id to the store's find method"), typeof id === 'string' || typeof id === 'number'));
(true && Ember.assert("Calling store.find() with a query object is no longer supported. Use store.query() instead.", typeof id !== 'object'));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
return this.findRecord(modelName, id);
}
/**
This method returns a record for a given type and id combination.
The `findRecord` method will always resolve its promise with the same
object for a given type and `id`.
The `findRecord` method will always return a **promise** that will be
resolved with the record.
Example
```app/routes/post.js
import Route from '@ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findRecord('post', params.post_id);
}
});
```
If the record is not yet available, the store will ask the adapter's `find`
method to find the necessary data. If the record is already present in the
store, it depends on the reload behavior _when_ the returned promise
resolves.
### Preloading
You can optionally `preload` specific attributes and relationships that you know of
by passing them via the passed `options`.
For example, if your Ember route looks like `/posts/1/comments/2` and your API route
for the comment also looks like `/posts/1/comments/2` if you want to fetch the comment
without fetching the post you can pass in the post to the `findRecord` call:
```javascript
store.findRecord('comment', 2, { preload: { post: 1 } });
```
If you have access to the post model you can also pass the model itself:
```javascript
store.findRecord('post', 1).then(function (myPostModel) {
store.findRecord('comment', 2, { post: myPostModel });
});
```
### Reloading
The reload behavior is configured either via the passed `options` hash or
the result of the adapter's `shouldReloadRecord`.
If `{ reload: true }` is passed or `adapter.shouldReloadRecord` evaluates
to `true`, then the returned promise resolves once the adapter returns
data, regardless if the requested record is already in the store:
```js
store.push({
data: {
id: 1,
type: 'post',
revision: 1
}
});
// adapter#findRecord resolves with
// [
// {
// id: 1,
// type: 'post',
// revision: 2
// }
// ]
store.findRecord('post', 1, { reload: true }).then(function(post) {
post.get('revision'); // 2
});
```
If no reload is indicated via the above mentioned ways, then the promise
immediately resolves with the cached version in the store.
### Background Reloading
Optionally, if `adapter.shouldBackgroundReloadRecord` evaluates to `true`,
then a background reload is started, which updates the records' data, once
it is available:
```js
// app/adapters/post.js
import ApplicationAdapter from "./application";
export default ApplicationAdapter.extend({
shouldReloadRecord(store, snapshot) {
return false;
},
shouldBackgroundReloadRecord(store, snapshot) {
return true;
}
});
// ...
store.push({
data: {
id: 1,
type: 'post',
revision: 1
}
});
let blogPost = store.findRecord('post', 1).then(function(post) {
post.get('revision'); // 1
});
// later, once adapter#findRecord resolved with
// [
// {
// id: 1,
// type: 'post',
// revision: 2
// }
// ]
blogPost.get('revision'); // 2
```
If you would like to force or prevent background reloading, you can set a
boolean value for `backgroundReload` in the options object for
`findRecord`.
```app/routes/post/edit.js
import Route from '@ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findRecord('post', params.post_id, { backgroundReload: false });
}
});
```
If you pass an object on the `adapterOptions` property of the options
argument it will be passed to you adapter via the snapshot
```app/routes/post/edit.js
import Route from '@ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findRecord('post', params.post_id, {
adapterOptions: { subscribe: false }
});
}
});
```
```app/adapters/post.js
import MyCustomAdapter from './custom-adapter';
export default MyCustomAdapter.extend({
findRecord(store, type, id, snapshot) {
if (snapshot.adapterOptions.subscribe) {
// ...
}
// ...
}
});
```
See [peekRecord](#method_peekRecord) to get the cached version of a record.
### Retrieving Related Model Records
If you use an adapter such as Ember's default
[`JSONAPIAdapter`](https://emberjs.com/api/data/classes/DS.JSONAPIAdapter.html)
that supports the [JSON API specification](http://jsonapi.org/) and if your server
endpoint supports the use of an
['include' query parameter](http://jsonapi.org/format/#fetching-includes),
you can use `findRecord()` to automatically retrieve additional records related to
the one you request by supplying an `include` parameter in the `options` object.
For example, given a `post` model that has a `hasMany` relationship with a `comment`
model, when we retrieve a specific post we can have the server also return that post's
comments in the same request:
```app/routes/post.js
import Route from '@ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findRecord('post', params.post_id, { include: 'comments' });
}
});
```
In this case, the post's comments would then be available in your template as
`model.comments`.
Multiple relationships can be requested using an `include` parameter consisting of a
comma-separated list (without white-space) while nested relationships can be specified
using a dot-separated sequence of relationship names. So to request both the post's
comments and the authors of those comments the request would look like this:
```app/routes/post.js
import Route from '@ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findRecord('post', params.post_id, { include: 'comments,comments.author' });
}
});
```
@since 1.13.0
@method findRecord
@param {String} modelName
@param {(String|Integer)} id
@param {Object} [options]
@param {Object} preload - optional set of attributes and relationships passed in either as IDs or as actual models
@return {Promise} promise
*/
findRecord(modelName, id, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'findRecord');
}
(true && Ember.assert("You need to pass a model name to the store's findRecord method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
const normalizedModelName = (0, _normalizeModelName.default)(modelName);
const normalizedId = (0, _coerceId.ensureStringId)(id);
const internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(normalizedModelName, normalizedId, null);
options = options || {};
if (!this.hasRecordForId(normalizedModelName, normalizedId)) {
return this._findByInternalModel(internalModel, options);
}
let fetchedInternalModel = this._findRecord(internalModel, options);
return (0, _promiseRecord.default)(fetchedInternalModel, "DS: Store#findRecord ".concat(normalizedModelName, " with id: ").concat(id));
}
_findRecord(internalModel, options) {
// Refetch if the reload option is passed
if (options.reload) {
return this._scheduleFetch(internalModel, options);
}
let snapshot = internalModel.createSnapshot(options);
let adapter = this.adapterFor(internalModel.modelName); // Refetch the record if the adapter thinks the record is stale
if (adapter.shouldReloadRecord(this, snapshot)) {
return this._scheduleFetch(internalModel, options);
}
if (options.backgroundReload === false) {
return Ember.RSVP.Promise.resolve(internalModel);
} // Trigger the background refetch if backgroundReload option is passed
if (options.backgroundReload || adapter.shouldBackgroundReloadRecord(this, snapshot)) {
this._scheduleFetch(internalModel, options);
} // Return the cached record
return Ember.RSVP.Promise.resolve(internalModel);
}
_findByInternalModel(internalModel, options = {}) {
if (options.preload) {
internalModel.preloadData(options.preload);
}
let fetchedInternalModel = this._findEmptyInternalModel(internalModel, options);
return (0, _promiseRecord.default)(fetchedInternalModel, "DS: Store#findRecord ".concat(internalModel.modelName, " with id: ").concat(internalModel.id));
}
_findEmptyInternalModel(internalModel, options) {
if (internalModel.isEmpty()) {
return this._scheduleFetch(internalModel, options);
} //TODO double check about reloading
if (!_canaryFeatures.REQUEST_SERVICE) {
if (internalModel.isLoading()) {
return internalModel._promiseProxy;
}
} else {
if (internalModel.isLoading()) {
return this._scheduleFetch(internalModel, options);
}
}
return Ember.RSVP.Promise.resolve(internalModel);
}
/**
This method makes a series of requests to the adapter's `find` method
and returns a promise that resolves once they are all loaded.
@private
@method findByIds
@param {String} modelName
@param {Array} ids
@return {Promise} promise
*/
findByIds(modelName, ids) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'findByIds');
}
(true && Ember.assert("You need to pass a model name to the store's findByIds method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let promises = new Array(ids.length);
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
for (let i = 0; i < ids.length; i++) {
promises[i] = this.findRecord(normalizedModelName, ids[i]);
}
return (0, _promiseProxies.promiseArray)(Ember.RSVP.all(promises).then(Ember.A, null, "DS: Store#findByIds of ".concat(normalizedModelName, " complete")));
}
/**
This method is called by `findRecord` if it discovers that a particular
type/id pair hasn't been loaded yet to kick off a request to the
adapter.
@method _fetchRecord
@private
@param {InternalModel} internalModel model
@return {Promise} promise
*/
_fetchRecord(internalModel, options) {
let modelName = internalModel.modelName;
let adapter = this.adapterFor(modelName);
(true && Ember.assert("You tried to find a record but you have no adapter (for ".concat(modelName, ")"), adapter));
(true && Ember.assert("You tried to find a record but your adapter (for ".concat(modelName, ") does not implement 'findRecord'"), typeof adapter.findRecord === 'function'));
return (0, _finders._find)(adapter, this, internalModel.type, internalModel.id, internalModel, options);
}
_scheduleFetchMany(internalModels, options) {
let fetches = new Array(internalModels.length);
for (let i = 0; i < internalModels.length; i++) {
fetches[i] = this._scheduleFetch(internalModels[i], options);
}
return Ember.RSVP.Promise.all(fetches);
}
_scheduleFetchThroughFetchManager(internalModel, options = {}) {
let generateStackTrace = this.generateStackTracesForTrackedRequests; // TODO remove this once we dont rely on state machine
internalModel.loadingData();
let identifier = internalModel.identifier;
let promise = this._fetchManager.scheduleFetch(internalModel.identifier, options, generateStackTrace);
return promise.then(payload => {
if (_canaryFeatures.IDENTIFIERS) {
// ensure that regardless of id returned we assign to the correct record
if (payload.data && !Array.isArray(payload.data)) {
payload.data.lid = identifier.lid;
}
} // Returning this._push here, breaks typing but not any tests, invesstigate potential missing tests
let potentiallyNewIm = this._push(payload);
if (potentiallyNewIm && !Array.isArray(potentiallyNewIm)) {
return potentiallyNewIm;
} else {
return internalModel;
}
}, error => {
// TODO remove this once we dont rely on state machine
internalModel.notFound();
if (internalModel.isEmpty()) {
internalModel.unloadRecord();
}
throw error;
});
}
_scheduleFetch(internalModel, options) {
if (_canaryFeatures.REQUEST_SERVICE) {
return this._scheduleFetchThroughFetchManager(internalModel, options);
} else {
if (internalModel._promiseProxy) {
return internalModel._promiseProxy;
}
let {
id,
modelName
} = internalModel;
let resolver = Ember.RSVP.defer("Fetching ".concat(modelName, "' with id: ").concat(id));
let pendingFetchItem = {
internalModel,
resolver,
options
};
if (true
/* DEBUG */
) {
if (this.generateStackTracesForTrackedRequests === true) {
let trace;
try {
throw new Error("Trace Origin for scheduled fetch for ".concat(modelName, ":").concat(id, "."));
} catch (e) {
trace = e;
} // enable folks to discover the origin of this findRecord call when
// debugging. Ideally we would have a tracked queue for requests with
// labels or local IDs that could be used to merge this trace with
// the trace made available when we detect an async leak
pendingFetchItem.trace = trace;
}
}
let promise = resolver.promise;
internalModel.loadingData(promise);
if (this._pendingFetch.size === 0) {
emberRun.schedule('actions', this, this.flushAllPendingFetches);
}
let fetches = this._pendingFetch;
let pending = fetches.get(modelName);
if (pending === undefined) {
pending = [];
fetches.set(modelName, pending);
}
pending.push(pendingFetchItem);
return promise;
}
}
flushAllPendingFetches() {
if (_canaryFeatures.REQUEST_SERVICE) {
return; //assert here
} else {
if (this.isDestroyed || this.isDestroying) {
return;
}
this._pendingFetch.forEach(this._flushPendingFetchForType, this);
this._pendingFetch.clear();
}
}
_flushPendingFetchForType(pendingFetchItems, modelName) {
let store = this;
let adapter = store.adapterFor(modelName);
let shouldCoalesce = !!adapter.findMany && adapter.coalesceFindRequests;
let totalItems = pendingFetchItems.length;
let internalModels = new Array(totalItems);
let seeking = Object.create(null);
let optionsMap = new WeakMap();
for (let i = 0; i < totalItems; i++) {
let pendingItem = pendingFetchItems[i];
let internalModel = pendingItem.internalModel;
internalModels[i] = internalModel;
optionsMap.set(internalModel, pendingItem.options); // We can remove this "not null" cast once we have enough typing
// to know we are only dealing with ExistingResourceIdentifierObjects
seeking[internalModel.id] = pendingItem;
}
function _fetchRecord(recordResolverPair) {
let recordFetch = store._fetchRecord(recordResolverPair.internalModel, recordResolverPair.options);
recordResolverPair.resolver.resolve(recordFetch);
}
function handleFoundRecords(foundInternalModels, expectedInternalModels) {
// resolve found records
let found = Object.create(null);
for (let i = 0, l = foundInternalModels.length; i < l; i++) {
let internalModel = foundInternalModels[i]; // We can remove this "not null" cast once we have enough typing
// to know we are only dealing with ExistingResourceIdentifierObjects
let pair = seeking[internalModel.id];
found[internalModel.id] = internalModel;
if (pair) {
let resolver = pair.resolver;
resolver.resolve(internalModel);
}
} // reject missing records
let missingInternalModels = [];
for (let i = 0, l = expectedInternalModels.length; i < l; i++) {
let internalModel = expectedInternalModels[i]; // We can remove this "not null" cast once we have enough typing
// to know we are only dealing with ExistingResourceIdentifierObjects
if (!found[internalModel.id]) {
missingInternalModels.push(internalModel);
}
}
if (missingInternalModels.length) {
(true && Ember.warn('Ember Data expected to find records with the following ids in the adapter response but they were missing: [ "' + missingInternalModels.map(r => r.id).join('", "') + '" ]', false, {
id: 'ds.store.missing-records-from-adapter'
}));
rejectInternalModels(missingInternalModels);
}
}
function rejectInternalModels(internalModels, error) {
for (let i = 0, l = internalModels.length; i < l; i++) {
let internalModel = internalModels[i]; // We can remove this "not null" cast once we have enough typing
// to know we are only dealing with ExistingResourceIdentifierObjects
let pair = seeking[internalModel.id];
if (pair) {
pair.resolver.reject(error || new Error("Expected: '".concat(internalModel, "' to be present in the adapter provided payload, but it was not found.")));
}
}
}
if (shouldCoalesce) {
// TODO: Improve records => snapshots => records => snapshots
//
// We want to provide records to all store methods and snapshots to all
// adapter methods. To make sure we're doing that we're providing an array
// of snapshots to adapter.groupRecordsForFindMany(), which in turn will
// return grouped snapshots instead of grouped records.
//
// But since the _findMany() finder is a store method we need to get the
// records from the grouped snapshots even though the _findMany() finder
// will once again convert the records to snapshots for adapter.findMany()
let snapshots = new Array(totalItems);
for (let i = 0; i < totalItems; i++) {
snapshots[i] = internalModels[i].createSnapshot(optionsMap.get(internalModel));
}
let groups = adapter.groupRecordsForFindMany(this, snapshots);
for (var i = 0, l = groups.length; i < l; i++) {
var group = groups[i];
var totalInGroup = groups[i].length;
var ids = new Array(totalInGroup);
var groupedInternalModels = new Array(totalInGroup);
for (var j = 0; j < totalInGroup; j++) {
var internalModel = group[j]._internalModel;
groupedInternalModels[j] = internalModel;
ids[j] = internalModel.id;
}
if (totalInGroup > 1) {
(function (groupedInternalModels) {
(0, _finders._findMany)(adapter, store, modelName, ids, groupedInternalModels, optionsMap).then(function (foundInternalModels) {
handleFoundRecords(foundInternalModels, groupedInternalModels);
}).catch(function (error) {
rejectInternalModels(groupedInternalModels, error);
});
})(groupedInternalModels);
} else if (ids.length === 1) {
var pair = seeking[groupedInternalModels[0].id];
_fetchRecord(pair);
} else {
(true && Ember.assert("You cannot return an empty array from adapter's method groupRecordsForFindMany", false));
}
}
} else {
for (let i = 0; i < totalItems; i++) {
_fetchRecord(pendingFetchItems[i]);
}
}
}
/**
Get the reference for the specified record.
Example
```javascript
let userRef = store.getReference('user', 1);
// check if the user is loaded
let isLoaded = userRef.value() !== null;
// get the record of the reference (null if not yet available)
let user = userRef.value();
// get the identifier of the reference
if (userRef.remoteType() === 'id') {
let id = userRef.id();
}
// load user (via store.find)
userRef.load().then(...)
// or trigger a reload
userRef.reload().then(...)
// provide data for reference
userRef.push({ id: 1, username: '@user' }).then(function(user) {
userRef.value() === user;
});
```
@method getReference
@param {String} modelName
@param {String|Integer} id
@since 2.5.0
@return {RecordReference}
*/
getReference(modelName, id) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'getReference');
}
const normalizedModelName = (0, _normalizeModelName.default)(modelName);
const normalizedId = (0, _coerceId.ensureStringId)(id);
(true && Ember.assert("You must pass a valid id to getReference", typeof normalizedId === 'string' && normalizedId.length > 0));
return (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(normalizedModelName, normalizedId, null).recordReference;
}
/**
Get a record by a given type and ID without triggering a fetch.
This method will synchronously return the record if it is available in the store,
otherwise it will return `null`. A record is available if it has been fetched earlier, or
pushed manually into the store.
See [findRecord](#method_findRecord) if you would like to request this record from the backend.
_Note: This is a synchronous method and does not return a promise._
```js
let post = store.peekRecord('post', 1);
post.get('id'); // 1
```
@since 1.13.0
@method peekRecord
@param {String} modelName
@param {String|Integer} id
@return {Model|null} record
*/
peekRecord(modelName, id) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'peekRecord');
}
(true && Ember.assert("You need to pass a model name to the store's peekRecord method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
const normalizedId = (0, _coerceId.ensureStringId)(id);
if (this.hasRecordForId(normalizedModelName, normalizedId)) {
return (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(normalizedModelName, normalizedId, null).getRecord();
} else {
return null;
}
}
/**
This method is called by the record's `reload` method.
This method calls the adapter's `find` method, which returns a promise. When
**that** promise resolves, `_reloadRecord` will resolve the promise returned
by the record's `reload`.
@method _reloadRecord
@private
@param {Model} internalModel
@param options optional to include adapterOptions
@return {Promise} promise
*/
_reloadRecord(internalModel, options) {
if (_canaryFeatures.REQUEST_SERVICE) {
options.isReloading = true;
}
let {
id,
modelName
} = internalModel;
let adapter = this.adapterFor(modelName);
(true && Ember.assert("You cannot reload a record without an ID", id));
(true && Ember.assert("You tried to reload a record but you have no adapter (for ".concat(modelName, ")"), adapter));
(true && Ember.assert("You tried to reload a record but your adapter does not implement 'findRecord'", typeof adapter.findRecord === 'function' || typeof adapter.find === 'function'));
return this._scheduleFetch(internalModel, options);
}
/**
This method returns true if a record for a given modelName and id is already
loaded in the store. Use this function to know beforehand if a findRecord()
will result in a request or that it will be a cache hit.
Example
```javascript
store.hasRecordForId('post', 1); // false
store.findRecord('post', 1).then(function() {
store.hasRecordForId('post', 1); // true
});
```
@method hasRecordForId
@param {String} modelName
@param {(String|Integer)} id
@return {Boolean}
*/
hasRecordForId(modelName, id) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'hasRecordForId');
}
(true && Ember.assert("You need to pass a model name to the store's hasRecordForId method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
const normalizedModelName = (0, _normalizeModelName.default)(modelName);
const trueId = (0, _coerceId.ensureStringId)(id);
const internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this).peek(normalizedModelName, trueId, null);
return !!internalModel && internalModel.isLoaded();
}
/**
Returns id record for a given type and ID. If one isn't already loaded,
it builds a new record and leaves it in the `empty` state.
@method recordForId
@private
@param {String} modelName
@param {(String|Integer)} id
@return {Model} record
*/
recordForId(modelName, id) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'recordForId');
}
(true && Ember.assert("You need to pass a model name to the store's recordForId method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
const trueId = (0, _coerceId.ensureStringId)(id);
return (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(modelName, trueId, null).getRecord();
}
/**
@method findMany
@private
@param {Array} internalModels
@return {Promise} promise
*/
findMany(internalModels, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'findMany');
}
let finds = new Array(internalModels.length);
for (let i = 0; i < internalModels.length; i++) {
finds[i] = this._findEmptyInternalModel(internalModels[i], options);
}
return Ember.RSVP.Promise.all(finds);
}
/**
If a relationship was originally populated by the adapter as a link
(as opposed to a list of IDs), this method is called when the
relationship is fetched.
The link (which is usually a URL) is passed through unchanged, so the
adapter can make whatever request it wants.
The usual use-case is for the server to register a URL as a link, and
then use that URL in the future to make a request for the relationship.
@method findHasMany
@private
@param {InternalModel} internalModel
@param {any} link
@param {(Relationship)} relationship
@return {Promise} promise
*/
findHasMany(internalModel, link, relationship, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'findHasMany');
}
let adapter = this.adapterFor(internalModel.modelName);
(true && Ember.assert("You tried to load a hasMany relationship but you have no adapter (for ".concat(internalModel.modelName, ")"), adapter));
(true && Ember.assert("You tried to load a hasMany relationship from a specified 'link' in the original payload but your adapter does not implement 'findHasMany'", typeof adapter.findHasMany === 'function'));
return (0, _finders._findHasMany)(adapter, this, internalModel, link, relationship, options);
}
_findHasManyByJsonApiResource(resource, parentInternalModel, relationshipMeta, options) {
if (!resource) {
return Ember.RSVP.resolve([]);
}
let {
relationshipIsStale,
allInverseRecordsAreLoaded,
hasDematerializedInverse,
hasAnyRelationshipData,
relationshipIsEmpty,
shouldForceReload
} = resource._relationship;
let shouldFindViaLink = resource.links && resource.links.related && (shouldForceReload || hasDematerializedInverse || relationshipIsStale || !allInverseRecordsAreLoaded && !relationshipIsEmpty); // fetch via link
if (shouldFindViaLink) {
return this.findHasMany(parentInternalModel, resource.links.related, relationshipMeta, options).then(internalModels => {
let payload = {
data: internalModels.map(im => (0, _recordDataFor.default)(im).getResourceIdentifier())
};
if (internalModels.meta !== undefined) {
payload.meta = internalModels.meta;
}
parentInternalModel.linkWasLoadedForRelationship(relationshipMeta.key, payload);
return internalModels;
});
}
let preferLocalCache = hasAnyRelationshipData && !relationshipIsEmpty;
let hasLocalPartialData = hasDematerializedInverse || relationshipIsEmpty && Array.isArray(resource.data) && resource.data.length > 0; // fetch using data, pulling from local cache if possible
if (!shouldForceReload && !relationshipIsStale && (preferLocalCache || hasLocalPartialData)) {
let internalModels = resource.data.map(json => this._internalModelForResource(json));
return this.findMany(internalModels, options);
}
let hasData = hasAnyRelationshipData && !relationshipIsEmpty; // fetch by data
if (hasData || hasLocalPartialData) {
let internalModels = resource.data.map(json => this._internalModelForResource(json));
return this._scheduleFetchMany(internalModels, options);
} // we were explicitly told we have no data and no links.
// TODO if the relationshipIsStale, should we hit the adapter anyway?
return Ember.RSVP.resolve([]);
}
_getHasManyByJsonApiResource(resource) {
let internalModels = [];
if (resource && resource.data) {
internalModels = resource.data.map(reference => this._internalModelForResource(reference));
}
return internalModels;
}
/**
@method findBelongsTo
@private
@param {InternalModel} internalModel
@param {any} link
@param {Relationship} relationship
@return {Promise} promise
*/
findBelongsTo(internalModel, link, relationship, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'findBelongsTo');
}
let adapter = this.adapterFor(internalModel.modelName);
(true && Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for ".concat(internalModel.modelName, ")"), adapter));
(true && Ember.assert("You tried to load a belongsTo relationship from a specified 'link' in the original payload but your adapter does not implement 'findBelongsTo'", typeof adapter.findBelongsTo === 'function'));
return (0, _finders._findBelongsTo)(adapter, this, internalModel, link, relationship, options);
}
_fetchBelongsToLinkFromResource(resource, parentInternalModel, relationshipMeta, options) {
if (!resource || !resource.links || !resource.links.related) {
// should we warn here, not sure cause its an internal method
return Ember.RSVP.resolve(null);
}
return this.findBelongsTo(parentInternalModel, resource.links.related, relationshipMeta, options).then(internalModel => {
let response = internalModel && (0, _recordDataFor.default)(internalModel).getResourceIdentifier();
parentInternalModel.linkWasLoadedForRelationship(relationshipMeta.key, {
data: response
});
if (internalModel === null) {
return null;
} // TODO Igor this doesn't seem like the right boundary, probably the caller method should extract the record out
return internalModel.getRecord();
});
}
_findBelongsToByJsonApiResource(resource, parentInternalModel, relationshipMeta, options) {
if (!resource) {
return Ember.RSVP.resolve(null);
}
const internalModel = resource.data ? this._internalModelForResource(resource.data) : null;
let {
relationshipIsStale,
allInverseRecordsAreLoaded,
hasDematerializedInverse,
hasAnyRelationshipData,
relationshipIsEmpty,
shouldForceReload
} = resource._relationship;
let shouldFindViaLink = resource.links && resource.links.related && (shouldForceReload || hasDematerializedInverse || relationshipIsStale || !allInverseRecordsAreLoaded && !relationshipIsEmpty);
if (internalModel) {
// short circuit if we are already loading
if (_canaryFeatures.REQUEST_SERVICE) {
// Temporary fix for requests already loading until we move this inside the fetch manager
let pendingRequests = this.getRequestStateService().getPendingRequestsForRecord(internalModel.identifier).filter(req => req.type === 'query');
if (pendingRequests.length > 0) {
return pendingRequests[0][_requestCache.RequestPromise].then(() => internalModel.getRecord());
}
} else {
if (internalModel.isLoading()) {
return internalModel._promiseProxy.then(() => {
return internalModel.getRecord();
});
}
}
} // fetch via link
if (shouldFindViaLink) {
return this._fetchBelongsToLinkFromResource(resource, parentInternalModel, relationshipMeta, options);
}
let preferLocalCache = hasAnyRelationshipData && allInverseRecordsAreLoaded && !relationshipIsEmpty;
let hasLocalPartialData = hasDematerializedInverse || relationshipIsEmpty && resource.data; // null is explicit empty, undefined is "we don't know anything"
let localDataIsEmpty = resource.data === undefined || resource.data === null; // fetch using data, pulling from local cache if possible
if (!shouldForceReload && !relationshipIsStale && (preferLocalCache || hasLocalPartialData)) {
/*
We have canonical data, but our local state is empty
*/
if (localDataIsEmpty) {
return Ember.RSVP.resolve(null);
}
return this._findByInternalModel(internalModel, options);
}
let resourceIsLocal = !localDataIsEmpty && resource.data.id === null;
if (internalModel && resourceIsLocal) {
return Ember.RSVP.resolve(internalModel.getRecord());
} // fetch by data
if (internalModel && !localDataIsEmpty) {
return this._scheduleFetch(internalModel, options).then(() => {
return internalModel.getRecord();
});
} // we were explicitly told we have no data and no links.
// TODO if the relationshipIsStale, should we hit the adapter anyway?
return Ember.RSVP.resolve(null);
}
/**
This method delegates a query to the adapter. This is the one place where
adapter-level semantics are exposed to the application.
Each time this method is called a new request is made through the adapter.
Exposing queries this way seems preferable to creating an abstract query
language for all server-side queries, and then require all adapters to
implement them.
---
If you do something like this:
```javascript
store.query('person', { page: 1 });
```
The request made to the server will look something like this:
```
GET "/api/v1/person?page=1"
```
---
If you do something like this:
```javascript
store.query('person', { ids: [1, 2, 3] });
```
The request made to the server will look something like this:
```
GET "/api/v1/person?ids%5B%5D=1&ids%5B%5D=2&ids%5B%5D=3"
decoded: "/api/v1/person?ids[]=1&ids[]=2&ids[]=3"
```
This method returns a promise, which is resolved with an
[`AdapterPopulatedRecordArray`](https://emberjs.com/api/data/classes/DS.AdapterPopulatedRecordArray.html)
once the server returns.
@since 1.13.0
@method query
@param {String} modelName
@param {any} query an opaque query to be used by the adapter
@param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter.query
@return {Promise} promise
*/
query(modelName, query, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'query');
}
(true && Ember.assert("You need to pass a model name to the store's query method", Ember.isPresent(modelName)));
(true && Ember.assert("You need to pass a query hash to the store's query method", query));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let adapterOptionsWrapper = {};
if (options && options.adapterOptions) {
adapterOptionsWrapper.adapterOptions = options.adapterOptions;
}
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
return this._query(normalizedModelName, query, null, adapterOptionsWrapper);
}
_query(modelName, query, array, options) {
(true && Ember.assert("You need to pass a model name to the store's query method", Ember.isPresent(modelName)));
(true && Ember.assert("You need to pass a query hash to the store's query method", query));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let adapter = this.adapterFor(modelName);
(true && Ember.assert("You tried to load a query but you have no adapter (for ".concat(modelName, ")"), adapter));
(true && Ember.assert("You tried to load a query but your adapter does not implement 'query'", typeof adapter.query === 'function'));
return (0, _promiseProxies.promiseArray)((0, _finders._query)(adapter, this, modelName, query, array, options));
}
/**
This method makes a request for one record, where the `id` is not known
beforehand (if the `id` is known, use [`findRecord`](#method_findRecord)
instead).
This method can be used when it is certain that the server will return a
single object for the primary data.
Each time this method is called a new request is made through the adapter.
Let's assume our API provides an endpoint for the currently logged in user
via:
```
// GET /api/current_user
{
user: {
id: 1234,
username: 'admin'
}
}
```
Since the specific `id` of the `user` is not known beforehand, we can use
`queryRecord` to get the user:
```javascript
store.queryRecord('user', {}).then(function(user) {
let username = user.get('username');
console.log(`Currently logged in as ${username}`);
});
```
The request is made through the adapters' `queryRecord`:
```app/adapters/user.js
import $ from 'jquery';
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
queryRecord(modelName, query) {
return $.getJSON('/api/current_user');
}
});
```
Note: the primary use case for `store.queryRecord` is when a single record
is queried and the `id` is not known beforehand. In all other cases
`store.query` and using the first item of the array is likely the preferred
way:
```
// GET /users?username=unique
{
data: [{
id: 1234,
type: 'user',
attributes: {
username: "unique"
}
}]
}
```
```javascript
store.query('user', { username: 'unique' }).then(function(users) {
return users.get('firstObject');
}).then(function(user) {
let id = user.get('id');
});
```
This method returns a promise, which resolves with the found record.
If the adapter returns no data for the primary data of the payload, then
`queryRecord` resolves with `null`:
```
// GET /users?username=unique
{
data: null
}
```
```javascript
store.queryRecord('user', { username: 'unique' }).then(function(user) {
console.log(user); // null
});
```
@since 1.13.0
@method queryRecord
@param {String} modelName
@param {any} query an opaque query to be used by the adapter
@param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter.queryRecord
@return {Promise} promise which resolves with the found record or `null`
*/
queryRecord(modelName, query, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'queryRecord');
}
(true && Ember.assert("You need to pass a model name to the store's queryRecord method", Ember.isPresent(modelName)));
(true && Ember.assert("You need to pass a query hash to the store's queryRecord method", query));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let adapter = this.adapterFor(normalizedModelName);
let adapterOptionsWrapper = {};
if (options && options.adapterOptions) {
adapterOptionsWrapper.adapterOptions = options.adapterOptions;
}
(true && Ember.assert("You tried to make a query but you have no adapter (for ".concat(normalizedModelName, ")"), adapter));
(true && Ember.assert("You tried to make a query but your adapter does not implement 'queryRecord'", typeof adapter.queryRecord === 'function'));
return (0, _promiseProxies.promiseObject)((0, _finders._queryRecord)(adapter, this, normalizedModelName, query, adapterOptionsWrapper).then(internalModel => {
// the promise returned by store.queryRecord is expected to resolve with
// an instance of DS.Model
if (internalModel) {
return internalModel.getRecord();
}
return null;
}));
}
/**
`findAll` asks the adapter's `findAll` method to find the records for the
given type, and returns a promise which will resolve with all records of
this type present in the store, even if the adapter only returns a subset
of them.
```app/routes/authors.js
import Route from '@ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findAll('author');
}
});
```
_When_ the returned promise resolves depends on the reload behavior,
configured via the passed `options` hash and the result of the adapter's
`shouldReloadAll` method.
### Reloading
If `{ reload: true }` is passed or `adapter.shouldReloadAll` evaluates to
`true`, then the returned promise resolves once the adapter returns data,
regardless if there are already records in the store:
```js
store.push({
data: {
id: 'first',
type: 'author'
}
});
// adapter#findAll resolves with
// [
// {
// id: 'second',
// type: 'author'
// }
// ]
store.findAll('author', { reload: true }).then(function(authors) {
authors.getEach('id'); // ['first', 'second']
});
```
If no reload is indicated via the above mentioned ways, then the promise
immediately resolves with all the records currently loaded in the store.
### Background Reloading
Optionally, if `adapter.shouldBackgroundReloadAll` evaluates to `true`,
then a background reload is started. Once this resolves, the array with
which the promise resolves, is updated automatically so it contains all the
records in the store:
```app/adapters/application.js
import Adapter from '@ember-data/adapter';
export default Adapter.extend({
shouldReloadAll(store, snapshotsArray) {
return false;
},
shouldBackgroundReloadAll(store, snapshotsArray) {
return true;
}
});
// ...
store.push({
data: {
id: 'first',
type: 'author'
}
});
let allAuthors;
store.findAll('author').then(function(authors) {
authors.getEach('id'); // ['first']
allAuthors = authors;
});
// later, once adapter#findAll resolved with
// [
// {
// id: 'second',
// type: 'author'
// }
// ]
allAuthors.getEach('id'); // ['first', 'second']
```
If you would like to force or prevent background reloading, you can set a
boolean value for `backgroundReload` in the options object for
`findAll`.
```app/routes/post/edit.js
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return this.store.findAll('post', { backgroundReload: false });
}
});
```
If you pass an object on the `adapterOptions` property of the options
argument it will be passed to you adapter via the `snapshotRecordArray`
```app/routes/posts.js
import Route from '@ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findAll('post', {
adapterOptions: { subscribe: false }
});
}
});
```
```app/adapters/post.js
import MyCustomAdapter from './custom-adapter';
export default MyCustomAdapter.extend({
findAll(store, type, sinceToken, snapshotRecordArray) {
if (snapshotRecordArray.adapterOptions.subscribe) {
// ...
}
// ...
}
});
```
See [peekAll](#method_peekAll) to get an array of current records in the
store, without waiting until a reload is finished.
### Retrieving Related Model Records
If you use an adapter such as Ember's default
[`JSONAPIAdapter`](https://emberjs.com/api/data/classes/DS.JSONAPIAdapter.html)
that supports the [JSON API specification](http://jsonapi.org/) and if your server
endpoint supports the use of an
['include' query parameter](http://jsonapi.org/format/#fetching-includes),
you can use `findAll()` to automatically retrieve additional records related to
those requested by supplying an `include` parameter in the `options` object.
For example, given a `post` model that has a `hasMany` relationship with a `comment`
model, when we retrieve all of the post records we can have the server also return
all of the posts' comments in the same request:
```app/routes/posts.js
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return this.store.findAll('post', { include: 'comments' });
}
});
```
Multiple relationships can be requested using an `include` parameter consisting of a
comma-separated list (without white-space) while nested relationships can be specified
using a dot-separated sequence of relationship names. So to request both the posts'
comments and the authors of those comments the request would look like this:
```app/routes/posts.js
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return this.store.findAll('post', { include: 'comments,comments.author' });
}
});
```
See [query](#method_query) to only get a subset of records from the server.
@since 1.13.0
@method findAll
@param {String} modelName
@param {Object} options
@return {Promise} promise
*/
findAll(modelName, options) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'findAll');
}
(true && Ember.assert("You need to pass a model name to the store's findAll method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let fetch = this._fetchAll(normalizedModelName, this.peekAll(normalizedModelName), options);
return fetch;
}
/**
@method _fetchAll
@private
@param {Model} modelName
@param {RecordArray} array
@return {Promise} promise
*/
_fetchAll(modelName, array, options = {}) {
let adapter = this.adapterFor(modelName);
(true && Ember.assert("You tried to load all records but you have no adapter (for ".concat(modelName, ")"), adapter));
(true && Ember.assert("You tried to load all records but your adapter does not implement 'findAll'", typeof adapter.findAll === 'function'));
if (options.reload) {
Ember.set(array, 'isUpdating', true);
return (0, _promiseProxies.promiseArray)((0, _finders._findAll)(adapter, this, modelName, options));
}
let snapshotArray = array._createSnapshot(options);
if (adapter.shouldReloadAll(this, snapshotArray)) {
Ember.set(array, 'isUpdating', true);
return (0, _promiseProxies.promiseArray)((0, _finders._findAll)(adapter, this, modelName, options));
}
if (options.backgroundReload === false) {
return (0, _promiseProxies.promiseArray)(Ember.RSVP.Promise.resolve(array));
}
if (options.backgroundReload || adapter.shouldBackgroundReloadAll(this, snapshotArray)) {
Ember.set(array, 'isUpdating', true);
(0, _finders._findAll)(adapter, this, modelName, options);
}
return (0, _promiseProxies.promiseArray)(Ember.RSVP.Promise.resolve(array));
}
/**
@method _didUpdateAll
@param {String} modelName
@private
*/
_didUpdateAll(modelName) {
this.recordArrayManager._didUpdateAll(modelName);
}
/**
This method returns a filtered array that contains all of the
known records for a given type in the store.
Note that because it's just a filter, the result will contain any
locally created records of the type, however, it will not make a
request to the backend to retrieve additional records. If you
would like to request all the records from the backend please use
[store.findAll](#method_findAll).
Also note that multiple calls to `peekAll` for a given type will always
return the same `RecordArray`.
Example
```javascript
let localPosts = store.peekAll('post');
```
@since 1.13.0
@method peekAll
@param {String} modelName
@return {RecordArray}
*/
peekAll(modelName) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'peekAll');
}
(true && Ember.assert("You need to pass a model name to the store's peekAll method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
return this.recordArrayManager.liveRecordArrayFor(normalizedModelName);
}
/**
This method unloads all records in the store.
It schedules unloading to happen during the next run loop.
Optionally you can pass a type which unload all records for a given type.
```javascript
store.unloadAll();
store.unloadAll('post');
```
@method unloadAll
@param {String} modelName
*/
unloadAll(modelName) {
if (true
/* DEBUG */
) {
assertDestroyedStoreOnly(this, 'unloadAll');
}
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), !modelName || typeof modelName === 'string'));
const factory = (0, _internalModelFactory.internalModelFactoryFor)(this);
if (modelName === undefined) {
factory.clear();
} else {
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
factory.clear(normalizedModelName);
}
}
filter() {
(true && Ember.assert('The filter API has been moved to a plugin. To enable store.filter using an environment flag, or to use an alternative, you can visit the ember-data-filter addon page. https://github.com/ember-data/ember-data-filter', false));
} // ..............
// . PERSISTING .
// ..............
/**
This method is called by `record.save`, and gets passed a
resolver for the promise that `record.save` returns.
It schedules saving to happen at the end of the run loop.
@method scheduleSave
@private
@param {InternalModel} internalModel
@param {Resolver} resolver
@param {Object} options
*/
scheduleSave(internalModel, resolver, options) {
let snapshot = internalModel.createSnapshot(options);
if (internalModel._isRecordFullyDeleted()) {
resolver.resolve();
return resolver.promise;
}
internalModel.adapterWillCommit();
if (_canaryFeatures.REQUEST_SERVICE) {
if (!options) {
options = {};
}
let recordData = internalModel._recordData;
let operation = 'updateRecord'; // TODO handle missing isNew
if (recordData.isNew && recordData.isNew()) {
operation = 'createRecord';
} else if (recordData.isDeleted && recordData.isDeleted()) {
operation = 'deleteRecord';
}
options[_fetchManager.SaveOp] = operation;
let fetchManagerPromise = this._fetchManager.scheduleSave(internalModel.identifier, options);
let promise = fetchManagerPromise.then(payload => {
/*
Note to future spelunkers hoping to optimize.
We rely on this `run` to create a run loop if needed
that `store._push` and `store.didSaveRecord` will both share.
We use `join` because it is often the case that we
have an outer run loop available still from the first
call to `store._push`;
*/
this._backburner.join(() => {
let data = payload && payload.data;
this.didSaveRecord(internalModel, {
data
}, operation);
if (payload && payload.included) {
this._push({
data: null,
included: payload.included
});
}
});
}, ({
error,
parsedErrors
}) => {
this.recordWasInvalid(internalModel, parsedErrors, error);
throw error;
});
return promise;
}
this._pendingSave.push({
snapshot: snapshot,
resolver: resolver
});
emberRun.scheduleOnce('actions', this, this.flushPendingSave);
}
/**
This method is called at the end of the run loop, and
flushes any records passed into `scheduleSave`
@method flushPendingSave
@private
*/
flushPendingSave() {
if (_canaryFeatures.REQUEST_SERVICE) {
// assert here
return;
}
let pending = this._pendingSave.slice();
this._pendingSave = [];
for (let i = 0, j = pending.length; i < j; i++) {
let pendingItem = pending[i];
let snapshot = pendingItem.snapshot;
let resolver = pendingItem.resolver;
let internalModel = snapshot._internalModel;
let adapter = this.adapterFor(internalModel.modelName);
let operation;
if (_canaryFeatures.RECORD_DATA_STATE) {
// TODO move this out of internalModel
if (internalModel.isNew()) {
operation = 'createRecord';
} else if (internalModel.isDeleted()) {
operation = 'deleteRecord';
} else {
operation = 'updateRecord';
}
} else {
if (internalModel.currentState.stateName === 'root.deleted.saved') {
resolver.resolve();
continue;
} else if (internalModel.isNew()) {
operation = 'createRecord';
} else if (internalModel.isDeleted()) {
operation = 'deleteRecord';
} else {
operation = 'updateRecord';
}
}
resolver.resolve(_commit(adapter, this, operation, snapshot));
}
}
/**
This method is called once the promise returned by an
adapter's `createRecord`, `updateRecord` or `deleteRecord`
is resolved.
If the data provides a server-generated ID, it will
update the record and the store's indexes.
@method didSaveRecord
@private
@param {InternalModel} internalModel the in-flight internal model
@param {Object} data optional data (see above)
@param {string} op the adapter operation that was committed
*/
didSaveRecord(internalModel, dataArg, op) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'didSaveRecord');
}
let data;
if (dataArg) {
data = dataArg.data;
}
if (!data) {
(true && Ember.assert("Your ".concat(internalModel.modelName, " record was saved to the server, but the response does not have an id and no id has been set client side. Records must have ids. Please update the server response to provide an id in the response or generate the id on the client side either before saving the record or while normalizing the response."), internalModel.id));
}
if (_canaryFeatures.IDENTIFIERS) {
const cache = (0, _cache.identifierCacheFor)(this);
const identifier = internalModel.identifier;
if (op !== 'deleteRecord' && data) {
cache.updateRecordIdentifier(identifier, data);
}
} //We first make sure the primary data has been updated
//TODO try to move notification to the user to the end of the runloop
internalModel.adapterDidCommit(data);
}
/**
This method is called once the promise returned by an
adapter's `createRecord`, `updateRecord` or `deleteRecord`
is rejected with a `InvalidError`.
@method recordWasInvalid
@private
@param {InternalModel} internalModel
@param {Object} errors
*/
recordWasInvalid(internalModel, parsedErrors, error) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'recordWasInvalid');
}
if (_canaryFeatures.RECORD_DATA_ERRORS) {
internalModel.adapterDidInvalidate(parsedErrors, error);
} else {
internalModel.adapterDidInvalidate(parsedErrors);
}
}
/**
This method is called once the promise returned by an
adapter's `createRecord`, `updateRecord` or `deleteRecord`
is rejected (with anything other than a `InvalidError`).
@method recordWasError
@private
@param {InternalModel} internalModel
@param {Error} error
*/
recordWasError(internalModel, error) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'recordWasError');
}
internalModel.adapterDidError(error);
}
/**
Sets newly received ID from the adapter's `createRecord`, `updateRecord`
or `deleteRecord`.
@method setRecordId
@private
@param {String} modelName
@param {string} newId
@param {string} clientId
*/
setRecordId(modelName, newId, clientId) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'setRecordId');
}
(0, _internalModelFactory.internalModelFactoryFor)(this).setRecordId(modelName, newId, clientId);
} // ................
// . LOADING DATA .
// ................
/**
This internal method is used by `push`.
@method _load
@private
@param {Object} data
*/
_load(data) {
const modelName = (0, _normalizeModelName.default)(data.type);
const id = (0, _coerceId.ensureStringId)(data.id);
const lid = (0, _coerceId.default)(data.lid);
let internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(modelName, id, lid, data); // store.push will be from empty
// findRecord will be from root.loading
// all else will be updates
const isLoading = internalModel.currentState.stateName === 'root.loading';
const isUpdate = internalModel.currentState.isEmpty === false && !isLoading;
if (_canaryFeatures.IDENTIFIERS) {
// exclude store.push (root.empty) case
if (isUpdate || isLoading) {
let identifier = internalModel.identifier;
let updatedIdentifier = (0, _cache.identifierCacheFor)(this).updateRecordIdentifier(identifier, data);
if (updatedIdentifier !== identifier) {
// we encountered a merge of identifiers in which
// two identifiers (and likely two internalModels)
// existed for the same resource. Now that we have
// determined the correct identifier to use, make sure
// that we also use the correct internalModel.
identifier = updatedIdentifier;
internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(identifier.type, identifier.id, identifier.lid);
}
}
}
internalModel.setupData(data);
if (isUpdate) {
this.recordArrayManager.recordDidChange(internalModel);
} else {
this.recordArrayManager.recordWasLoaded(internalModel);
}
return internalModel;
}
/**
Returns the model class for the particular `modelName`.
The class of a model might be useful if you want to get a list of all the
relationship names of the model, see
[`relationshipNames`](https://emberjs.com/api/data/classes/DS.Model.html#property_relationshipNames)
for example.
@method modelFor
@param {String} modelName
@return {Model}
*/
modelFor(modelName) {
if (true
/* DEBUG */
) {
assertDestroyedStoreOnly(this, 'modelFor');
}
(true && Ember.assert("You need to pass a model name to the store's modelFor method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let maybeFactory = this._modelFactoryFor(modelName); // for factorFor factory/class split
return maybeFactory.class ? maybeFactory.class : maybeFactory;
}
_modelFactoryFor(modelName) {
if (true
/* DEBUG */
) {
assertDestroyedStoreOnly(this, '_modelFactoryFor');
}
(true && Ember.assert("You need to pass a model name to the store's _modelFactoryFor method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let factory = getModelFactory(this, this._modelFactoryCache, normalizedModelName);
if (factory === null) {
throw new Ember.Error("No model was found for '".concat(normalizedModelName, "'"));
}
return factory;
}
/*
Returns whether a ModelClass exists for a given modelName
This exists for legacy support for the RESTSerializer,
which due to how it must guess whether a key is a model
must query for whether a match exists.
We should investigate an RFC to make this public or removing
this requirement.
@private
*/
_hasModelFor(modelName) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, '_hasModelFor');
}
(true && Ember.assert("You need to pass a model name to the store's hasModelFor method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let factory = getModelFactory(this, this._modelFactoryCache, normalizedModelName);
return factory !== null;
}
/**
Push some data for a given type into the store.
This method expects normalized [JSON API](http://jsonapi.org/) document. This means you have to follow [JSON API specification](http://jsonapi.org/format/) with few minor adjustments:
- record's `type` should always be in singular, dasherized form
- members (properties) should be camelCased
[Your primary data should be wrapped inside `data` property](http://jsonapi.org/format/#document-top-level):
```js
store.push({
data: {
// primary data for single record of type `Person`
id: '1',
type: 'person',
attributes: {
firstName: 'Daniel',
lastName: 'Kmak'
}
}
});
```
[Demo.](http://ember-twiddle.com/fb99f18cd3b4d3e2a4c7)
`data` property can also hold an array (of records):
```js
store.push({
data: [
// an array of records
{
id: '1',
type: 'person',
attributes: {
firstName: 'Daniel',
lastName: 'Kmak'
}
},
{
id: '2',
type: 'person',
attributes: {
firstName: 'Tom',
lastName: 'Dale'
}
}
]
});
```
[Demo.](http://ember-twiddle.com/69cdbeaa3702159dc355)
There are some typical properties for `JSONAPI` payload:
* `id` - mandatory, unique record's key
* `type` - mandatory string which matches `model`'s dasherized name in singular form
* `attributes` - object which holds data for record attributes - `DS.attr`'s declared in model
* `relationships` - object which must contain any of the following properties under each relationships' respective key (example path is `relationships.achievements.data`):
- [`links`](http://jsonapi.org/format/#document-links)
- [`data`](http://jsonapi.org/format/#document-resource-object-linkage) - place for primary data
- [`meta`](http://jsonapi.org/format/#document-meta) - object which contains meta-information about relationship
For this model:
```app/models/person.js
import Model, { attr, hasMany } from '@ember-data/model';
export default Model.extend({
firstName: attr('string'),
lastName: attr('string'),
children: hasMany('person')
});
```
To represent the children as IDs:
```js
{
data: {
id: '1',
type: 'person',
attributes: {
firstName: 'Tom',
lastName: 'Dale'
},
relationships: {
children: {
data: [
{
id: '2',
type: 'person'
},
{
id: '3',
type: 'person'
},
{
id: '4',
type: 'person'
}
]
}
}
}
}
```
[Demo.](http://ember-twiddle.com/343e1735e034091f5bde)
To represent the children relationship as a URL:
```js
{
data: {
id: '1',
type: 'person',
attributes: {
firstName: 'Tom',
lastName: 'Dale'
},
relationships: {
children: {
links: {
related: '/people/1/children'
}
}
}
}
}
```
If you're streaming data or implementing an adapter, make sure
that you have converted the incoming data into this form. The
store's [normalize](#method_normalize) method is a convenience
helper for converting a json payload into the form Ember Data
expects.
```js
store.push(store.normalize('person', data));
```
This method can be used both to push in brand new
records, as well as to update existing records.
@method push
@param {Object} data
@return the record(s) that was created or
updated.
*/
push(data) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'push');
}
let pushed = this._push(data);
if (Array.isArray(pushed)) {
let records = pushed.map(internalModel => internalModel.getRecord());
return records;
}
if (pushed === null) {
return null;
}
let record = pushed.getRecord();
return record;
}
/*
Push some data in the form of a json-api document into the store,
without creating materialized records.
@method _push
@private
@param {Object} jsonApiDoc
@return {InternalModel|Array} pushed InternalModel(s)
*/
_push(jsonApiDoc) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, '_push');
}
let internalModelOrModels = this._backburner.join(() => {
let included = jsonApiDoc.included;
let i, length;
if (included) {
for (i = 0, length = included.length; i < length; i++) {
this._pushInternalModel(included[i]);
}
}
if (Array.isArray(jsonApiDoc.data)) {
length = jsonApiDoc.data.length;
let internalModels = new Array(length);
for (i = 0; i < length; i++) {
internalModels[i] = this._pushInternalModel(jsonApiDoc.data[i]);
}
return internalModels;
}
if (jsonApiDoc.data === null) {
return null;
}
(true && Ember.assert("Expected an object in the 'data' property in a call to 'push' for ".concat(jsonApiDoc.type, ", but was ").concat(Ember.typeOf(jsonApiDoc.data)), Ember.typeOf(jsonApiDoc.data) === 'object'));
return this._pushInternalModel(jsonApiDoc.data);
}); // this typecast is necessary because `backburner.join` is mistyped to return void
return internalModelOrModels;
}
_pushInternalModel(data) {
let modelName = data.type;
(true && Ember.assert("You must include an 'id' for ".concat(modelName, " in an object passed to 'push'"), data.id !== null && data.id !== undefined && data.id !== ''));
(true && Ember.assert("You tried to push data with a type '".concat(modelName, "' but no model could be found with that name."), this._hasModelFor(modelName)));
if (true
/* DEBUG */
) {
// If ENV.DS_WARN_ON_UNKNOWN_KEYS is set to true and the payload
// contains unknown attributes or relationships, log a warning.
if (ENV.DS_WARN_ON_UNKNOWN_KEYS) {
let modelClass = this.modelFor(modelName); // Check unknown attributes
let unknownAttributes = Object.keys(data.attributes || {}).filter(key => {
return !Ember.get(modelClass, 'fields').has(key);
});
let unknownAttributesMessage = "The payload for '".concat(modelName, "' contains these unknown attributes: ").concat(unknownAttributes, ". Make sure they've been defined in your model.");
(true && Ember.warn(unknownAttributesMessage, unknownAttributes.length === 0, {
id: 'ds.store.unknown-keys-in-payload'
})); // Check unknown relationships
let unknownRelationships = Object.keys(data.relationships || {}).filter(key => {
return !Ember.get(modelClass, 'fields').has(key);
});
let unknownRelationshipsMessage = "The payload for '".concat(modelName, "' contains these unknown relationships: ").concat(unknownRelationships, ". Make sure they've been defined in your model.");
(true && Ember.warn(unknownRelationshipsMessage, unknownRelationships.length === 0, {
id: 'ds.store.unknown-keys-in-payload'
}));
}
} // Actually load the record into the store.
let internalModel = this._load(data); // this._setupRelationshipsForModel(internalModel, data);
return internalModel;
}
/**
Push some raw data into the store.
This method can be used both to push in brand new
records, as well as to update existing records. You
can push in more than one type of object at once.
All objects should be in the format expected by the
serializer.
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
export default RESTSerializer;
```
```js
let pushData = {
posts: [
{ id: 1, postTitle: "Great post", commentIds: [2] }
],
comments: [
{ id: 2, commentBody: "Insightful comment" }
]
}
store.pushPayload(pushData);
```
By default, the data will be deserialized using a default
serializer (the application serializer if it exists).
Alternatively, `pushPayload` will accept a model type which
will determine which serializer will process the payload.
```app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
export default RESTSerializer;
```
```app/serializers/post.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer;
```
```js
store.pushPayload(pushData); // Will use the application serializer
store.pushPayload('post', pushData); // Will use the post serializer
```
@method pushPayload
@param {String} modelName Optionally, a model type used to determine which serializer will be used
@param {Object} inputPayload
*/
pushPayload(modelName, inputPayload) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'pushPayload');
}
let serializer;
let payload;
if (!inputPayload) {
payload = modelName;
serializer = this.serializerFor('application');
(true && Ember.assert("You cannot use 'store#pushPayload' without a modelName unless your default serializer defines 'pushPayload'", typeof serializer.pushPayload === 'function'));
} else {
payload = inputPayload;
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
serializer = this.serializerFor(normalizedModelName);
}
serializer.pushPayload(this, payload);
}
reloadManyArray(manyArray, internalModel, key, options) {
return internalModel.reloadHasMany(key, options);
}
reloadBelongsTo(belongsToProxy, internalModel, key, options) {
return internalModel.reloadBelongsTo(key, options);
}
_relationshipMetaFor(modelName, id, key) {
let modelClass = this.modelFor(modelName);
let relationshipsByName = Ember.get(modelClass, 'relationshipsByName');
return relationshipsByName.get(key);
}
_attributesDefinitionFor(modelName) {
let attributes = this._attributesDefCache[modelName];
if (attributes === undefined) {
let modelClass = this.modelFor(modelName);
let attributeMap = Ember.get(modelClass, 'attributes');
attributes = Object.create(null);
attributeMap.forEach((meta, name) => attributes[name] = meta);
this._attributesDefCache[modelName] = attributes;
}
return attributes;
}
_relationshipsDefinitionFor(modelName) {
let relationships = this._relationshipsDefCache[modelName];
if (relationships === undefined) {
let modelClass = this.modelFor(modelName);
relationships = Ember.get(modelClass, 'relationshipsObject') || null;
this._relationshipsDefCache[modelName] = relationships;
}
return relationships;
}
_internalModelForResource(resource) {
return (0, _internalModelFactory.internalModelFactoryFor)(this).getByResource(resource);
}
/**
* TODO Only needed temporarily for test support
*
* @internal
*/
_internalModelForId(modelName, id, lid) {
if (!(0, _hasValidId.default)(id, lid)) {
throw new Error("Expected id or lid to be a string with some length");
}
return (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(modelName, id, lid);
}
_createRecordData(modelName, id, clientId) {
return this.createRecordDataFor(modelName, id, clientId, this._storeWrapper);
}
createRecordDataFor(modelName, id, clientId, storeWrapper) {
if (_canaryFeatures.IDENTIFIERS) {
let identifier = (0, _cache.identifierCacheFor)(this).getOrCreateRecordIdentifier({
type: modelName,
id,
lid: clientId
});
return new _recordData.default(identifier, storeWrapper);
} else {
return new _recordData.default(modelName, id, clientId, storeWrapper);
}
}
recordDataFor(modelName, id, clientId) {
let internalModel;
if (!(0, _hasValidId.default)(id, clientId)) {
internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this).build(modelName, null);
} else {
internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this).lookup(modelName, id, clientId);
}
return (0, _recordDataFor.default)(internalModel);
}
/**
`normalize` converts a json payload into the normalized form that
[push](#method_push) expects.
Example
```js
socket.on('message', function(message) {
let modelName = message.model;
let data = message.data;
store.push(store.normalize(modelName, data));
});
```
@method normalize
@param {String} modelName The name of the model type for this payload
@param {Object} payload
@return {Object} The normalized payload
*/
normalize(modelName, payload) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'normalize');
}
(true && Ember.assert("You need to pass a model name to the store's normalize method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store methods has been removed. Please pass a dasherized string instead of ".concat(Ember.inspect(modelName)), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let serializer = this.serializerFor(normalizedModelName);
let model = this.modelFor(normalizedModelName);
return serializer.normalize(model, payload);
}
newClientId() {
return globalClientIdCounter++;
} //Called by the state machine to notify the store that the record is ready to be interacted with
recordWasLoaded(record) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'recordWasLoaded');
}
this.recordArrayManager.recordWasLoaded(record);
} // ...............
// . DESTRUCTION .
// ...............
/**
* TODO remove test usage
*
* @internal
*/
_internalModelsFor(modelName) {
return (0, _internalModelFactory.internalModelFactoryFor)(this).modelMapFor(modelName);
} // ......................
// . PER-TYPE ADAPTERS
// ......................
/**
Returns an instance of the adapter for a given type. For
example, `adapterFor('person')` will return an instance of
`App.PersonAdapter`.
If no `App.PersonAdapter` is found, this method will look
for an `App.ApplicationAdapter` (the default adapter for
your entire application).
If no `App.ApplicationAdapter` is found, it will return
the value of the `defaultAdapter`.
@method adapterFor
@public
@param {String} modelName
@return Adapter
*/
adapterFor(modelName) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'adapterFor');
}
(true && Ember.assert("You need to pass a model name to the store's adapterFor method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store.adapterFor has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let {
_adapterCache
} = this;
let adapter = _adapterCache[normalizedModelName];
if (adapter) {
return adapter;
}
let owner = Ember.getOwner(this);
adapter = owner.lookup("adapter:".concat(normalizedModelName));
if (adapter !== undefined) {
Ember.set(adapter, 'store', this);
_adapterCache[normalizedModelName] = adapter;
return adapter;
} // no adapter found for the specific model, fallback and check for application adapter
adapter = _adapterCache.application || owner.lookup('adapter:application');
if (adapter !== undefined) {
Ember.set(adapter, 'store', this);
_adapterCache[normalizedModelName] = adapter;
_adapterCache.application = adapter;
return adapter;
} // no model specific adapter or application adapter, check for an `adapter`
// property defined on the store
let adapterName = this.adapter || '-json-api';
adapter = adapterName ? _adapterCache[adapterName] || owner.lookup("adapter:".concat(adapterName)) : undefined;
if (adapter !== undefined) {
Ember.set(adapter, 'store', this);
_adapterCache[normalizedModelName] = adapter;
_adapterCache[adapterName] = adapter;
return adapter;
} // final fallback, no model specific adapter, no application adapter, no
// `adapter` property on store: use json-api adapter
adapter = _adapterCache['-json-api'] || owner.lookup('adapter:-json-api');
(true && Ember.assert("No adapter was found for '".concat(modelName, "' and no 'application', store.adapter = 'adapter-fallback-name', or '-json-api' adapter were found as fallbacks."), adapter !== undefined));
Ember.set(adapter, 'store', this);
_adapterCache[normalizedModelName] = adapter;
_adapterCache['-json-api'] = adapter;
return adapter;
} // ..............................
// . RECORD CHANGE NOTIFICATION .
// ..............................
/**
Returns an instance of the serializer for a given type. For
example, `serializerFor('person')` will return an instance of
`App.PersonSerializer`.
If no `App.PersonSerializer` is found, this method will look
for an `App.ApplicationSerializer` (the default serializer for
your entire application).
if no `App.ApplicationSerializer` is found, it will attempt
to get the `defaultSerializer` from the `PersonAdapter`
(`adapterFor('person')`).
If a serializer cannot be found on the adapter, it will fall back
to an instance of `JSONSerializer`.
@method serializerFor
@public
@param {String} modelName the record to serialize
@return {Serializer}
*/
serializerFor(modelName) {
if (true
/* DEBUG */
) {
assertDestroyingStore(this, 'serializerFor');
}
(true && Ember.assert("You need to pass a model name to the store's serializerFor method", Ember.isPresent(modelName)));
(true && Ember.assert("Passing classes to store.serializerFor has been removed. Please pass a dasherized string instead of ".concat(modelName), typeof modelName === 'string'));
let normalizedModelName = (0, _normalizeModelName.default)(modelName);
let {
_serializerCache
} = this;
let serializer = _serializerCache[normalizedModelName];
if (serializer) {
return serializer;
}
let owner = Ember.getOwner(this);
serializer = owner.lookup("serializer:".concat(normalizedModelName));
if (serializer !== undefined) {
Ember.set(serializer, 'store', this);
_serializerCache[normalizedModelName] = serializer;
return serializer;
} // no serializer found for the specific model, fallback and check for application serializer
serializer = _serializerCache.application || owner.lookup('serializer:application');
if (serializer !== undefined) {
Ember.set(serializer, 'store', this);
_serializerCache[normalizedModelName] = serializer;
_serializerCache.application = serializer;
return serializer;
} // no model specific serializer or application serializer, check for the `defaultSerializer`
// property defined on the adapter
let adapter = this.adapterFor(modelName);
let serializerName = Ember.get(adapter, 'defaultSerializer');
serializer = serializerName ? _serializerCache[serializerName] || owner.lookup("serializer:".concat(serializerName)) : undefined;
if (serializer !== undefined) {
Ember.set(serializer, 'store', this);
_serializerCache[normalizedModelName] = serializer;
_serializerCache[serializerName] = serializer;
return serializer;
} // final fallback, no model specific serializer, no application serializer, no
// `serializer` property on store: use json-api serializer
serializer = _serializerCache['-default'] || owner.lookup('serializer:-default');
(true && Ember.assert("No serializer was found for '".concat(modelName, "' and no 'application', Adapter.defaultSerializer, or '-default' serializer were found as fallbacks."), serializer !== undefined));
Ember.set(serializer, 'store', this);
_serializerCache[normalizedModelName] = serializer;
_serializerCache['-default'] = serializer;
return serializer;
}
willDestroy() {
super.willDestroy();
this.recordArrayManager.destroy(); // Check if we need to null this out
// this._adapterCache = null;
// this._serializerCache = null;
(0, _cache.identifierCacheFor)(this).destroy();
this.unloadAll();
if (true
/* DEBUG */
) {
Ember.Test.unregisterWaiter(this.__asyncWaiter);
let shouldTrack = this.shouldTrackAsyncRequests;
let tracked = this._trackedAsyncRequests;
let isSettled = tracked.length === 0;
if (!isSettled) {
if (shouldTrack) {
throw new Error('Async Request leaks detected. Add a breakpoint here and set `store.generateStackTracesForTrackedRequests = true;`to inspect traces for leak origins:\n\t - ' + tracked.map(o => o.label).join('\n\t - '));
} else {
(true && Ember.warn('Async Request leaks detected. Add a breakpoint here and set `store.generateStackTracesForTrackedRequests = true;`to inspect traces for leak origins:\n\t - ' + tracked.map(o => o.label).join('\n\t - '), false, {
id: 'ds.async.leak.detected'
}));
}
}
}
}
_updateRelationshipState(relationship) {
if (this._updatedRelationships.push(relationship) !== 1) {
return;
}
this._backburner.join(() => {
this._backburner.schedule('syncRelationships', this, this._flushUpdatedRelationships);
});
}
_flushUpdatedRelationships() {
let updated = this._updatedRelationships;
for (let i = 0, l = updated.length; i < l; i++) {
updated[i].flushCanonical();
}
updated.length = 0;
}
_updateInternalModel(internalModel) {
if (this._updatedInternalModels.push(internalModel) !== 1) {
return;
}
emberRun.schedule('actions', this, this._flushUpdatedInternalModels);
}
_flushUpdatedInternalModels() {
let updated = this._updatedInternalModels;
for (let i = 0, l = updated.length; i < l; i++) {
updated[i]._triggerDeferredTriggers();
}
updated.length = 0;
}
}
Ember.defineProperty(Store.prototype, 'defaultAdapter', Ember.computed('adapter', function () {
let adapter = this.adapter || '-json-api';
(true && Ember.assert('You tried to set `adapter` property to an instance of `Adapter`, where it should be a name', typeof adapter === 'string'));
return this.adapterFor(adapter);
}));
var _default = Store;
_exports.default = _default;
function _commit(adapter, store, operation, snapshot) {
let internalModel = snapshot._internalModel;
let modelName = snapshot.modelName;
let modelClass = store.modelFor(modelName);
(true && Ember.assert("You tried to update a record but you have no adapter (for ".concat(modelName, ")"), adapter));
(true && Ember.assert("You tried to update a record but your adapter (for ".concat(modelName, ") does not implement '").concat(operation, "'"), typeof adapter[operation] === 'function'));
let promise = Ember.RSVP.Promise.resolve().then(() => adapter[operation](store, modelClass, snapshot));
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let label = "DS: Extract and notify about ".concat(operation, " completion of ").concat(internalModel);
(true && Ember.assert("Your adapter's '".concat(operation, "' method must return a value, but it returned 'undefined'"), promise !== undefined));
promise = (0, _common.guardDestroyedStore)(promise, store, label);
promise = (0, _common._guard)(promise, (0, _common._bind)(_common._objectIsAlive, internalModel));
return promise.then(adapterPayload => {
/*
Note to future spelunkers hoping to optimize.
We rely on this `run` to create a run loop if needed
that `store._push` and `store.didSaveRecord` will both share.
We use `join` because it is often the case that we
have an outer run loop available still from the first
call to `store._push`;
*/
store._backburner.join(() => {
let payload, data, sideloaded;
if (adapterPayload) {
payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, snapshot.id, operation);
if (payload.included) {
sideloaded = payload.included;
}
data = payload.data;
}
store.didSaveRecord(internalModel, {
data
}, operation); // seems risky, but if the tests pass might be fine?
if (sideloaded) {
store._push({
data: null,
included: sideloaded
});
}
});
return internalModel;
}, function (error) {
if (error instanceof _error.InvalidError) {
let parsedErrors;
if (typeof serializer.extractErrors === 'function') {
parsedErrors = serializer.extractErrors(store, modelClass, error, snapshot.id);
} else {
parsedErrors = errorsArrayToHash(error.errors);
}
store.recordWasInvalid(internalModel, parsedErrors, error);
} else {
store.recordWasError(internalModel, error);
}
throw error;
}, label);
}
/**
*
* @param store
* @param cache modelFactoryCache
* @param normalizedModelName already normalized modelName
* @return {*}
*/
function getModelFactory(store, cache, normalizedModelName) {
let factory = cache[normalizedModelName];
if (!factory) {
factory = _lookupModelFactory(store, normalizedModelName);
if (!factory) {
//Support looking up mixins as base types for polymorphic relationships
factory = _modelForMixin(store, normalizedModelName);
}
if (!factory) {
// we don't cache misses in case someone wants to register a missing model
return null;
}
let klass = factory.class;
(true && Ember.assert("'".concat(Ember.inspect(klass), "' does not appear to be an ember-data model"), klass.isModel)); // TODO: deprecate this
let hasOwnModelNameSet = klass.modelName && klass.hasOwnProperty('modelName');
if (!hasOwnModelNameSet) {
klass.modelName = normalizedModelName;
}
cache[normalizedModelName] = factory;
}
return factory;
}
function _lookupModelFactory(store, normalizedModelName) {
let owner = Ember.getOwner(store);
return owner.factoryFor("model:".concat(normalizedModelName));
}
/*
In case someone defined a relationship to a mixin, for example:
```
import Model, { belongsTo, hasMany } from '@ember-data/model';
let Comment = Model.extend({
owner: belongsTo('commentable'. { polymorphic: true })
});
let Commentable = Ember.Mixin.create({
comments: hasMany('comment')
});
```
we want to look up a Commentable class which has all the necessary
relationship metadata. Thus, we look up the mixin and create a mock
Model, so we can access the relationship CPs of the mixin (`comments`)
in this case
*/
function _modelForMixin(store, normalizedModelName) {
let owner = Ember.getOwner(store);
let MaybeMixin = owner.factoryFor("mixin:".concat(normalizedModelName));
let mixin = MaybeMixin && MaybeMixin.class;
if (mixin) {
let ModelForMixin = _model.default.extend(mixin);
ModelForMixin.reopenClass({
__isMixin: true,
__mixin: mixin
}); //Cache the class as a model
owner.register('model:' + normalizedModelName, ModelForMixin);
}
return _lookupModelFactory(store, normalizedModelName);
}
let assertDestroyingStore;
let assertDestroyedStoreOnly;
if (true
/* DEBUG */
) {
assertDestroyingStore = function assertDestroyedStore(store, method) {
if (!store.shouldAssertMethodCallsOnDestroyedStore) {
(true && !(!(store.isDestroying || store.isDestroyed)) && Ember.deprecate("Attempted to call store.".concat(method, "(), but the store instance has already been destroyed."), !(store.isDestroying || store.isDestroyed), {
id: 'ember-data:method-calls-on-destroyed-store',
until: '3.8'
}));
} else {
(true && Ember.assert("Attempted to call store.".concat(method, "(), but the store instance has already been destroyed."), !(store.isDestroying || store.isDestroyed)));
}
};
assertDestroyedStoreOnly = function assertDestroyedStoreOnly(store, method) {
if (!store.shouldAssertMethodCallsOnDestroyedStore) {
(true && !(!store.isDestroyed) && Ember.deprecate("Attempted to call store.".concat(method, "(), but the store instance has already been destroyed."), !store.isDestroyed, {
id: 'ember-data:method-calls-on-destroyed-store',
until: '3.8'
}));
} else {
(true && Ember.assert("Attempted to call store.".concat(method, "(), but the store instance has already been destroyed."), !store.isDestroyed));
}
};
}
});
define("@ember-data/store/-private/system/ts-upgrade-map", ["exports", "@ember-data/store/-private/ts-interfaces/utils/brand"], function (_exports, _brand) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.upgradeForInternal = upgradeForInternal;
/**
@module @ember-data/store
*/
/**
* Casts a public interface to the matching internal class implementation
*
* @internal
*/
function upgradeForInternal(external) {
return external;
}
});
define("@ember-data/store/-private/ts-interfaces/ds-model", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/ember-data-json-api", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/fetch-manager", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.RequestStateEnum = void 0;
let RequestStateEnum;
_exports.RequestStateEnum = RequestStateEnum;
(function (RequestStateEnum) {
RequestStateEnum["pending"] = "pending";
RequestStateEnum["fulfilled"] = "fulfilled";
RequestStateEnum["rejected"] = "rejected";
})(RequestStateEnum || (_exports.RequestStateEnum = RequestStateEnum = {}));
});
define("@ember-data/store/-private/ts-interfaces/identifier", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.DEBUG_IDENTIFIER_BUCKET = _exports.DEBUG_CLIENT_ORIGINATED = _exports.IS_IDENTIFIER = void 0;
/**
@module @ember-data/store
*/
const IS_IDENTIFIER = Symbol('is-identifier'); // provided for additional debuggability
_exports.IS_IDENTIFIER = IS_IDENTIFIER;
const DEBUG_CLIENT_ORIGINATED = Symbol('record-originated-on-client');
_exports.DEBUG_CLIENT_ORIGINATED = DEBUG_CLIENT_ORIGINATED;
const DEBUG_IDENTIFIER_BUCKET = Symbol('identifier-bucket');
_exports.DEBUG_IDENTIFIER_BUCKET = DEBUG_IDENTIFIER_BUCKET;
});
define("@ember-data/store/-private/ts-interfaces/minimum-serializer-interface", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/promise-proxies", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/record-data-json-api", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/record-data-schemas", ["@ember-data/store/-private/ts-interfaces/utils/brand"], function (_brand) {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/record-data-store-wrapper", ["@ember-data/store/-private/ts-interfaces/utils/brand"], function (_brand) {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/record-data", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/record", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/relationship-record-data", [], function () {
"use strict";
});
define("@ember-data/store/-private/ts-interfaces/utils", [], function () {
"use strict";
});
define("@ember-data/store/-private/utils/has-valid-id", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = hasValidId;
/**
* Ensures that one of id or clientId is not null|undefined|""
*
* Caution! casts "id" to string for type narrowing, as we are unable to
* narrow the correct one in this manner.
*
* @internal
*/
function hasValidId(id, clientId) {
// weed out anything falsey
if (!id && !clientId) {
return false;
}
return true;
}
});
define("@ember-data/store/-private/utils/is-non-empty-string", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = isNonEmptyString;
function isNonEmptyString(str) {
return typeof str === 'string' && str.length > 0;
}
});
define("@ember-data/store/-private/utils/promise-record", ["exports", "@ember-data/store/-private/system/promise-proxies"], function (_exports, _promiseProxies) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = promiseRecord;
/**
@module @ember-data/store
*/
/**
* Get the materialized model from the internalModel/promise
* that returns an internal model and return it in a promiseObject.
*
* Useful for returning from find methods
*
* @internal
*/
function promiseRecord(internalModelPromise, label) {
let toReturn = internalModelPromise.then(internalModel => internalModel.getRecord());
return (0, _promiseProxies.promiseObject)(toReturn, label);
}
});
define("@ember-data/store/-private/identifiers/utils/uuid-v4", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = uuidv4;
/**
@module @ember-data/store
*/
// support IE11
const CRYPTO = typeof window !== 'undefined' && window.msCrypto && typeof window.msCrypto.getRandomValues === 'function' ? window.msCrypto : window.crypto; // we might be able to optimize this by requesting more bytes than we need at a time
function rng() {
// WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
let rnds8 = new Uint8Array(16);
return CRYPTO.getRandomValues(rnds8);
}
/**
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
*/
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex[i] = (i + 0x100).toString(16).substr(1);
}
function bytesToUuid(buf) {
let bth = byteToHex; // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
return [bth[buf[0]], bth[buf[1]], bth[buf[2]], bth[buf[3]], '-', bth[buf[4]], bth[buf[5]], '-', bth[buf[6]], bth[buf[7]], '-', bth[buf[8]], bth[buf[9]], '-', bth[buf[10]], bth[buf[11]], bth[buf[12]], bth[buf[13]], bth[buf[14]], bth[buf[15]]].join('');
}
function uuidv4() {
let rnds = rng(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds[6] = rnds[6] & 0x0f | 0x40;
rnds[8] = rnds[8] & 0x3f | 0x80;
return bytesToUuid(rnds);
}
});
define("@ember-data/store/-private/system/model/errors", ["exports", "@ember-data/store/-private/system/deprecated-evented"], function (_exports, _deprecatedEvented) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
/**
Holds validation errors for a given record, organized by attribute names.
Every `Model` has an `errors` property that is an instance of
`Errors`. This can be used to display validation error
messages returned from the server when a `record.save()` rejects.
For Example, if you had a `User` model that looked like this:
```app/models/user.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
username: attr('string'),
email: attr('string')
});
```
And you attempted to save a record that did not validate on the backend:
```javascript
let user = store.createRecord('user', {
username: 'tomster',
email: 'invalidEmail'
});
user.save();
```
Your backend would be expected to return an error response that described
the problem, so that error messages can be generated on the app.
API responses will be translated into instances of `Errors` differently,
depending on the specific combination of adapter and serializer used. You
may want to check the documentation or the source code of the libraries
that you are using, to know how they expect errors to be communicated.
Errors can be displayed to the user by accessing their property name
to get an array of all the error objects for that property. Each
error object is a JavaScript object with two keys:
- `message` A string containing the error message from the backend
- `attribute` The name of the property associated with this error message
```handlebars
Username: {{input value=username}}
{{#each model.errors.username as |error|}}
{{error.message}}
{{/each}}
Email: {{input value=email}}
{{#each model.errors.email as |error|}}
{{error.message}}
{{/each}}
```
You can also access the special `messages` property on the error
object to get an array of all the error strings.
```handlebars
{{#each model.errors.messages as |message|}}
{{message}}
{{/each}}
```
@class Errors
@extends Ember.ArrayProxy
@uses Ember.Evented
*/
var _default = Ember.ArrayProxy.extend(_deprecatedEvented.default, {
/**
Register with target handler
@method _registerHandlers
@private
*/
_registerHandlers(becameInvalid, becameValid) {
this._registeredHandlers = {
becameInvalid,
becameValid
};
},
/**
@property errorsByAttributeName
@type {MapWithDefault}
@private
*/
errorsByAttributeName: Ember.computed(function () {
return new Map();
}),
/**
Returns errors for a given attribute
```javascript
let user = store.createRecord('user', {
username: 'tomster',
email: 'invalidEmail'
});
user.save().catch(function(){
user.get('errors').errorsFor('email'); // returns:
// [{attribute: "email", message: "Doesn't look like a valid email."}]
});
```
@method errorsFor
@param {String} attribute
@return {Array}
*/
errorsFor(attribute) {
let map = Ember.get(this, 'errorsByAttributeName');
if (!map.has(attribute)) {
map.set(attribute, Ember.A());
}
return map.get(attribute);
},
/**
An array containing all of the error messages for this
record. This is useful for displaying all errors to the user.
```handlebars
{{#each model.errors.messages as |message|}}
{{message}}
{{/each}}
```
@property messages
@type {Array}
*/
messages: Ember.computed.mapBy('content', 'message'),
/**
@property content
@type {Array}
@private
*/
content: Ember.computed(function () {
return Ember.A();
}),
/**
@method unknownProperty
@private
*/
unknownProperty(attribute) {
let errors = this.errorsFor(attribute);
if (errors.length === 0) {
return undefined;
}
return errors;
},
/**
Total number of errors.
@property length
@type {Number}
@readOnly
*/
/**
@property isEmpty
@type {Boolean}
@readOnly
*/
isEmpty: Ember.computed.not('length').readOnly(),
/**
Manually adds errors to the record. This will triger the `becameInvalid` event/ lifecycle method on
the record and transition the record into an `invalid` state.
Example
```javascript
let errors = get(user, 'errors');
// add multiple errors
errors.add('password', [
'Must be at least 12 characters',
'Must contain at least one symbol',
'Cannot contain your name'
]);
errors.errorsFor('password');
// =>
// [
// { attribute: 'password', message: 'Must be at least 12 characters' },
// { attribute: 'password', message: 'Must contain at least one symbol' },
// { attribute: 'password', message: 'Cannot contain your name' },
// ]
// add a single error
errors.add('username', 'This field is required');
errors.errorsFor('password');
// =>
// [
// { attribute: 'username', message: 'This field is required' },
// ]
```
@method add
@param {string} attribute - the property name of an attribute or relationship
@param {string[]|string} messages - an error message or array of error messages for the attribute
*/
add(attribute, messages) {
let wasEmpty = Ember.get(this, 'isEmpty');
this._add(attribute, messages);
if (wasEmpty && !Ember.get(this, 'isEmpty')) {
this._registeredHandlers && this._registeredHandlers.becameInvalid();
if (true
/* DEBUG */
&& this._has('becameInvalid')) {
this.trigger('becameInvalid');
}
}
},
/**
Adds error messages to a given attribute without sending event.
@method _add
@private
*/
_add(attribute, messages) {
messages = this._findOrCreateMessages(attribute, messages);
this.addObjects(messages);
this.errorsFor(attribute).addObjects(messages);
this.notifyPropertyChange(attribute);
},
/**
@method _findOrCreateMessages
@private
*/
_findOrCreateMessages(attribute, messages) {
let errors = this.errorsFor(attribute);
let messagesArray = Ember.makeArray(messages);
let _messages = new Array(messagesArray.length);
for (let i = 0; i < messagesArray.length; i++) {
let message = messagesArray[i];
let err = errors.findBy('message', message);
if (err) {
_messages[i] = err;
} else {
_messages[i] = {
attribute: attribute,
message: message
};
}
}
return _messages;
},
/**
Manually removes all errors for a given member from the record.
This will transition the record into a `valid` state, and
triggers the `becameValid` event and lifecycle method.
Example:
```javascript
let errors = get('user', errors);
errors.add('phone', ['error-1', 'error-2']);
errors.errorsFor('phone');
// =>
// [
// { attribute: 'phone', message: 'error-1' },
// { attribute: 'phone', message: 'error-2' },
// ]
errors.remove('phone');
errors.errorsFor('phone');
// => undefined
```
@method remove
@param {string} member - the property name of an attribute or relationship
*/
remove(attribute) {
if (Ember.get(this, 'isEmpty')) {
return;
}
this._remove(attribute);
if (Ember.get(this, 'isEmpty')) {
this._registeredHandlers && this._registeredHandlers.becameValid();
if (true
/* DEBUG */
&& this._has('becameValid')) {
this.trigger('becameValid');
}
}
},
/**
Removes all error messages from the given attribute without sending event.
@method _remove
@private
*/
_remove(attribute) {
if (Ember.get(this, 'isEmpty')) {
return;
}
let content = this.rejectBy('attribute', attribute);
Ember.get(this, 'content').setObjects(content);
Ember.get(this, 'errorsByAttributeName').delete(attribute);
this.notifyPropertyChange(attribute);
this.notifyPropertyChange('length');
},
/**
Manually clears all errors for the record.
This will transition the record into a `valid` state, and
will trigger the `becameValid` event and lifecycle method.
Example:
```javascript
let errors = get('user', errors);
errors.add('username', ['error-a']);
errors.add('phone', ['error-1', 'error-2']);
errors.errorsFor('username');
// =>
// [
// { attribute: 'username', message: 'error-a' },
// ]
errors.errorsFor('phone');
// =>
// [
// { attribute: 'phone', message: 'error-1' },
// { attribute: 'phone', message: 'error-2' },
// ]
errors.clear();
errors.errorsFor('username');
// => undefined
errors.errorsFor('phone');
// => undefined
errors.get('messages')
// => []
```
@method remove
*/
clear() {
if (Ember.get(this, 'isEmpty')) {
return;
}
this._clear();
this._registeredHandlers && this._registeredHandlers.becameValid();
if (true
/* DEBUG */
&& this._has('becameValid')) {
this.trigger('becameValid');
}
},
/**
Removes all error messages.
to the record.
@method _clear
@private
*/
_clear() {
if (Ember.get(this, 'isEmpty')) {
return;
}
let errorsByAttributeName = Ember.get(this, 'errorsByAttributeName');
let attributes = [];
errorsByAttributeName.forEach(function (_, attribute) {
attributes.push(attribute);
});
errorsByAttributeName.clear();
attributes.forEach(attribute => {
this.notifyPropertyChange(attribute);
});
Ember.ArrayProxy.prototype.clear.call(this);
},
/**
Checks if there are error messages for the given attribute.
```app/routes/user/edit.js
import Route from '@ember/routing/route';
export default Route.extend({
actions: {
save: function(user) {
if (user.get('errors').has('email')) {
return alert('Please update your email before attempting to save.');
}
user.save();
}
}
});
```
@method has
@param {String} attribute
@return {Boolean} true if there some errors on given attribute
*/
has(attribute) {
return this.errorsFor(attribute).length > 0;
}
});
_exports.default = _default;
});
define("@ember-data/store/-private/system/model/internal-model", ["exports", "@ember-data/store/-private/system/model/states", "@ember-data/store/-private/system/snapshot", "@ember-data/store/-private/system/ordered-set", "@ember-data/store/-private/system/many-array", "@ember-data/store/-private/system/promise-proxies", "@ember-data/store/-private/system/errors-utils", "@ember-data/store/-private/system/references", "@ember-data/store/-private/system/record-data-for", "@ember-data/canary-features", "@ember-data/store/-private/identifiers/cache", "@ember-data/store/-private/system/store/internal-model-factory", "@ember-data/store/-private/system/coerce-id"], function (_exports, _states, _snapshot, _orderedSet, _manyArray, _promiseProxies, _errorsUtils, _references, _recordDataFor, _canaryFeatures, _cache, _internalModelFactory, _coerceId) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/*
The TransitionChainMap caches the `state.enters`, `state.setups`, and final state reached
when transitioning from one state to another, so that future transitions can replay the
transition without needing to walk the state tree, collect these hook calls and determine
the state to transition into.
A future optimization would be to build a single chained method out of the collected enters
and setups. It may also be faster to do a two level cache (from: { to }) instead of caching based
on a key that adds the two together.
*/
const TransitionChainMap = Object.create(null);
const _extractPivotNameCache = Object.create(null);
const _splitOnDotCache = Object.create(null);
function splitOnDot(name) {
return _splitOnDotCache[name] || (_splitOnDotCache[name] = name.split('.'));
}
function extractPivotName(name) {
return _extractPivotNameCache[name] || (_extractPivotNameCache[name] = splitOnDot(name)[0]);
}
/*
`InternalModel` is the Model class that we use internally inside Ember Data to represent models.
Internal ED methods should only deal with `InternalModel` objects. It is a fast, plain Javascript class.
We expose `Model` to application code, by materializing a `Model` from `InternalModel` lazily, as
a performance optimization.
`InternalModel` should never be exposed to application code. At the boundaries of the system, in places
like `find`, `push`, etc. we convert between Models and InternalModels.
We need to make sure that the properties from `InternalModel` are correctly exposed/proxied on `Model`
if they are needed.
@private
@class InternalModel
*/
class InternalModel {
// Not typed yet
// The previous ManyArrays for this relationship which will be destroyed when
// we create a new ManyArray, but in the interim the retained version will be
// updated if inverse internal models are unloaded.
constructor(store, identifier) {
this.store = store;
this.identifier = identifier;
_defineProperty(this, "_id", void 0);
_defineProperty(this, "modelName", void 0);
_defineProperty(this, "clientId", void 0);
_defineProperty(this, "__recordData", void 0);
_defineProperty(this, "_isDestroyed", void 0);
_defineProperty(this, "isError", void 0);
_defineProperty(this, "_pendingRecordArrayManagerFlush", void 0);
_defineProperty(this, "_isDematerializing", void 0);
_defineProperty(this, "isReloading", void 0);
_defineProperty(this, "_doNotDestroy", void 0);
_defineProperty(this, "isDestroying", void 0);
_defineProperty(this, "_promiseProxy", void 0);
_defineProperty(this, "_record", void 0);
_defineProperty(this, "_scheduledDestroy", void 0);
_defineProperty(this, "_modelClass", void 0);
_defineProperty(this, "__deferredTriggers", void 0);
_defineProperty(this, "__recordArrays", void 0);
_defineProperty(this, "_references", void 0);
_defineProperty(this, "_recordReference", void 0);
_defineProperty(this, "_manyArrayCache", Object.create(null));
_defineProperty(this, "_retainedManyArrayCache", Object.create(null));
_defineProperty(this, "_relationshipPromisesCache", Object.create(null));
_defineProperty(this, "_relationshipProxyCache", Object.create(null));
_defineProperty(this, "currentState", void 0);
_defineProperty(this, "error", void 0);
this._id = identifier.id;
this.modelName = identifier.type;
this.clientId = identifier.lid;
this.__recordData = null; // this ensure ordered set can quickly identify this as unique
this[Ember.GUID_KEY] = identifier.lid;
this._promiseProxy = null;
this._record = null;
this._isDestroyed = false;
this.isError = false;
this._pendingRecordArrayManagerFlush = false; // used by the recordArrayManager
// During dematerialization we don't want to rematerialize the record. The
// reason this might happen is that dematerialization removes records from
// record arrays, and Ember arrays will always `objectAt(0)` and
// `objectAt(len - 1)` to test whether or not `firstObject` or `lastObject`
// have changed.
this._isDematerializing = false;
this._scheduledDestroy = null;
this.resetRecord(); // caches for lazy getters
this._modelClass = null;
this.__deferredTriggers = null;
this.__recordArrays = null;
this._references = null;
this._recordReference = null;
}
get id() {
if (_canaryFeatures.IDENTIFIERS) {
return this.identifier.id; // || this._id;
}
return this._id;
}
set id(value) {
if (_canaryFeatures.IDENTIFIERS) {
if (value !== this._id) {
let newIdentifier = {
type: this.identifier.type,
lid: this.identifier.lid,
id: value
};
(0, _cache.identifierCacheFor)(this.store).updateRecordIdentifier(this.identifier, newIdentifier); // TODO Show deprecation for private api
}
} else if (!_canaryFeatures.IDENTIFIERS) {
this._id = value;
}
}
get modelClass() {
return this._modelClass || (this._modelClass = this.store.modelFor(this.modelName));
}
get type() {
return this.modelClass;
}
get recordReference() {
if (this._recordReference === null) {
this._recordReference = new _references.RecordReference(this.store, this);
}
return this._recordReference;
}
get _recordData() {
if (this.__recordData === null) {
let recordData = this.store._createRecordData(this.modelName, this.id, this.clientId);
this._recordData = recordData;
return recordData;
}
return this.__recordData;
}
set _recordData(newValue) {
this.__recordData = newValue;
}
get _recordArrays() {
if (this.__recordArrays === null) {
this.__recordArrays = new _orderedSet.default();
}
return this.__recordArrays;
}
get references() {
if (this._references === null) {
this._references = Object.create(null);
}
return this._references;
}
get _deferredTriggers() {
if (this.__deferredTriggers === null) {
this.__deferredTriggers = [];
}
return this.__deferredTriggers;
}
isHiddenFromRecordArrays() {
// During dematerialization we don't want to rematerialize the record.
// recordWasDeleted can cause other records to rematerialize because it
// removes the internal model from the array and Ember arrays will always
// `objectAt(0)` and `objectAt(len -1)` to check whether `firstObject` or
// `lastObject` have changed. When this happens we don't want those
// models to rematerialize their records.
// eager checks to avoid instantiating record data if we are empty or loading
if (this.isEmpty()) {
return true;
}
if (_canaryFeatures.RECORD_DATA_STATE) {
if (this.isLoading()) {
return false;
}
}
let isRecordFullyDeleted;
if (_canaryFeatures.RECORD_DATA_STATE) {
isRecordFullyDeleted = this._isRecordFullyDeleted();
} else {
isRecordFullyDeleted = this.currentState.stateName === 'root.deleted.saved';
}
return this._isDematerializing || this.hasScheduledDestroy() || this.isDestroyed || isRecordFullyDeleted;
}
_isRecordFullyDeleted() {
if (_canaryFeatures.RECORD_DATA_STATE) {
if (this._recordData.isDeletionCommitted && this._recordData.isDeletionCommitted()) {
return true;
} else if (this._recordData.isNew && this._recordData.isDeleted && this._recordData.isNew() && this._recordData.isDeleted()) {
return true;
} else {
return this.currentState.stateName === 'root.deleted.saved';
}
} else {
// assert here
return false;
}
}
isRecordInUse() {
let record = this._record;
return record && !(record.get('isDestroyed') || record.get('isDestroying'));
}
isEmpty() {
return this.currentState.isEmpty;
}
isLoading() {
return this.currentState.isLoading;
}
isLoaded() {
return this.currentState.isLoaded;
}
hasDirtyAttributes() {
return this.currentState.hasDirtyAttributes;
}
isSaving() {
return this.currentState.isSaving;
}
isDeleted() {
if (_canaryFeatures.RECORD_DATA_STATE) {
if (this._recordData.isDeleted) {
return this._recordData.isDeleted();
} else {
return this.currentState.isDeleted;
}
} else {
return this.currentState.isDeleted;
}
}
isNew() {
if (_canaryFeatures.RECORD_DATA_STATE) {
if (this._recordData.isNew) {
return this._recordData.isNew();
} else {
return this.currentState.isNew;
}
} else {
return this.currentState.isNew;
}
}
isValid() {
if (_canaryFeatures.RECORD_DATA_ERRORS) {} else {
return this.currentState.isValid;
}
}
dirtyType() {
return this.currentState.dirtyType;
}
getRecord(properties) {
if (!this._record && !this._isDematerializing) {
let {
store
} = this; // lookupFactory should really return an object that creates
// instances with the injections applied
let createOptions = {
store,
_internalModel: this,
currentState: this.currentState
};
if (!_canaryFeatures.REQUEST_SERVICE) {
createOptions.isError = this.isError;
createOptions.adapterError = this.error;
}
if (properties !== undefined) {
(true && Ember.assert("You passed '".concat(properties, "' as properties for record creation instead of an object."), typeof properties === 'object' && properties !== null));
if ('id' in properties) {
const id = (0, _coerceId.default)(properties.id);
if (id !== null) {
this.setId(id);
}
} // convert relationship Records to RecordDatas before passing to RecordData
let defs = store._relationshipsDefinitionFor(this.modelName);
if (defs !== null) {
let keys = Object.keys(properties);
let relationshipValue;
for (let i = 0; i < keys.length; i++) {
let prop = keys[i];
let def = defs[prop];
if (def !== undefined) {
if (def.kind === 'hasMany') {
if (true
/* DEBUG */
) {
assertRecordsPassedToHasMany(properties[prop]);
}
relationshipValue = extractRecordDatasFromRecords(properties[prop]);
} else {
relationshipValue = extractRecordDataFromRecord(properties[prop]);
}
properties[prop] = relationshipValue;
}
}
}
}
let additionalCreateOptions = this._recordData._initRecordCreateOptions(properties);
Ember.assign(createOptions, additionalCreateOptions); // ensure that `getOwner(this)` works inside a model instance
Ember.setOwner(createOptions, Ember.getOwner(store));
this._record = store._modelFactoryFor(this.modelName).create(createOptions);
if (_canaryFeatures.IDENTIFIERS) {
(0, _internalModelFactory.setRecordIdentifier)(this._record, this.identifier);
}
this._triggerDeferredTriggers();
}
return this._record;
}
resetRecord() {
this._record = null;
this.isReloading = false;
this.error = null;
this.currentState = _states.default.empty;
}
dematerializeRecord() {
this._isDematerializing = true; // TODO IGOR add a test that fails when this is missing, something that involves canceliing a destroy
// and the destroy not happening, and then later on trying to destroy
this._doNotDestroy = false;
if (this._record) {
this._record.destroy();
Object.keys(this._relationshipProxyCache).forEach(key => {
if (this._relationshipProxyCache[key].destroy) {
this._relationshipProxyCache[key].destroy();
}
delete this._relationshipProxyCache[key];
});
Object.keys(this._manyArrayCache).forEach(key => {
let manyArray = this._retainedManyArrayCache[key] = this._manyArrayCache[key];
delete this._manyArrayCache[key];
if (manyArray && !manyArray._inverseIsAsync) {
/*
If the manyArray is for a sync relationship, we should clear it
to preserve the semantics of client-side delete.
It is likely in this case instead of retaining we should destroy
- @runspired
*/
manyArray.clear();
}
});
} // move to an empty never-loaded state
this._recordData.unloadRecord();
this.resetRecord();
this.updateRecordArrays();
}
deleteRecord() {
if (_canaryFeatures.RECORD_DATA_STATE) {
if (this._recordData.setIsDeleted) {
this._recordData.setIsDeleted(true);
}
}
this.send('deleteRecord');
}
save(options) {
let promiseLabel = 'DS: Model#save ' + this;
let resolver = Ember.RSVP.defer(promiseLabel);
if (_canaryFeatures.REQUEST_SERVICE) {
return this.store.scheduleSave(this, resolver, options);
} else {
this.store.scheduleSave(this, resolver, options);
return resolver.promise;
}
}
startedReloading() {
this.isReloading = true;
if (this.hasRecord) {
Ember.set(this._record, 'isReloading', true);
}
}
linkWasLoadedForRelationship(key, data) {
let relationships = {};
relationships[key] = data;
this._recordData.pushData({
id: this.id,
type: this.modelName,
relationships
});
}
finishedReloading() {
this.isReloading = false;
if (this.hasRecord) {
Ember.set(this._record, 'isReloading', false);
}
}
reload(options) {
if (_canaryFeatures.REQUEST_SERVICE) {
if (!options) {
options = {};
}
this.startedReloading();
let internalModel = this;
let promiseLabel = 'DS: Model#reload of ' + this;
return internalModel.store._reloadRecord(internalModel, options).then(function () {
//TODO NOW seems like we shouldn't need to do this
return internalModel;
}, function (error) {
throw error;
}, 'DS: Model#reload complete, update flags').finally(function () {
internalModel.finishedReloading();
internalModel.updateRecordArrays();
});
} else {
this.startedReloading();
let internalModel = this;
let promiseLabel = 'DS: Model#reload of ' + this;
return new Ember.RSVP.Promise(function (resolve) {
internalModel.send('reloadRecord', {
resolve,
options
});
}, promiseLabel).then(function () {
internalModel.didCleanError();
return internalModel;
}, function (error) {
internalModel.didError(error);
throw error;
}, 'DS: Model#reload complete, update flags').finally(function () {
internalModel.finishedReloading();
internalModel.updateRecordArrays();
});
}
}
/*
Unload the record for this internal model. This will cause the record to be
destroyed and freed up for garbage collection. It will also do a check
for cleaning up internal models.
This check is performed by first computing the set of related internal
models. If all records in this set are unloaded, then the entire set is
destroyed. Otherwise, nothing in the set is destroyed.
This means that this internal model will be freed up for garbage collection
once all models that refer to it via some relationship are also unloaded.
*/
unloadRecord() {
if (this.isDestroyed) {
return;
}
this.send('unloadRecord');
this.dematerializeRecord();
if (this._scheduledDestroy === null) {
this._scheduledDestroy = Ember.run.backburner.schedule('destroy', this, '_checkForOrphanedInternalModels');
}
}
hasScheduledDestroy() {
return !!this._scheduledDestroy;
}
cancelDestroy() {
(true && Ember.assert("You cannot cancel the destruction of an InternalModel once it has already been destroyed", !this.isDestroyed));
this._doNotDestroy = true;
this._isDematerializing = false;
Ember.run.cancel(this._scheduledDestroy);
this._scheduledDestroy = null;
} // typically, we prefer to async destroy this lets us batch cleanup work.
// Unfortunately, some scenarios where that is not possible. Such as:
//
// ```js
// const record = store.find(‘record’, 1);
// record.unloadRecord();
// store.createRecord(‘record’, 1);
// ```
//
// In those scenarios, we make that model's cleanup work, sync.
//
destroySync() {
if (this._isDematerializing) {
this.cancelDestroy();
}
this._checkForOrphanedInternalModels();
if (this.isDestroyed || this.isDestroying) {
return;
} // just in-case we are not one of the orphaned, we should still
// still destroy ourselves
this.destroy();
}
_checkForOrphanedInternalModels() {
this._isDematerializing = false;
this._scheduledDestroy = null;
if (this.isDestroyed) {
return;
}
}
eachRelationship(callback, binding) {
return this.modelClass.eachRelationship(callback, binding);
}
_findBelongsTo(key, resource, relationshipMeta, options) {
// TODO @runspired follow up if parent isNew then we should not be attempting load here
return this.store._findBelongsToByJsonApiResource(resource, this, relationshipMeta, options).then(internalModel => handleCompletedRelationshipRequest(this, key, resource._relationship, internalModel, null), e => handleCompletedRelationshipRequest(this, key, resource._relationship, null, e));
}
getBelongsTo(key, options) {
let resource = this._recordData.getBelongsTo(key);
let identifier = resource && resource.data ? (0, _cache.identifierCacheFor)(this.store).getOrCreateRecordIdentifier(resource.data) : null;
let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
let store = this.store;
let parentInternalModel = this;
let async = relationshipMeta.options.async;
let isAsync = typeof async === 'undefined' ? true : async;
let _belongsToState = {
key,
store,
originatingInternalModel: this,
modelName: relationshipMeta.type
};
if (isAsync) {
let internalModel = identifier !== null ? store._internalModelForResource(identifier) : null;
if (resource._relationship.hasFailedLoadAttempt) {
return this._relationshipProxyCache[key];
}
let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
return this._updatePromiseProxyFor('belongsTo', key, {
promise,
content: internalModel ? internalModel.getRecord() : null,
_belongsToState
});
} else {
if (identifier === null) {
return null;
} else {
let internalModel = store._internalModelForResource(identifier);
let toReturn = internalModel.getRecord();
(true && Ember.assert("You looked up the '" + key + "' relationship on a '" + parentInternalModel.modelName + "' with id " + parentInternalModel.id + ' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.belongsTo({ async: true })`)', toReturn === null || !toReturn.get('isEmpty')));
return toReturn;
}
}
} // TODO Igor consider getting rid of initial state
getManyArray(key, isAsync = false) {
let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
let jsonApi = this._recordData.getHasMany(key);
let manyArray = this._manyArrayCache[key];
(true && Ember.assert("Error: relationship ".concat(this.modelName, ":").concat(key, " has both many array and retained many array"), !manyArray || !this._retainedManyArrayCache[key]));
if (!manyArray) {
let initialState = this.store._getHasManyByJsonApiResource(jsonApi); // TODO move this to a public api
let inverseIsAsync = jsonApi._relationship ? jsonApi._relationship._inverseIsAsync() : false;
manyArray = _manyArray.default.create({
store: this.store,
type: this.store.modelFor(relationshipMeta.type),
recordData: this._recordData,
meta: jsonApi.meta,
key,
isPolymorphic: relationshipMeta.options.polymorphic,
initialState: initialState.slice(),
_inverseIsAsync: inverseIsAsync,
internalModel: this,
isLoaded: !isAsync
});
this._manyArrayCache[key] = manyArray;
}
if (this._retainedManyArrayCache[key]) {
this._retainedManyArrayCache[key].destroy();
delete this._retainedManyArrayCache[key];
}
return manyArray;
}
fetchAsyncHasMany(key, relationshipMeta, jsonApi, manyArray, options) {
// TODO @runspired follow up if parent isNew then we should not be attempting load here
let loadingPromise = this._relationshipPromisesCache[key];
if (loadingPromise) {
return loadingPromise;
}
loadingPromise = this.store._findHasManyByJsonApiResource(jsonApi, this, relationshipMeta, options).then(initialState => {
// TODO why don't we do this in the store method
manyArray.retrieveLatest();
manyArray.set('isLoaded', true);
return manyArray;
}).then(manyArray => handleCompletedRelationshipRequest(this, key, jsonApi._relationship, manyArray, null), e => handleCompletedRelationshipRequest(this, key, jsonApi._relationship, null, e));
this._relationshipPromisesCache[key] = loadingPromise;
return loadingPromise;
}
getHasMany(key, options) {
let jsonApi = this._recordData.getHasMany(key);
let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
let async = relationshipMeta.options.async;
let isAsync = typeof async === 'undefined' ? true : async;
let manyArray = this.getManyArray(key, isAsync);
if (isAsync) {
if (jsonApi._relationship.hasFailedLoadAttempt) {
return this._relationshipProxyCache[key];
}
let promise = this.fetchAsyncHasMany(key, relationshipMeta, jsonApi, manyArray, options);
return this._updatePromiseProxyFor('hasMany', key, {
promise,
content: manyArray
});
} else {
(true && Ember.assert("You looked up the '".concat(key, "' relationship on a '").concat(this.type.modelName, "' with id ").concat(this.id, " but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async ('DS.hasMany({ async: true })')"), !manyArray.anyUnloaded()));
return manyArray;
}
}
_updatePromiseProxyFor(kind, key, args) {
let promiseProxy = this._relationshipProxyCache[key];
if (promiseProxy) {
if (args.content !== undefined) {
// this usage of `any` can be removed when `@types/ember_object` proxy allows `null` for content
promiseProxy.set('content', args.content);
}
promiseProxy.set('promise', args.promise);
} else {
const klass = kind === 'hasMany' ? _promiseProxies.PromiseManyArray : _promiseProxies.PromiseBelongsTo; // this usage of `any` can be removed when `@types/ember_object` proxy allows `null` for content
this._relationshipProxyCache[key] = klass.create(args);
}
return this._relationshipProxyCache[key];
}
reloadHasMany(key, options) {
let loadingPromise = this._relationshipPromisesCache[key];
if (loadingPromise) {
return loadingPromise;
}
let jsonApi = this._recordData.getHasMany(key); // TODO move this to a public api
if (jsonApi._relationship) {
jsonApi._relationship.setHasFailedLoadAttempt(false);
jsonApi._relationship.setShouldForceReload(true);
}
let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
let manyArray = this.getManyArray(key);
let promise = this.fetchAsyncHasMany(key, relationshipMeta, jsonApi, manyArray, options);
if (this._relationshipProxyCache[key]) {
return this._updatePromiseProxyFor('hasMany', key, {
promise
});
}
return promise;
}
reloadBelongsTo(key, options) {
let loadingPromise = this._relationshipPromisesCache[key];
if (loadingPromise) {
return loadingPromise;
}
let resource = this._recordData.getBelongsTo(key); // TODO move this to a public api
if (resource._relationship) {
resource._relationship.setHasFailedLoadAttempt(false);
resource._relationship.setShouldForceReload(true);
}
let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
if (this._relationshipProxyCache[key]) {
return this._updatePromiseProxyFor('belongsTo', key, {
promise
});
}
return promise;
}
destroyFromRecordData() {
if (this._doNotDestroy) {
this._doNotDestroy = false;
return;
}
this.destroy();
}
destroy() {
(true && Ember.assert('Cannot destroy an internalModel while its record is materialized', !this._record || this._record.get('isDestroyed') || this._record.get('isDestroying')));
this.isDestroying = true;
Object.keys(this._retainedManyArrayCache).forEach(key => {
this._retainedManyArrayCache[key].destroy();
delete this._retainedManyArrayCache[key];
});
(0, _internalModelFactory.internalModelFactoryFor)(this.store).remove(this);
this._isDestroyed = true;
}
eachAttribute(callback, binding) {
return this.modelClass.eachAttribute(callback, binding);
}
inverseFor(key) {
return this.modelClass.inverseFor(key);
}
setupData(data) {
let changedKeys = this._recordData.pushData(data, this.hasRecord);
if (this.hasRecord) {
this._record._notifyProperties(changedKeys);
}
this.pushedData();
}
getAttributeValue(key) {
return this._recordData.getAttr(key);
}
setDirtyHasMany(key, records) {
assertRecordsPassedToHasMany(records);
return this._recordData.setDirtyHasMany(key, extractRecordDatasFromRecords(records));
}
setDirtyBelongsTo(key, value) {
return this._recordData.setDirtyBelongsTo(key, extractRecordDataFromRecord(value));
}
setDirtyAttribute(key, value) {
if (this.isDeleted()) {
throw new Ember.Error("Attempted to set '".concat(key, "' to '").concat(value, "' on the deleted record ").concat(this));
}
let currentValue = this.getAttributeValue(key);
if (currentValue !== value) {
this._recordData.setDirtyAttribute(key, value);
let isDirty = this._recordData.isAttrDirty(key);
this.send('didSetProperty', {
name: key,
isDirty: isDirty
});
}
return value;
}
get isDestroyed() {
return this._isDestroyed;
}
get hasRecord() {
return !!this._record;
}
/*
@method createSnapshot
@private
*/
createSnapshot(options) {
return new _snapshot.default(options || {}, this.identifier, this.store);
}
/*
@method loadingData
@private
@param {Promise} promise
*/
loadingData(promise) {
if (_canaryFeatures.REQUEST_SERVICE) {
this.send('loadingData');
} else {
this.send('loadingData', promise);
}
}
/*
@method loadedData
@private
*/
loadedData() {
this.send('loadedData');
}
/*
@method notFound
@private
*/
notFound() {
this.send('notFound');
}
/*
@method pushedData
@private
*/
pushedData() {
this.send('pushedData');
}
hasChangedAttributes() {
if (_canaryFeatures.REQUEST_SERVICE) {
if (!this.__recordData) {
// no need to calculate changed attributes when calling `findRecord`
return false;
}
} else {
if (this.isLoading() && !this.isReloading) {
// no need to calculate changed attributes when calling `findRecord`
return false;
}
}
return this._recordData.hasChangedAttributes();
}
/*
Returns an object, whose keys are changed properties, and value is an
[oldProp, newProp] array.
@method changedAttributes
@private
*/
changedAttributes() {
if (_canaryFeatures.REQUEST_SERVICE) {
if (!this.__recordData) {
// no need to calculate changed attributes when calling `findRecord`
return {};
}
} else {
if (this.isLoading() && !this.isReloading) {
// no need to calculate changed attributes when calling `findRecord`
return {};
}
}
return this._recordData.changedAttributes();
}
/*
@method adapterWillCommit
@private
*/
adapterWillCommit() {
this._recordData.willCommit();
this.send('willCommit');
}
/*
@method adapterDidDirty
@private
*/
adapterDidDirty() {
this.send('becomeDirty');
this.updateRecordArrays();
}
/*
@method send
@private
@param {String} name
@param {Object} context
*/
send(name, context) {
let currentState = this.currentState;
if (!currentState[name]) {
this._unhandledEvent(currentState, name, context);
}
return currentState[name](this, context);
}
manyArrayRecordAdded(key) {
if (this.hasRecord) {
this._record.notifyHasManyAdded(key);
}
}
notifyHasManyChange(key) {
if (this.hasRecord) {
let manyArray = this._manyArrayCache[key];
if (manyArray) {
// TODO: this will "resurrect" previously unloaded records
// see test '1:many async unload many side'
// in `tests/integration/records/unload-test.js`
// probably we don't want to retrieve latest eagerly when notifyhasmany changed
// but rather lazily when someone actually asks for a manyarray
//
// that said, also not clear why we haven't moved this to retainedmanyarray so maybe that's the bit that's just not workign
manyArray.retrieveLatest();
}
this.updateRecordArrays();
}
}
notifyBelongsToChange(key) {
if (this.hasRecord) {
this._record.notifyBelongsToChange(key, this._record);
this.updateRecordArrays();
}
}
notifyPropertyChange(key) {
if (this.hasRecord) {
this._record.notifyPropertyChange(key);
this.updateRecordArrays();
}
let manyArray = this._manyArrayCache[key] || this._retainedManyArrayCache[key];
if (manyArray) {
let didRemoveUnloadedModel = manyArray.removeUnloadedInternalModel();
if (this._manyArrayCache[key] && didRemoveUnloadedModel) {
this._retainedManyArrayCache[key] = this._manyArrayCache[key];
delete this._manyArrayCache[key];
}
}
}
notifyStateChange(key) {
(true && Ember.assert('Cannot notify state change if Record Data State flag is not on', _canaryFeatures.RECORD_DATA_STATE));
if (this.hasRecord) {
if (!key || key === 'isNew') {
this.getRecord().notifyPropertyChange('isNew');
}
if (!key || key === 'isDeleted') {
this.getRecord().notifyPropertyChange('isDeleted');
}
}
if (!key || key === 'isDeletionCommitted') {
this.updateRecordArrays();
}
}
didCreateRecord() {
this._recordData.clientDidCreate();
}
rollbackAttributes() {
let dirtyKeys = this._recordData.rollbackAttributes();
if (Ember.get(this, 'isError')) {
this.didCleanError();
}
this.send('rolledBack');
if (this._record && dirtyKeys && dirtyKeys.length > 0) {
this._record._notifyProperties(dirtyKeys);
}
}
/*
@method transitionTo
@private
@param {String} name
*/
transitionTo(name) {
// POSSIBLE TODO: Remove this code and replace with
// always having direct reference to state objects
let pivotName = extractPivotName(name);
let state = this.currentState;
let oldState = state;
let transitionMapId = "".concat(state.stateName, "->").concat(name);
do {
if (state.exit) {
state.exit(this);
}
state = state.parentState;
} while (!state[pivotName]);
let setups;
let enters;
let i;
let l;
let map = TransitionChainMap[transitionMapId];
if (map) {
setups = map.setups;
enters = map.enters;
state = map.state;
} else {
setups = [];
enters = [];
let path = splitOnDot(name);
for (i = 0, l = path.length; i < l; i++) {
state = state[path[i]];
if (state.enter) {
enters.push(state);
}
if (state.setup) {
setups.push(state);
}
}
TransitionChainMap[transitionMapId] = {
setups,
enters,
state
};
}
for (i = 0, l = enters.length; i < l; i++) {
enters[i].enter(this);
}
this.currentState = state;
if (this.hasRecord) {
Ember.set(this._record, 'currentState', state);
}
for (i = 0, l = setups.length; i < l; i++) {
setups[i].setup(this);
}
this.updateRecordArrays();
}
_unhandledEvent(state, name, context) {
let errorMessage = 'Attempted to handle event `' + name + '` ';
errorMessage += 'on ' + String(this) + ' while in state ';
errorMessage += state.stateName + '. ';
if (context !== undefined) {
errorMessage += 'Called with ' + Ember.inspect(context) + '.';
}
throw new Ember.Error(errorMessage);
}
triggerLater(...args) {
if (this._deferredTriggers.push(args) !== 1) {
return;
}
this.store._updateInternalModel(this);
}
_triggerDeferredTriggers() {
//TODO: Before 1.0 we want to remove all the events that happen on the pre materialized record,
//but for now, we queue up all the events triggered before the record was materialized, and flush
//them once we have the record
if (!this.hasRecord) {
return;
}
let triggers = this._deferredTriggers;
let record = this._record;
let trigger = record.trigger;
for (let i = 0, l = triggers.length; i < l; i++) {
let eventName = triggers[i];
trigger.apply(record, eventName);
}
triggers.length = 0;
}
removeFromInverseRelationships(isNew = false) {
this._recordData.removeFromInverseRelationships(isNew);
}
/*
When a find request is triggered on the store, the user can optionally pass in
attributes and relationships to be preloaded. These are meant to behave as if they
came back from the server, except the user obtained them out of band and is informing
the store of their existence. The most common use case is for supporting client side
nested URLs, such as `/posts/1/comments/2` so the user can do
`store.findRecord('comment', 2, { preload: { post: 1 } })` without having to fetch the post.
Preloaded data can be attributes and relationships passed in either as IDs or as actual
models.
@method preloadData
@private
@param {Object} preload
*/
preloadData(preload) {
let jsonPayload = {}; //TODO(Igor) consider the polymorphic case
Object.keys(preload).forEach(key => {
let preloadValue = Ember.get(preload, key);
let relationshipMeta = this.modelClass.metaForProperty(key);
if (relationshipMeta.isRelationship) {
if (!jsonPayload.relationships) {
jsonPayload.relationships = {};
}
jsonPayload.relationships[key] = this._preloadRelationship(key, preloadValue);
} else {
if (!jsonPayload.attributes) {
jsonPayload.attributes = {};
}
jsonPayload.attributes[key] = preloadValue;
}
});
this._recordData.pushData(jsonPayload);
}
_preloadRelationship(key, preloadValue) {
let relationshipMeta = this.modelClass.metaForProperty(key);
let modelClass = relationshipMeta.type;
let data;
if (relationshipMeta.kind === 'hasMany') {
(true && Ember.assert('You need to pass in an array to set a hasMany property on a record', Array.isArray(preloadValue)));
data = preloadValue.map(value => this._convertPreloadRelationshipToJSON(value, modelClass));
} else {
data = this._convertPreloadRelationshipToJSON(preloadValue, modelClass);
}
return {
data
};
}
_convertPreloadRelationshipToJSON(value, modelClass) {
if (typeof value === 'string' || typeof value === 'number') {
return {
type: modelClass,
id: value
};
}
let internalModel;
if (value._internalModel) {
internalModel = value._internalModel;
} else {
internalModel = value;
} // TODO IGOR DAVID assert if no id is present
return {
type: internalModel.modelName,
id: internalModel.id
};
}
/*
Used to notify the store to update FilteredRecordArray membership.
@method updateRecordArrays
@private
*/
updateRecordArrays() {
// @ts-ignore: Store is untyped and typescript does not detect instance props set in `init`
this.store.recordArrayManager.recordDidChange(this);
}
setId(id) {
if (!_canaryFeatures.IDENTIFIERS) {
(true && Ember.assert("A record's id cannot be changed once it is in the loaded state", this.id === null || this.id === id));
}
let didChange = id !== this._id;
this._id = id;
if (didChange && id !== null) {
this.store.setRecordId(this.modelName, id, this.clientId);
}
if (didChange && this.hasRecord) {
this._record.notifyPropertyChange('id');
}
}
didError(error) {
if (!_canaryFeatures.REQUEST_SERVICE) {
this.error = error;
this.isError = true;
if (this.hasRecord) {
this._record.setProperties({
isError: true,
adapterError: error
});
}
}
}
didCleanError() {
if (!_canaryFeatures.REQUEST_SERVICE) {
this.error = null;
this.isError = false;
if (this.hasRecord) {
this._record.setProperties({
isError: false,
adapterError: null
});
}
}
}
/*
If the adapter did not return a hash in response to a commit,
merge the changed attributes and relationships into the existing
saved data.
@method adapterDidCommit
*/
adapterDidCommit(data) {
this.didCleanError();
let changedKeys = this._recordData.didCommit(data);
this.send('didCommit');
this.updateRecordArrays();
if (!data) {
return;
}
this._record._notifyProperties(changedKeys);
}
addErrorMessageToAttribute(attribute, message) {
Ember.get(this.getRecord(), 'errors')._add(attribute, message);
}
removeErrorMessageFromAttribute(attribute) {
Ember.get(this.getRecord(), 'errors')._remove(attribute);
}
clearErrorMessages() {
Ember.get(this.getRecord(), 'errors')._clear();
}
hasErrors() {
if (_canaryFeatures.RECORD_DATA_ERRORS) {
if (this._recordData.getErrors) {
return this._recordData.getErrors(_canaryFeatures.IDENTIFIERS ? this.identifier : {}).length > 0;
} else {
let errors = Ember.get(this.getRecord(), 'errors');
return errors.get('length') > 0;
}
} else {
let errors = Ember.get(this.getRecord(), 'errors');
return errors.get('length') > 0;
}
} // FOR USE DURING COMMIT PROCESS
/*
@method adapterDidInvalidate
@private
*/
adapterDidInvalidate(parsedErrors, error) {
if (_canaryFeatures.RECORD_DATA_ERRORS) {
let attribute;
if (error && parsedErrors) {
if (!this._recordData.getErrors) {
for (attribute in parsedErrors) {
if (parsedErrors.hasOwnProperty(attribute)) {
this.addErrorMessageToAttribute(attribute, parsedErrors[attribute]);
}
}
}
let jsonApiErrors = (0, _errorsUtils.errorsHashToArray)(parsedErrors);
this.send('becameInvalid');
if (jsonApiErrors.length === 0) {
jsonApiErrors = [{
title: 'Invalid Error',
detail: '',
source: {
pointer: '/data'
}
}];
}
this._recordData.commitWasRejected(_canaryFeatures.IDENTIFIERS ? this.identifier : {}, jsonApiErrors);
} else {
this.send('becameError');
this._recordData.commitWasRejected(_canaryFeatures.IDENTIFIERS ? this.identifier : {});
}
} else {
let attribute;
for (attribute in parsedErrors) {
if (parsedErrors.hasOwnProperty(attribute)) {
this.addErrorMessageToAttribute(attribute, parsedErrors[attribute]);
}
}
this.send('becameInvalid');
this._recordData.commitWasRejected();
}
}
notifyErrorsChange() {
let invalidErrors;
if (this._recordData.getErrors) {
invalidErrors = this._recordData.getErrors(_canaryFeatures.IDENTIFIERS ? this.identifier : {}) || [];
} else {
return;
}
this.notifyInvalidErrorsChange(invalidErrors);
}
notifyInvalidErrorsChange(jsonApiErrors) {
this.getRecord().invalidErrorsChanged(jsonApiErrors);
}
/*
@method adapterDidError
@private
*/
adapterDidError(error) {
this.send('becameError');
this.didError(error);
this._recordData.commitWasRejected();
}
toString() {
return "<".concat(this.modelName, ":").concat(this.id, ">");
}
referenceFor(kind, name) {
let reference = this.references[name];
if (!reference) {
// TODO IGOR AND DAVID REFACTOR
let relationship = (0, _recordDataFor.relationshipStateFor)(this, name);
if (true
/* DEBUG */
) {
let modelName = this.modelName;
(true && Ember.assert("There is no ".concat(kind, " relationship named '").concat(name, "' on a model of modelClass '").concat(modelName, "'"), relationship));
let actualRelationshipKind = relationship.relationshipMeta.kind;
(true && Ember.assert("You tried to get the '".concat(name, "' relationship on a '").concat(modelName, "' via record.").concat(kind, "('").concat(name, "'), but the relationship is of kind '").concat(actualRelationshipKind, "'. Use record.").concat(actualRelationshipKind, "('").concat(name, "') instead."), actualRelationshipKind === kind));
}
if (kind === 'belongsTo') {
reference = new _references.BelongsToReference(this.store, this, relationship, name);
} else if (kind === 'hasMany') {
reference = new _references.HasManyReference(this.store, this, relationship, name);
}
this.references[name] = reference;
}
return reference;
}
}
_exports.default = InternalModel;
function handleCompletedRelationshipRequest(internalModel, key, relationship, value, error) {
delete internalModel._relationshipPromisesCache[key];
relationship.setShouldForceReload(false);
if (error) {
relationship.setHasFailedLoadAttempt(true);
let proxy = internalModel._relationshipProxyCache[key]; // belongsTo relationships are sometimes unloaded
// when a load fails, in this case we need
// to make sure that we aren't proxying
// to destroyed content
// for the sync belongsTo reload case there will be no proxy
// for the async reload case there will be no proxy if the ui
// has never been accessed
if (proxy && relationship.kind === 'belongsTo') {
if (proxy.content.isDestroying) {
proxy.set('content', null);
} // clear the promise to make re-access safe
// e.g. after initial rejection, don't replay
// rejection on subsequent access, otherwise
// templates cause lots of rejected promise blow-ups
proxy.set('promise', Ember.RSVP.resolve(null));
}
throw error;
}
relationship.setHasFailedLoadAttempt(false); // only set to not stale if no error is thrown
relationship.setRelationshipIsStale(false);
return value;
}
function assertRecordsPassedToHasMany(records) {
// TODO only allow native arrays
(true && Ember.assert("You must pass an array of records to set a hasMany relationship", Array.isArray(records) || Ember.Array.detect(records)));
(true && Ember.assert("All elements of a hasMany relationship must be instances of Model, you passed ".concat(Ember.inspect(records)), function () {
return Ember.A(records).every(record => record.hasOwnProperty('_internalModel') === true);
}()));
}
function extractRecordDatasFromRecords(records) {
return records.map(extractRecordDataFromRecord);
}
function extractRecordDataFromRecord(recordOrPromiseRecord) {
if (!recordOrPromiseRecord) {
return null;
}
if (recordOrPromiseRecord.then) {
let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content');
(true && Ember.assert('You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.', content !== undefined));
return content ? (0, _recordDataFor.default)(content) : null;
}
return (0, _recordDataFor.default)(recordOrPromiseRecord);
}
});
define("@ember-data/store/-private/system/model/model", ["exports", "@ember-data/store/-private/system/deprecated-evented", "@ember-data/store/-private/system/promise-proxies", "@ember-data/store/-private/system/errors-utils", "@ember-data/store/-private/system/model/errors", "@ember-data/store/-private/system/relationships/ext", "@ember-data/store/-private/system/record-data-for", "@ember-data/store/-private/system/model/internal-model", "@ember-data/store/-private/system/model/states", "@ember-data/canary-features", "@ember-data/store/-private/system/coerce-id", "@ember-data/store/-private/system/store/internal-model-factory", "@ember-data/adapter/error"], function (_exports, _deprecatedEvented, _promiseProxies, _errorsUtils, _errors, _ext, _recordDataFor, _internalModel, _states, _canaryFeatures, _coerceId, _internalModelFactory, _error) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
const {
changeProperties
} = Ember;
/**
@module @ember-data/model
*/
function findPossibleInverses(type, inverseType, name, relationshipsSoFar) {
let possibleRelationships = relationshipsSoFar || [];
let relationshipMap = Ember.get(inverseType, 'relationships');
if (!relationshipMap) {
return possibleRelationships;
}
let relationshipsForType = relationshipMap.get(type.modelName);
let relationships = Array.isArray(relationshipsForType) ? relationshipsForType.filter(relationship => {
let optionsForRelationship = inverseType.metaForProperty(relationship.name).options;
if (!optionsForRelationship.inverse && optionsForRelationship.inverse !== null) {
return true;
}
return name === optionsForRelationship.inverse;
}) : null;
if (relationships) {
possibleRelationships.push.apply(possibleRelationships, relationships);
} //Recurse to support polymorphism
if (type.superclass) {
findPossibleInverses(type.superclass, inverseType, name, possibleRelationships);
}
return possibleRelationships;
}
const retrieveFromCurrentState = Ember.computed('currentState', function (key) {
return Ember.get(this._internalModel.currentState, key);
}).readOnly();
const isValidRecordData = Ember.computed('errors.length', function (key) {
return !(this.get('errors.length') > 0);
}).readOnly();
const isValid = _canaryFeatures.RECORD_DATA_ERRORS ? isValidRecordData : retrieveFromCurrentState;
let isDeletedCP;
if (_canaryFeatures.RECORD_DATA_STATE) {
isDeletedCP = Ember.computed('currentState', function () {
let rd = (0, _recordDataFor.default)(this);
if (rd.isDeleted) {
return rd.isDeleted();
} else {
return Ember.get(this._internalModel.currentState, 'isDeleted');
}
}).readOnly();
} else {
isDeletedCP = retrieveFromCurrentState;
}
let isNewCP;
if (_canaryFeatures.RECORD_DATA_STATE) {
isNewCP = Ember.computed('currentState', function () {
let rd = (0, _recordDataFor.default)(this);
if (rd.isNew) {
return rd.isNew();
} else {
return Ember.get(this._internalModel.currentState, 'isNew');
}
}).readOnly();
} else {
isNewCP = retrieveFromCurrentState;
}
let adapterError;
if (_canaryFeatures.REQUEST_SERVICE) {
adapterError = Ember.computed(function () {
let request = this._lastError;
if (!request) {
return null;
}
return request.state === 'rejected' && request.response.data;
});
} else {
adapterError = null;
}
let isError;
if (_canaryFeatures.REQUEST_SERVICE) {
isError = Ember.computed(function () {
let errorReq = this._errorRequests[this._errorRequests.length - 1];
if (!errorReq) {
return false;
} else {
return true;
}
});
} else {
isError = false;
}
let isReloading;
if (_canaryFeatures.REQUEST_SERVICE) {
isReloading = Ember.computed(function () {
let requests = this.store.getRequestStateService().getPendingRequestsForRecord((0, _internalModelFactory.recordIdentifierFor)(this));
return !!requests.find(req => req.request.data[0].options.isReloading);
});
} else {
isReloading = false;
}
/**
The model class that all Ember Data records descend from.
This is the public API of Ember Data models. If you are using Ember Data
in your application, this is the class you should use.
@class Model
@extends EmberObject
@uses EmberData.DeprecatedEvented
*/
const Model = Ember.Object.extend(_deprecatedEvented.default, {
init() {
this._super(...arguments);
if (true
/* DEBUG */
) {
if (!this._internalModel) {
throw new Ember.Error('You should not call `create` on a model. Instead, call `store.createRecord` with the attributes you would like to set.');
}
}
if (_canaryFeatures.RECORD_DATA_ERRORS) {
this._invalidRequests = [];
}
if (_canaryFeatures.REQUEST_SERVICE) {
this.store.getRequestStateService().subscribeForRecord(this._internalModel.identifier, request => {
if (request.state === 'rejected') {
// TODO filter out queries
this._lastError = request;
if (!(request.response && request.response.data instanceof _error.InvalidError)) {
this._errorRequests.push(request);
} else {
this._invalidRequests.push(request);
}
} else if (request.state === 'fulfilled') {
this._invalidRequests = [];
this._errorRequests = [];
this._lastError = null;
}
this._notifyNetworkChanges();
});
this._errorRequests = [];
this._lastError = null;
}
},
_notifyNetworkChanges: function () {
if (_canaryFeatures.REQUEST_SERVICE) {
['isSaving', 'isValid', 'isError', 'adapterError', 'isReloading'].forEach(key => this.notifyPropertyChange(key));
} else {
['isValid'].forEach(key => this.notifyPropertyChange(key));
}
},
/**
If this property is `true` the record is in the `empty`
state. Empty is the first state all records enter after they have
been created. Most records created by the store will quickly
transition to the `loading` state if data needs to be fetched from
the server or the `created` state if the record is created on the
client. A record can also enter the empty state if the adapter is
unable to locate the record.
@property isEmpty
@type {Boolean}
@readOnly
*/
isEmpty: retrieveFromCurrentState,
/**
If this property is `true` the record is in the `loading` state. A
record enters this state when the store asks the adapter for its
data. It remains in this state until the adapter provides the
requested data.
@property isLoading
@type {Boolean}
@readOnly
*/
isLoading: retrieveFromCurrentState,
/**
If this property is `true` the record is in the `loaded` state. A
record enters this state when its data is populated. Most of a
record's lifecycle is spent inside substates of the `loaded`
state.
Example
```javascript
let record = store.createRecord('model');
record.get('isLoaded'); // true
store.findRecord('model', 1).then(function(model) {
model.get('isLoaded'); // true
});
```
@property isLoaded
@type {Boolean}
@readOnly
*/
isLoaded: retrieveFromCurrentState,
/**
If this property is `true` the record is in the `dirty` state. The
record has local changes that have not yet been saved by the
adapter. This includes records that have been created (but not yet
saved) or deleted.
Example
```javascript
let record = store.createRecord('model');
record.get('hasDirtyAttributes'); // true
store.findRecord('model', 1).then(function(model) {
model.get('hasDirtyAttributes'); // false
model.set('foo', 'some value');
model.get('hasDirtyAttributes'); // true
});
```
@since 1.13.0
@property hasDirtyAttributes
@type {Boolean}
@readOnly
*/
hasDirtyAttributes: Ember.computed('currentState.isDirty', function () {
return this.get('currentState.isDirty');
}),
/**
If this property is `true` the record is in the `saving` state. A
record enters the saving state when `save` is called, but the
adapter has not yet acknowledged that the changes have been
persisted to the backend.
Example
```javascript
let record = store.createRecord('model');
record.get('isSaving'); // false
let promise = record.save();
record.get('isSaving'); // true
promise.then(function() {
record.get('isSaving'); // false
});
```
@property isSaving
@type {Boolean}
@readOnly
*/
isSaving: retrieveFromCurrentState,
/**
If this property is `true` the record is in the `deleted` state
and has been marked for deletion. When `isDeleted` is true and
`hasDirtyAttributes` is true, the record is deleted locally but the deletion
was not yet persisted. When `isSaving` is true, the change is
in-flight. When both `hasDirtyAttributes` and `isSaving` are false, the
change has persisted.
Example
```javascript
let record = store.createRecord('model');
record.get('isDeleted'); // false
record.deleteRecord();
// Locally deleted
record.get('isDeleted'); // true
record.get('hasDirtyAttributes'); // true
record.get('isSaving'); // false
// Persisting the deletion
let promise = record.save();
record.get('isDeleted'); // true
record.get('isSaving'); // true
// Deletion Persisted
promise.then(function() {
record.get('isDeleted'); // true
record.get('isSaving'); // false
record.get('hasDirtyAttributes'); // false
});
```
@property isDeleted
@type {Boolean}
@readOnly
*/
isDeleted: isDeletedCP,
/**
If this property is `true` the record is in the `new` state. A
record will be in the `new` state when it has been created on the
client and the adapter has not yet report that it was successfully
saved.
Example
```javascript
let record = store.createRecord('model');
record.get('isNew'); // true
record.save().then(function(model) {
model.get('isNew'); // false
});
```
@property isNew
@type {Boolean}
@readOnly
*/
isNew: isNewCP,
/**
If this property is `true` the record is in the `valid` state.
A record will be in the `valid` state when the adapter did not report any
server-side validation failures.
@property isValid
@type {Boolean}
@readOnly
*/
isValid: isValid,
_markInvalidRequestAsClean() {
if (_canaryFeatures.RECORD_DATA_ERRORS) {
this._invalidRequests = [];
this._notifyNetworkChanges();
}
},
/**
If the record is in the dirty state this property will report what
kind of change has caused it to move into the dirty
state. Possible values are:
- `created` The record has been created by the client and not yet saved to the adapter.
- `updated` The record has been updated by the client and not yet saved to the adapter.
- `deleted` The record has been deleted by the client and not yet saved to the adapter.
Example
```javascript
let record = store.createRecord('model');
record.get('dirtyType'); // 'created'
```
@property dirtyType
@type {String}
@readOnly
*/
dirtyType: retrieveFromCurrentState,
/**
If `true` the adapter reported that it was unable to save local
changes to the backend for any reason other than a server-side
validation error.
Example
```javascript
record.get('isError'); // false
record.set('foo', 'valid value');
record.save().then(null, function() {
record.get('isError'); // true
});
```
@property isError
@type {Boolean}
@readOnly
*/
isError: isError,
_markErrorRequestAsClean() {
this._errorRequests = [];
this._lastError = null;
this._notifyNetworkChanges();
},
/**
If `true` the store is attempting to reload the record from the adapter.
Example
```javascript
record.get('isReloading'); // false
record.reload();
record.get('isReloading'); // true
```
@property isReloading
@type {Boolean}
@readOnly
*/
isReloading: isReloading,
/**
All ember models have an id property. This is an identifier
managed by an external source. These are always coerced to be
strings before being used internally. Note when declaring the
attributes for a model it is an error to declare an id
attribute.
```javascript
let record = store.createRecord('model');
record.get('id'); // null
store.findRecord('model', 1).then(function(model) {
model.get('id'); // '1'
});
```
@property id
@type {String}
*/
/**
@property currentState
@private
@type {Object}
*/
currentState: _states.default.empty,
// defined here to avoid triggering setUnknownProperty
/**
@property _internalModel
@private
@type {Object}
*/
_internalModel: null,
// defined here to avoid triggering setUnknownProperty
/**
@property recordData
@private
@type undefined (reserved)
*/
// will be defined here to avoid triggering setUnknownProperty
/**
@property store
*/
store: null,
// defined here to avoid triggering setUnknownProperty
/**
When the record is in the `invalid` state this object will contain
any errors returned by the adapter. When present the errors hash
contains keys corresponding to the invalid property names
and values which are arrays of Javascript objects with two keys:
- `message` A string containing the error message from the backend
- `attribute` The name of the property associated with this error message
```javascript
record.get('errors.length'); // 0
record.set('foo', 'invalid value');
record.save().catch(function() {
record.get('errors').get('foo');
// [{message: 'foo should be a number.', attribute: 'foo'}]
});
```
The `errors` property us useful for displaying error messages to
the user.
```handlebars
Username: {{input value=username}}
{{#each model.errors.username as |error|}}
{{error.message}}
{{/each}}
Email: {{input value=email}}
{{#each model.errors.email as |error|}}
{{error.message}}
{{/each}}
```
You can also access the special `messages` property on the error
object to get an array of all the error strings.
```handlebars
{{#each model.errors.messages as |message|}}
{{message}}
{{/each}}
```
@property errors
@type {Errors}
*/
errors: Ember.computed(function () {
let errors = _errors.default.create();
errors._registerHandlers(() => {
this.send('becameInvalid');
}, () => {
this.send('becameValid');
});
if (_canaryFeatures.RECORD_DATA_ERRORS) {
let recordData = (0, _recordDataFor.default)(this);
let jsonApiErrors;
if (recordData.getErrors) {
jsonApiErrors = recordData.getErrors();
if (jsonApiErrors) {
let errorsHash = (0, _errorsUtils.errorsArrayToHash)(jsonApiErrors);
let errorKeys = Object.keys(errorsHash);
for (let i = 0; i < errorKeys.length; i++) {
errors._add(errorKeys[i], errorsHash[errorKeys[i]]);
}
}
}
}
return errors;
}).readOnly(),
invalidErrorsChanged(jsonApiErrors) {
if (_canaryFeatures.RECORD_DATA_ERRORS) {
this._clearErrorMessages();
let errors = (0, _errorsUtils.errorsArrayToHash)(jsonApiErrors);
let errorKeys = Object.keys(errors);
for (let i = 0; i < errorKeys.length; i++) {
this._addErrorMessageToAttribute(errorKeys[i], errors[errorKeys[i]]);
}
}
},
_addErrorMessageToAttribute(attribute, message) {
this.get('errors')._add(attribute, message);
},
_clearErrorMessages() {
this.get('errors')._clear();
},
/**
This property holds the `AdapterError` object with which
last adapter operation was rejected.
@property adapterError
@type {AdapterError}
*/
adapterError: adapterError,
/**
Create a JSON representation of the record, using the serialization
strategy of the store's adapter.
`serialize` takes an optional hash as a parameter, currently
supported options are:
- `includeId`: `true` if the record's ID should be included in the
JSON representation.
@method serialize
@param {Object} options
@return {Object} an object whose values are primitive JSON values only
*/
serialize(options) {
return this._internalModel.createSnapshot().serialize(options);
},
/**
Use [JSONSerializer](DS.JSONSerializer.html) to
get the JSON representation of a record.
`toJSON` takes an optional hash as a parameter, currently
supported options are:
- `includeId`: `true` if the record's ID should be included in the
JSON representation.
@method toJSON
@param {Object} options
@return {Object} A JSON representation of the object.
*/
toJSON(options) {
// container is for lazy transform lookups
let serializer = this._internalModel.store.serializerFor('-default');
let snapshot = this._internalModel.createSnapshot();
return serializer.serialize(snapshot, options);
},
/**
Fired when the record is ready to be interacted with,
that is either loaded from the server or created locally.
@event ready
*/
ready: null,
/**
Fired when the record is loaded from the server.
@event didLoad
*/
didLoad: null,
/**
Fired when the record is updated.
@event didUpdate
*/
didUpdate: null,
/**
Fired when a new record is commited to the server.
@event didCreate
*/
didCreate: null,
/**
Fired when the record is deleted.
@event didDelete
*/
didDelete: null,
/**
Fired when the record becomes invalid.
@event becameInvalid
*/
becameInvalid: null,
/**
Fired when the record enters the error state.
@event becameError
*/
becameError: null,
/**
Fired when the record is rolled back.
@event rolledBack
*/
rolledBack: null,
//TODO Do we want to deprecate these?
/**
@method send
@private
@param {String} name
@param {Object} context
*/
send(name, context) {
return this._internalModel.send(name, context);
},
/**
@method transitionTo
@private
@param {String} name
*/
transitionTo(name) {
return this._internalModel.transitionTo(name);
},
/**
Marks the record as deleted but does not save it. You must call
`save` afterwards if you want to persist it. You might use this
method if you want to allow the user to still `rollbackAttributes()`
after a delete was made.
Example
```app/routes/model/delete.js
import Route from '@ember/routing/route';
export default Route.extend({
actions: {
softDelete() {
this.get('controller.model').deleteRecord();
},
confirm() {
this.get('controller.model').save();
},
undo() {
this.get('controller.model').rollbackAttributes();
}
}
});
```
@method deleteRecord
*/
deleteRecord() {
this._internalModel.deleteRecord();
},
/**
Same as `deleteRecord`, but saves the record immediately.
Example
```app/routes/model/delete.js
import Route from '@ember/routing/route';
export default Route.extend({
actions: {
delete() {
this.get('controller.model').destroyRecord().then(function() {
controller.transitionToRoute('model.index');
});
}
}
});
```
If you pass an object on the `adapterOptions` property of the options
argument it will be passed to your adapter via the snapshot
```js
record.destroyRecord({ adapterOptions: { subscribe: false } });
```
```app/adapters/post.js
import MyCustomAdapter from './custom-adapter';
export default MyCustomAdapter.extend({
deleteRecord(store, type, snapshot) {
if (snapshot.adapterOptions.subscribe) {
// ...
}
// ...
}
});
```
@method destroyRecord
@param {Object} options
@return {Promise} a promise that will be resolved when the adapter returns
successfully or rejected if the adapter returns with an error.
*/
destroyRecord(options) {
this.deleteRecord();
return this.save(options);
},
/**
Unloads the record from the store. This will not send a delete request
to your server, it just unloads the record from memory.
@method unloadRecord
*/
unloadRecord() {
if (this.isDestroyed) {
return;
}
this._internalModel.unloadRecord();
},
/**
@method _notifyProperties
@private
*/
_notifyProperties(keys) {
// changeProperties defers notifications until after the delegate
// and protects with a try...finally block
// previously used begin...endPropertyChanges but this is private API
changeProperties(() => {
let key;
for (let i = 0, length = keys.length; i < length; i++) {
key = keys[i];
this.notifyPropertyChange(key);
}
});
},
/**
Returns an object, whose keys are changed properties, and value is
an [oldProp, newProp] array.
The array represents the diff of the canonical state with the local state
of the model. Note: if the model is created locally, the canonical state is
empty since the adapter hasn't acknowledged the attributes yet:
Example
```app/models/mascot.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
name: attr('string'),
isAdmin: attr('boolean', {
defaultValue: false
})
});
```
```javascript
let mascot = store.createRecord('mascot');
mascot.changedAttributes(); // {}
mascot.set('name', 'Tomster');
mascot.changedAttributes(); // { name: [undefined, 'Tomster'] }
mascot.set('isAdmin', true);
mascot.changedAttributes(); // { isAdmin: [undefined, true], name: [undefined, 'Tomster'] }
mascot.save().then(function() {
mascot.changedAttributes(); // {}
mascot.set('isAdmin', false);
mascot.changedAttributes(); // { isAdmin: [true, false] }
});
```
@method changedAttributes
@return {Object} an object, whose keys are changed properties,
and value is an [oldProp, newProp] array.
*/
changedAttributes() {
return this._internalModel.changedAttributes();
},
/**
If the model `hasDirtyAttributes` this function will discard any unsaved
changes. If the model `isNew` it will be removed from the store.
Example
```javascript
record.get('name'); // 'Untitled Document'
record.set('name', 'Doc 1');
record.get('name'); // 'Doc 1'
record.rollbackAttributes();
record.get('name'); // 'Untitled Document'
```
@since 1.13.0
@method rollbackAttributes
*/
rollbackAttributes() {
this._internalModel.rollbackAttributes();
if (_canaryFeatures.RECORD_DATA_ERRORS) {
this._markInvalidRequestAsClean();
}
if (_canaryFeatures.REQUEST_SERVICE) {
this._markErrorRequestAsClean();
}
},
/*
@method _createSnapshot
@private
*/
_createSnapshot() {
return this._internalModel.createSnapshot();
},
toStringExtension() {
// the _internalModel guard exists, because some dev-only deprecation code
// (addListener via validatePropertyInjections) invokes toString before the
// object is real.
return this._internalModel && this._internalModel.id;
},
/**
Save the record and persist any changes to the record to an
external source via the adapter.
Example
```javascript
record.set('name', 'Tomster');
record.save().then(function() {
// Success callback
}, function() {
// Error callback
});
```
If you pass an object using the `adapterOptions` property of the options
argument it will be passed to your adapter via the snapshot.
```js
record.save({ adapterOptions: { subscribe: false } });
```
```app/adapters/post.js
import MyCustomAdapter from './custom-adapter';
export default MyCustomAdapter.extend({
updateRecord(store, type, snapshot) {
if (snapshot.adapterOptions.subscribe) {
// ...
}
// ...
}
});
```
@method save
@param {Object} options
@return {Promise} a promise that will be resolved when the adapter returns
successfully or rejected if the adapter returns with an error.
*/
save(options) {
return _promiseProxies.PromiseObject.create({
promise: this._internalModel.save(options).then(() => this)
});
},
/**
Reload the record from the adapter.
This will only work if the record has already finished loading.
Example
```app/routes/model/view.js
import Route from '@ember/routing/route';
export default Route.extend({
actions: {
reload() {
this.controller.get('model').reload().then(function(model) {
// do something with the reloaded model
});
}
}
});
```
@method reload
@param {Object} options optional, may include `adapterOptions` hash which will be passed to adapter request
@return {Promise} a promise that will be resolved with the record when the
adapter returns successfully or rejected if the adapter returns
with an error.
*/
reload(options) {
let wrappedAdapterOptions;
if (typeof options === 'object' && options !== null && options.adapterOptions) {
wrappedAdapterOptions = {
adapterOptions: options.adapterOptions
};
}
return _promiseProxies.PromiseObject.create({
promise: this._internalModel.reload(wrappedAdapterOptions).then(() => this)
});
},
/**
Override the default event firing from Ember.Evented to
also call methods with the given name.
@method trigger
@private
@param {String} name
*/
trigger(name) {
let fn = this[name];
if (typeof fn === 'function') {
let length = arguments.length;
let args = new Array(length - 1);
for (let i = 1; i < length; i++) {
args[i - 1] = arguments[i];
}
fn.apply(this, args);
}
const _hasEvent = true
/* DEBUG */
? this._has(name) : this.has(name);
if (_hasEvent) {
this._super(...arguments);
}
},
attr() {
(true && Ember.assert('The `attr` method is not available on Model, a Snapshot was probably expected. Are you passing a Model instead of a Snapshot to your serializer?', false));
},
/**
Get the reference for the specified belongsTo relationship.
Example
```app/models/blog.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo({ async: true })
});
```
```javascript
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
data: { type: 'user', id: 1 }
}
}
}
});
let userRef = blog.belongsTo('user');
// check if the user relationship is loaded
let isLoaded = userRef.value() !== null;
// get the record of the reference (null if not yet available)
let user = userRef.value();
// get the identifier of the reference
if (userRef.remoteType() === "id") {
let id = userRef.id();
} else if (userRef.remoteType() === "link") {
let link = userRef.link();
}
// load user (via store.findRecord or store.findBelongsTo)
userRef.load().then(...)
// or trigger a reload
userRef.reload().then(...)
// provide data for reference
userRef.push({
type: 'user',
id: 1,
attributes: {
username: "@user"
}
}).then(function(user) {
userRef.value() === user;
});
```
@method belongsTo
@param {String} name of the relationship
@since 2.5.0
@return {BelongsToReference} reference for this relationship
*/
belongsTo(name) {
return this._internalModel.referenceFor('belongsTo', name);
},
/**
Get the reference for the specified hasMany relationship.
Example
```app/models/blog.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
comments: {
data: [
{ type: 'comment', id: 1 },
{ type: 'comment', id: 2 }
]
}
}
}
});
let commentsRef = blog.hasMany('comments');
// check if the comments are loaded already
let isLoaded = commentsRef.value() !== null;
// get the records of the reference (null if not yet available)
let comments = commentsRef.value();
// get the identifier of the reference
if (commentsRef.remoteType() === "ids") {
let ids = commentsRef.ids();
} else if (commentsRef.remoteType() === "link") {
let link = commentsRef.link();
}
// load comments (via store.findMany or store.findHasMany)
commentsRef.load().then(...)
// or trigger a reload
commentsRef.reload().then(...)
// provide data for reference
commentsRef.push([{ type: 'comment', id: 1 }, { type: 'comment', id: 2 }]).then(function(comments) {
commentsRef.value() === comments;
});
```
@method hasMany
@param {String} name of the relationship
@since 2.5.0
@return {HasManyReference} reference for this relationship
*/
hasMany(name) {
return this._internalModel.referenceFor('hasMany', name);
},
/**
Provides info about the model for debugging purposes
by grouping the properties into more semantic groups.
Meant to be used by debugging tools such as the Chrome Ember Extension.
- Groups all attributes in "Attributes" group.
- Groups all belongsTo relationships in "Belongs To" group.
- Groups all hasMany relationships in "Has Many" group.
- Groups all flags in "Flags" group.
- Flags relationship CPs as expensive properties.
@method _debugInfo
@for Model
@private
*/
_debugInfo() {
let attributes = ['id'];
let relationships = {};
let expensiveProperties = [];
this.eachAttribute((name, meta) => attributes.push(name));
let groups = [{
name: 'Attributes',
properties: attributes,
expand: true
}];
this.eachRelationship((name, relationship) => {
let properties = relationships[relationship.kind];
if (properties === undefined) {
properties = relationships[relationship.kind] = [];
groups.push({
name: relationship.name,
properties,
expand: true
});
}
properties.push(name);
expensiveProperties.push(name);
});
groups.push({
name: 'Flags',
properties: ['isLoaded', 'hasDirtyAttributes', 'isSaving', 'isDeleted', 'isError', 'isNew', 'isValid']
});
return {
propertyInfo: {
// include all other mixins / properties (not just the grouped ones)
includeOtherProperties: true,
groups: groups,
// don't pre-calculate unless cached
expensiveProperties: expensiveProperties
}
};
},
notifyBelongsToChange(key) {
this.notifyPropertyChange(key);
},
/**
Given a callback, iterates over each of the relationships in the model,
invoking the callback with the name of each relationship and its relationship
descriptor.
The callback method you provide should have the following signature (all
parameters are optional):
```javascript
function(name, descriptor);
```
- `name` the name of the current property in the iteration
- `descriptor` the meta object that describes this relationship
The relationship descriptor argument is an object with the following properties.
- **key** String the name of this relationship on the Model
- **kind** String "hasMany" or "belongsTo"
- **options** Object the original options hash passed when the relationship was declared
- **parentType** Model the type of the Model that owns this relationship
- **type** String the type name of the related Model
Note that in addition to a callback, you can also pass an optional target
object that will be set as `this` on the context.
Example
```app/serializers/application.js
import JSONSerializer from '@ember-data/serializer/json';
export default JSONSerializer.extend({
serialize: function(record, options) {
let json = {};
record.eachRelationship(function(name, descriptor) {
if (descriptor.kind === 'hasMany') {
let serializedHasManyName = name.toUpperCase() + '_IDS';
json[serializedHasManyName] = record.get(name).mapBy('id');
}
});
return json;
}
});
```
@method eachRelationship
@param {Function} callback the callback to invoke
@param {any} binding the value to which the callback's `this` should be bound
*/
eachRelationship(callback, binding) {
this.constructor.eachRelationship(callback, binding);
},
relationshipFor(name) {
return Ember.get(this.constructor, 'relationshipsByName').get(name);
},
inverseFor(key) {
return this.constructor.inverseFor(key, this._internalModel.store);
},
notifyHasManyAdded(key) {
//We need to notifyPropertyChange in the adding case because we need to make sure
//we fetch the newly added record in case it is unloaded
//TODO(Igor): Consider whether we could do this only if the record state is unloaded
this.notifyPropertyChange(key);
},
eachAttribute(callback, binding) {
this.constructor.eachAttribute(callback, binding);
}
});
/**
@property data
@private
@deprecated
@type {Object}
*/
Object.defineProperty(Model.prototype, 'data', {
configurable: false,
get() {
(true && !(false) && Ember.deprecate("Model.data was private and it's use has been deprecated. For public access, use the RecordData API or iterate attributes", false, {
id: 'ember-data:Model.data',
until: '3.9'
}));
return (0, _recordDataFor.default)(this)._data;
}
});
const ID_DESCRIPTOR = {
configurable: false,
set(id) {
const normalizedId = (0, _coerceId.default)(id);
if (normalizedId !== null) {
this._internalModel.setId(normalizedId);
}
},
get() {
// the _internalModel guard exists, because some dev-only deprecation code
// (addListener via validatePropertyInjections) invokes toString before the
// object is real.
return this._internalModel && this._internalModel.id;
}
};
Object.defineProperty(Model.prototype, 'id', ID_DESCRIPTOR);
if (true
/* DEBUG */
) {
let lookupDescriptor = function lookupDescriptor(obj, keyName) {
let current = obj;
do {
let descriptor = Object.getOwnPropertyDescriptor(current, keyName);
if (descriptor !== undefined) {
return descriptor;
}
current = Object.getPrototypeOf(current);
} while (current !== null);
return null;
};
let isBasicDesc = function isBasicDesc(desc) {
return !desc || !desc.get && !desc.set && desc.enumerable === true && desc.writable === true && desc.configurable === true;
};
let isDefaultEmptyDescriptor = function isDefaultEmptyDescriptor(obj, keyName) {
let instanceDesc = lookupDescriptor(obj, keyName);
return isBasicDesc(instanceDesc) && lookupDescriptor(obj.constructor, keyName) === null;
};
const INSTANCE_DEPRECATIONS = new WeakMap();
const DEPRECATED_LIFECYCLE_EVENT_METHODS = ['becameError', 'becameInvalid', 'didCreate', 'didDelete', 'didLoad', 'didUpdate', 'ready', 'rolledBack'];
let lookupDeprecations = function lookupInstanceDeprecations(instance) {
let deprecations = INSTANCE_DEPRECATIONS.get(instance);
if (!deprecations) {
deprecations = new Set();
INSTANCE_DEPRECATIONS.set(instance, deprecations);
}
return deprecations;
};
Model.reopen({
init() {
this._super(...arguments);
this._getDeprecatedEventedInfo = () => "".concat(this._internalModel.modelName, "#").concat(this.id);
if (!isDefaultEmptyDescriptor(this, '_internalModel') || !(this._internalModel instanceof _internalModel.default)) {
throw new Error("'_internalModel' is a reserved property name on instances of classes extending Model. Please choose a different property name for ".concat(this.constructor.toString()));
}
if (!isDefaultEmptyDescriptor(this, 'currentState') || this.get('currentState') !== this._internalModel.currentState) {
throw new Error("'currentState' is a reserved property name on instances of classes extending Model. Please choose a different property name for ".concat(this.constructor.toString()));
}
let idDesc = lookupDescriptor(this, 'id');
if (idDesc.get !== ID_DESCRIPTOR.get) {
throw new Ember.Error("You may not set 'id' as an attribute on your model. Please remove any lines that look like: `id: DS.attr('')` from ".concat(this.constructor.toString()));
}
let lifecycleDeprecations = lookupDeprecations(this.constructor);
DEPRECATED_LIFECYCLE_EVENT_METHODS.forEach(methodName => {
if (typeof this[methodName] === 'function' && !lifecycleDeprecations.has(methodName)) {
(true && !(false) && Ember.deprecate("You defined a `".concat(methodName, "` method for ").concat(this.constructor.toString(), " but lifecycle events for models have been deprecated."), false, {
id: 'ember-data:record-lifecycle-event-methods',
until: '4.0',
url: 'https://deprecations.emberjs.com/ember-data/v3.x#toc_record-lifecycle-event-methods'
}));
lifecycleDeprecations.add(methodName);
}
});
}
});
}
Model.reopenClass({
isModel: true,
/**
Create should only ever be called by the store. To create an instance of a
`Model` in a dirty state use `store.createRecord`.
To create instances of `Model` in a clean state, use `store.push`
@method create
@private
@static
*/
/**
Represents the model's class name as a string. This can be used to look up the model's class name through
`Store`'s modelFor method.
`modelName` is generated for you by Ember Data. It will be a lowercased, dasherized string.
For example:
```javascript
store.modelFor('post').modelName; // 'post'
store.modelFor('blog-post').modelName; // 'blog-post'
```
The most common place you'll want to access `modelName` is in your serializer's `payloadKeyFromModelName` method. For example, to change payload
keys to underscore (instead of dasherized), you might use the following code:
```javascript
import RESTSerializer from '@ember-data/serializer/rest';
import { underscore } from '@ember/string';
export default const PostSerializer = RESTSerializer.extend({
payloadKeyFromModelName(modelName) {
return underscore(modelName);
}
});
```
@property modelName
@type String
@readonly
@static
*/
modelName: null,
/*
These class methods below provide relationship
introspection abilities about relationships.
A note about the computed properties contained here:
**These properties are effectively sealed once called for the first time.**
To avoid repeatedly doing expensive iteration over a model's fields, these
values are computed once and then cached for the remainder of the runtime of
your application.
If your application needs to modify a class after its initial definition
(for example, using `reopen()` to add additional attributes), make sure you
do it before using your model with the store, which uses these properties
extensively.
*/
/**
For a given relationship name, returns the model type of the relationship.
For example, if you define a model like this:
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany('comment')
});
```
Calling `store.modelFor('post').typeForRelationship('comments', store)` will return `Comment`.
@method typeForRelationship
@static
@param {String} name the name of the relationship
@param {store} store an instance of Store
@return {Model} the type of the relationship, or undefined
*/
typeForRelationship(name, store) {
let relationship = Ember.get(this, 'relationshipsByName').get(name);
return relationship && store.modelFor(relationship.type);
},
inverseMap: Ember.computed(function () {
return Object.create(null);
}),
/**
Find the relationship which is the inverse of the one asked for.
For example, if you define models like this:
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany('message')
});
```
```app/models/message.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
owner: belongsTo('post')
});
```
``` js
store.modelFor('post').inverseFor('comments', store) // { type: App.Message, name: 'owner', kind: 'belongsTo' }
store.modelFor('message').inverseFor('owner', store) // { type: App.Post, name: 'comments', kind: 'hasMany' }
```
@method inverseFor
@static
@param {String} name the name of the relationship
@param {Store} store
@return {Object} the inverse relationship, or null
*/
inverseFor(name, store) {
let inverseMap = Ember.get(this, 'inverseMap');
if (inverseMap[name]) {
return inverseMap[name];
} else {
let inverse = this._findInverseFor(name, store);
inverseMap[name] = inverse;
return inverse;
}
},
//Calculate the inverse, ignoring the cache
_findInverseFor(name, store) {
let inverseType = this.typeForRelationship(name, store);
if (!inverseType) {
return null;
}
let propertyMeta = this.metaForProperty(name); //If inverse is manually specified to be null, like `comments: hasMany('message', { inverse: null })`
let options = propertyMeta.options;
if (options.inverse === null) {
return null;
}
let inverseName, inverseKind, inverse, inverseOptions; //If inverse is specified manually, return the inverse
if (options.inverse) {
inverseName = options.inverse;
inverse = Ember.get(inverseType, 'relationshipsByName').get(inverseName);
(true && Ember.assert("We found no inverse relationships by the name of '" + inverseName + "' on the '" + inverseType.modelName + "' model. This is most likely due to a missing attribute on your model definition.", !Ember.isNone(inverse))); // TODO probably just return the whole inverse here
inverseKind = inverse.kind;
inverseOptions = inverse.options;
} else {
//No inverse was specified manually, we need to use a heuristic to guess one
if (propertyMeta.type === propertyMeta.parentModelName) {
(true && Ember.warn("Detected a reflexive relationship by the name of '".concat(name, "' without an inverse option. Look at https://guides.emberjs.com/current/models/relationships/#toc_reflexive-relations for how to explicitly specify inverses."), false, {
id: 'ds.model.reflexive-relationship-without-inverse'
}));
}
let possibleRelationships = findPossibleInverses(this, inverseType, name);
if (possibleRelationships.length === 0) {
return null;
}
let filteredRelationships = possibleRelationships.filter(possibleRelationship => {
let optionsForRelationship = inverseType.metaForProperty(possibleRelationship.name).options;
return name === optionsForRelationship.inverse;
});
(true && Ember.assert("You defined the '" + name + "' relationship on " + this + ', but you defined the inverse relationships of type ' + inverseType.toString() + ' multiple times. Look at https://guides.emberjs.com/current/models/relationships/#toc_explicit-inverses for how to explicitly specify inverses', filteredRelationships.length < 2));
if (filteredRelationships.length === 1) {
possibleRelationships = filteredRelationships;
}
(true && Ember.assert("You defined the '" + name + "' relationship on " + this + ', but multiple possible inverse relationships of type ' + this + ' were found on ' + inverseType + '. Look at https://guides.emberjs.com/current/models/relationships/#toc_explicit-inverses for how to explicitly specify inverses', possibleRelationships.length === 1));
inverseName = possibleRelationships[0].name;
inverseKind = possibleRelationships[0].kind;
inverseOptions = possibleRelationships[0].options;
}
(true && Ember.assert("The ".concat(inverseType.modelName, ":").concat(inverseName, " relationship declares 'inverse: null', but it was resolved as the inverse for ").concat(this.modelName, ":").concat(name, "."), !inverseOptions || inverseOptions.inverse !== null));
return {
type: inverseType,
name: inverseName,
kind: inverseKind,
options: inverseOptions
};
},
/**
The model's relationships as a map, keyed on the type of the
relationship. The value of each entry is an array containing a descriptor
for each relationship with that type, describing the name of the relationship
as well as the type.
For example, given the following model definition:
```app/models/blog.js
import Model, { belongsTo, hasMany } from '@ember-data/model';
export default Model.extend({
users: hasMany('user'),
owner: belongsTo('user'),
posts: hasMany('post')
});
```
This computed property would return a map describing these
relationships, like this:
```javascript
import Ember from 'ember';
import Blog from 'app/models/blog';
import User from 'app/models/user';
import Post from 'app/models/post';
let relationships = Ember.get(Blog, 'relationships');
relationships.get('user');
//=> [ { name: 'users', kind: 'hasMany' },
// { name: 'owner', kind: 'belongsTo' } ]
relationships.get('post');
//=> [ { name: 'posts', kind: 'hasMany' } ]
```
@property relationships
@static
@type Map
@readOnly
*/
relationships: _ext.relationshipsDescriptor,
/**
A hash containing lists of the model's relationships, grouped
by the relationship kind. For example, given a model with this
definition:
```app/models/blog.js
import Model, { belongsTo, hasMany } from '@ember-data/model';
export default Model.extend({
users: hasMany('user'),
owner: belongsTo('user'),
posts: hasMany('post')
});
```
This property would contain the following:
```javascript
import Ember from 'ember';
import Blog from 'app/models/blog';
let relationshipNames = Ember.get(Blog, 'relationshipNames');
relationshipNames.hasMany;
//=> ['users', 'posts']
relationshipNames.belongsTo;
//=> ['owner']
```
@property relationshipNames
@static
@type Object
@readOnly
*/
relationshipNames: Ember.computed(function () {
let names = {
hasMany: [],
belongsTo: []
};
this.eachComputedProperty((name, meta) => {
if (meta.isRelationship) {
names[meta.kind].push(name);
}
});
return names;
}),
/**
An array of types directly related to a model. Each type will be
included once, regardless of the number of relationships it has with
the model.
For example, given a model with this definition:
```app/models/blog.js
import Model, { belongsTo, hasMany } from '@ember-data/model';
export default Model.extend({
users: hasMany('user'),
owner: belongsTo('user'),
posts: hasMany('post')
});
```
This property would contain the following:
```javascript
import Ember from 'ember';
import Blog from 'app/models/blog';
let relatedTypes = Ember.get(Blog, 'relatedTypes');
//=> [ User, Post ]
```
@property relatedTypes
@static
@type Ember.Array
@readOnly
*/
relatedTypes: _ext.relatedTypesDescriptor,
/**
A map whose keys are the relationships of a model and whose values are
relationship descriptors.
For example, given a model with this
definition:
```app/models/blog.js
import Model, { belongsTo, hasMany } from '@ember-data/model';
export default Model.extend({
users: hasMany('user'),
owner: belongsTo('user'),
posts: hasMany('post')
});
```
This property would contain the following:
```javascript
import Ember from 'ember';
import Blog from 'app/models/blog';
let relationshipsByName = Ember.get(Blog, 'relationshipsByName');
relationshipsByName.get('users');
//=> { key: 'users', kind: 'hasMany', type: 'user', options: Object, isRelationship: true }
relationshipsByName.get('owner');
//=> { key: 'owner', kind: 'belongsTo', type: 'user', options: Object, isRelationship: true }
```
@property relationshipsByName
@static
@type Map
@readOnly
*/
relationshipsByName: _ext.relationshipsByNameDescriptor,
relationshipsObject: _ext.relationshipsObjectDescriptor,
/**
A map whose keys are the fields of the model and whose values are strings
describing the kind of the field. A model's fields are the union of all of its
attributes and relationships.
For example:
```app/models/blog.js
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
export default Model.extend({
users: hasMany('user'),
owner: belongsTo('user'),
posts: hasMany('post'),
title: attr('string')
});
```
```js
import Ember from 'ember';
import Blog from 'app/models/blog';
let fields = Ember.get(Blog, 'fields');
fields.forEach(function(kind, field) {
console.log(field, kind);
});
// prints:
// users, hasMany
// owner, belongsTo
// posts, hasMany
// title, attribute
```
@property fields
@static
@type Map
@readOnly
*/
fields: Ember.computed(function () {
let map = new Map();
this.eachComputedProperty((name, meta) => {
if (meta.isRelationship) {
map.set(name, meta.kind);
} else if (meta.isAttribute) {
map.set(name, 'attribute');
}
});
return map;
}).readOnly(),
/**
Given a callback, iterates over each of the relationships in the model,
invoking the callback with the name of each relationship and its relationship
descriptor.
@method eachRelationship
@static
@param {Function} callback the callback to invoke
@param {any} binding the value to which the callback's `this` should be bound
*/
eachRelationship(callback, binding) {
Ember.get(this, 'relationshipsByName').forEach((relationship, name) => {
callback.call(binding, name, relationship);
});
},
/**
Given a callback, iterates over each of the types related to a model,
invoking the callback with the related type's class. Each type will be
returned just once, regardless of how many different relationships it has
with a model.
@method eachRelatedType
@static
@param {Function} callback the callback to invoke
@param {any} binding the value to which the callback's `this` should be bound
*/
eachRelatedType(callback, binding) {
let relationshipTypes = Ember.get(this, 'relatedTypes');
for (let i = 0; i < relationshipTypes.length; i++) {
let type = relationshipTypes[i];
callback.call(binding, type);
}
},
determineRelationshipType(knownSide, store) {
let knownKey = knownSide.key;
let knownKind = knownSide.kind;
let inverse = this.inverseFor(knownKey, store); // let key;
let otherKind;
if (!inverse) {
return knownKind === 'belongsTo' ? 'oneToNone' : 'manyToNone';
} // key = inverse.name;
otherKind = inverse.kind;
if (otherKind === 'belongsTo') {
return knownKind === 'belongsTo' ? 'oneToOne' : 'manyToOne';
} else {
return knownKind === 'belongsTo' ? 'oneToMany' : 'manyToMany';
}
},
/**
A map whose keys are the attributes of the model (properties
described by attr) and whose values are the meta object for the
property.
Example
```app/models/person.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
firstName: attr('string'),
lastName: attr('string'),
birthday: attr('date')
});
```
```javascript
import Ember from 'ember';
import Person from 'app/models/person';
let attributes = Ember.get(Person, 'attributes')
attributes.forEach(function(meta, name) {
console.log(name, meta);
});
// prints:
// firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"}
// lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"}
// birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"}
```
@property attributes
@static
@type {Map}
@readOnly
*/
attributes: Ember.computed(function () {
let map = new Map();
this.eachComputedProperty((name, meta) => {
if (meta.isAttribute) {
(true && Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: attr('')` from " + this.toString(), name !== 'id'));
meta.name = name;
map.set(name, meta);
}
});
return map;
}).readOnly(),
/**
A map whose keys are the attributes of the model (properties
described by attr) and whose values are type of transformation
applied to each attribute. This map does not include any
attributes that do not have an transformation type.
Example
```app/models/person.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
firstName: attr(),
lastName: attr('string'),
birthday: attr('date')
});
```
```javascript
import Ember from 'ember';
import Person from 'app/models/person';
let transformedAttributes = Ember.get(Person, 'transformedAttributes')
transformedAttributes.forEach(function(field, type) {
console.log(field, type);
});
// prints:
// lastName string
// birthday date
```
@property transformedAttributes
@static
@type {Map}
@readOnly
*/
transformedAttributes: Ember.computed(function () {
let map = new Map();
this.eachAttribute((key, meta) => {
if (meta.type) {
map.set(key, meta.type);
}
});
return map;
}).readOnly(),
/**
Iterates through the attributes of the model, calling the passed function on each
attribute.
The callback method you provide should have the following signature (all
parameters are optional):
```javascript
function(name, meta);
```
- `name` the name of the current property in the iteration
- `meta` the meta object for the attribute property in the iteration
Note that in addition to a callback, you can also pass an optional target
object that will be set as `this` on the context.
Example
```javascript
import Model, { attr } from '@ember-data/model';
let Person = Model.extend({
firstName: attr('string'),
lastName: attr('string'),
birthday: attr('date')
});
Person.eachAttribute(function(name, meta) {
console.log(name, meta);
});
// prints:
// firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"}
// lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"}
// birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"}
```
@method eachAttribute
@param {Function} callback The callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
@static
*/
eachAttribute(callback, binding) {
Ember.get(this, 'attributes').forEach((meta, name) => {
callback.call(binding, name, meta);
});
},
/**
Iterates through the transformedAttributes of the model, calling
the passed function on each attribute. Note the callback will not be
called for any attributes that do not have an transformation type.
The callback method you provide should have the following signature (all
parameters are optional):
```javascript
function(name, type);
```
- `name` the name of the current property in the iteration
- `type` a string containing the name of the type of transformed
applied to the attribute
Note that in addition to a callback, you can also pass an optional target
object that will be set as `this` on the context.
Example
```javascript
import Model, { attr } from '@ember-data/model';
let Person = Model.extend({
firstName: attr(),
lastName: attr('string'),
birthday: attr('date')
});
Person.eachTransformedAttribute(function(name, type) {
console.log(name, type);
});
// prints:
// lastName string
// birthday date
```
@method eachTransformedAttribute
@param {Function} callback The callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
@static
*/
eachTransformedAttribute(callback, binding) {
Ember.get(this, 'transformedAttributes').forEach((type, name) => {
callback.call(binding, name, type);
});
},
/**
Returns the name of the model class.
@method toString
@static
*/
toString() {
return "model:".concat(Ember.get(this, 'modelName'));
}
});
var _default = Model;
_exports.default = _default;
});
define("@ember-data/store/-private/system/model/record-data", ["exports", "@ember-data/store/-private/system/relationships/state/create", "@ember-data/store/-private/system/coerce-id", "@ember-data/canary-features"], function (_exports, _create, _coerceId, _canaryFeatures) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
let nextBfsId = 1;
class RecordDataDefault {
/**
* @deprecated
*/
constructor(arg1, arg2) {
_defineProperty(this, "_errors", void 0);
_defineProperty(this, "__relationships", void 0);
_defineProperty(this, "__implicitRelationships", void 0);
_defineProperty(this, "modelName", void 0);
_defineProperty(this, "clientId", void 0);
_defineProperty(this, "id", void 0);
_defineProperty(this, "isDestroyed", void 0);
_defineProperty(this, "_isNew", void 0);
_defineProperty(this, "_bfsId", void 0);
_defineProperty(this, "__attributes", void 0);
_defineProperty(this, "__inFlightAttributes", void 0);
_defineProperty(this, "__data", void 0);
_defineProperty(this, "_scheduledDestroy", void 0);
_defineProperty(this, "_isDeleted", void 0);
_defineProperty(this, "_isDeletionCommited", void 0);
_defineProperty(this, "identifier", void 0);
_defineProperty(this, "storeWrapper", void 0);
if (_canaryFeatures.IDENTIFIERS) {
const [identifier, storeWrapper] = arguments;
this.identifier = identifier;
this.modelName = identifier.type;
this.clientId = identifier.lid;
this.id = identifier.id;
this.storeWrapper = storeWrapper;
} else {
const [modelName, id, clientId, storeWrapper] = arguments;
this.modelName = modelName;
this.clientId = clientId;
this.id = id;
this.storeWrapper = storeWrapper;
}
this.__relationships = null;
this.__implicitRelationships = null;
this.isDestroyed = false;
this._isNew = false; // Used during the mark phase of unloading to avoid checking the same internal
// model twice in the same scan
this._bfsId = 0;
this.reset();
} // PUBLIC API
getResourceIdentifier() {
return _canaryFeatures.IDENTIFIERS ? this.identifier : {
id: this.id,
type: this.modelName,
lid: this.clientId,
clientId: this.clientId
};
}
pushData(data, calculateChange) {
let changedKeys;
if (this._isNew) {
this._isNew = false;
this.notifyStateChange();
}
if (calculateChange) {
changedKeys = this._changedKeys(data.attributes);
}
Ember.assign(this._data, data.attributes);
if (this.__attributes) {
// only do if we have attribute changes
this._updateChangedAttributes();
}
if (data.relationships) {
this._setupRelationships(data);
}
if (data.id) {
this.id = (0, _coerceId.default)(data.id);
}
return changedKeys;
}
willCommit() {
this._inFlightAttributes = this._attributes;
this._attributes = null;
}
hasChangedAttributes() {
return this.__attributes !== null && Object.keys(this.__attributes).length > 0;
}
_clearErrors() {
if (_canaryFeatures.RECORD_DATA_ERRORS) {
if (this._errors) {
this._errors = undefined;
this.storeWrapper.notifyErrorsChange(this.modelName, this.id, this.clientId);
}
}
}
getErrors() {
(true && Ember.assert('Can not call getErrors unless the RECORD_DATA_ERRORS feature flag is on', _canaryFeatures.RECORD_DATA_ERRORS));
if (_canaryFeatures.RECORD_DATA_ERRORS) {
let errors = this._errors || [];
return errors;
} else {
return [];
}
} // this is a hack bc we don't have access to the state machine
// and relationships need this info and @runspired didn't see
// how to get it just yet from storeWrapper.
isEmpty() {
return this.__attributes === null && this.__inFlightAttributes === null && this.__data === null;
}
deleteRecord() {
this._isDeleted = true;
this.notifyStateChange();
}
isDeleted() {
return this._isDeleted;
}
setIsDeleted(isDeleted) {
this._isDeleted = isDeleted;
if (this._isNew) {
this._deletionConfirmed();
}
this.notifyStateChange();
}
isDeletionCommitted() {
return this._isDeletionCommited;
}
reset() {
this.__attributes = null;
this.__inFlightAttributes = null;
this.__data = null;
this._errors = undefined;
}
_setupRelationships(data) {
let relationships = this.storeWrapper.relationshipsDefinitionFor(this.modelName);
let keys = Object.keys(relationships);
for (let i = 0; i < keys.length; i++) {
let relationshipName = keys[i];
if (!data.relationships[relationshipName]) {
continue;
} // in debug, assert payload validity eagerly
let relationshipData = data.relationships[relationshipName];
if (true
/* DEBUG */
) {
let storeWrapper = this.storeWrapper;
let recordData = this;
let relationshipMeta = relationships[relationshipName];
if (!relationshipData || !relationshipMeta) {
continue;
}
if (relationshipData.links) {
let isAsync = relationshipMeta.options && relationshipMeta.options.async !== false;
let relationship = this._relationships.get(relationshipName);
(true && Ember.warn("You pushed a record of type '".concat(this.modelName, "' with a relationship '").concat(relationshipName, "' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. EmberData will treat this relationship as known-to-be-empty."), isAsync || relationshipData.data || relationship.hasAnyRelationshipData, {
id: 'ds.store.push-link-for-sync-relationship'
}));
} else if (relationshipData.data) {
if (relationshipMeta.kind === 'belongsTo') {
(true && Ember.assert("A ".concat(this.modelName, " record was pushed into the store with the value of ").concat(relationshipName, " being ").concat(Ember.inspect(relationshipData.data), ", but ").concat(relationshipName, " is a belongsTo relationship so the value must not be an array. You should probably check your data payload or serializer."), !Array.isArray(relationshipData.data)));
assertRelationshipData(storeWrapper, recordData, relationshipData.data, relationshipMeta);
} else if (relationshipMeta.kind === 'hasMany') {
(true && Ember.assert("A ".concat(this.modelName, " record was pushed into the store with the value of ").concat(relationshipName, " being '").concat(Ember.inspect(relationshipData.data), "', but ").concat(relationshipName, " is a hasMany relationship so the value must be an array. You should probably check your data payload or serializer."), Array.isArray(relationshipData.data)));
if (Array.isArray(relationshipData.data)) {
for (let i = 0; i < relationshipData.data.length; i++) {
assertRelationshipData(storeWrapper, recordData, relationshipData.data[i], relationshipMeta);
}
}
}
}
}
let relationship = this._relationships.get(relationshipName);
relationship.push(relationshipData);
}
}
/*
Checks if the attributes which are considered as changed are still
different to the state which is acknowledged by the server.
This method is needed when data for the internal model is pushed and the
pushed data might acknowledge dirty attributes as confirmed.
@method updateChangedAttributes
@private
*/
_updateChangedAttributes() {
let changedAttributes = this.changedAttributes();
let changedAttributeNames = Object.keys(changedAttributes);
let attrs = this._attributes;
for (let i = 0, length = changedAttributeNames.length; i < length; i++) {
let attribute = changedAttributeNames[i];
let data = changedAttributes[attribute];
let oldData = data[0];
let newData = data[1];
if (oldData === newData) {
delete attrs[attribute];
}
}
}
/*
Returns an object, whose keys are changed properties, and value is an
[oldProp, newProp] array.
@method changedAttributes
@private
*/
changedAttributes() {
let oldData = this._data;
let currentData = this._attributes;
let inFlightData = this._inFlightAttributes;
let newData = Ember.assign({}, inFlightData, currentData);
let diffData = Object.create(null);
let newDataKeys = Object.keys(newData);
for (let i = 0, length = newDataKeys.length; i < length; i++) {
let key = newDataKeys[i];
diffData[key] = [oldData[key], newData[key]];
}
return diffData;
}
isNew() {
return this._isNew;
}
rollbackAttributes() {
let dirtyKeys;
this._isDeleted = false;
if (this.hasChangedAttributes()) {
dirtyKeys = Object.keys(this._attributes);
this._attributes = null;
}
if (this.isNew()) {
this.removeFromInverseRelationships(true);
this._isDeleted = true;
this._isNew = false;
}
this._inFlightAttributes = null;
this._clearErrors();
this.notifyStateChange();
return dirtyKeys;
}
_deletionConfirmed() {
this.removeFromInverseRelationships();
}
didCommit(data) {
if (this._isDeleted) {
this._deletionConfirmed();
this._isDeletionCommited = true;
}
this._isNew = false;
let newCanonicalAttributes = null;
if (data) {
// this.store._internalModelDidReceiveRelationshipData(this.modelName, this.id, data.relationships);
if (data.relationships) {
this._setupRelationships(data);
}
if (data.id) {
// didCommit provided an ID, notify the store of it
this.storeWrapper.setRecordId(this.modelName, data.id, this.clientId);
this.id = (0, _coerceId.default)(data.id);
}
newCanonicalAttributes = data.attributes || null;
}
let changedKeys = this._changedKeys(newCanonicalAttributes);
Ember.assign(this._data, this.__inFlightAttributes, newCanonicalAttributes);
this._inFlightAttributes = null;
this._updateChangedAttributes();
this._clearErrors();
this.notifyStateChange();
return changedKeys;
}
notifyStateChange() {
if (_canaryFeatures.RECORD_DATA_STATE) {
this.storeWrapper.notifyStateChange(this.modelName, this.id, this.clientId);
}
} // get ResourceIdentifiers for "current state"
getHasMany(key) {
return this._relationships.get(key).getData();
} // set a new "current state" via ResourceIdentifiers
setDirtyHasMany(key, recordDatas) {
let relationship = this._relationships.get(key);
relationship.clear();
relationship.addRecordDatas(recordDatas);
} // append to "current state" via RecordDatas
addToHasMany(key, recordDatas, idx) {
this._relationships.get(key).addRecordDatas(recordDatas, idx);
} // remove from "current state" via RecordDatas
removeFromHasMany(key, recordDatas) {
this._relationships.get(key).removeRecordDatas(recordDatas);
}
commitWasRejected(identifier, errors) {
let keys = Object.keys(this._inFlightAttributes);
if (keys.length > 0) {
let attrs = this._attributes;
for (let i = 0; i < keys.length; i++) {
if (attrs[keys[i]] === undefined) {
attrs[keys[i]] = this._inFlightAttributes[keys[i]];
}
}
}
this._inFlightAttributes = null;
if (_canaryFeatures.RECORD_DATA_ERRORS) {
if (errors) {
this._errors = errors;
}
this.storeWrapper.notifyErrorsChange(this.modelName, this.id, this.clientId);
}
}
getBelongsTo(key) {
return this._relationships.get(key).getData();
}
setDirtyBelongsTo(key, recordData) {
this._relationships.get(key).setRecordData(recordData);
}
setDirtyAttribute(key, value) {
let originalValue; // Add the new value to the changed attributes hash
this._attributes[key] = value;
if (key in this._inFlightAttributes) {
originalValue = this._inFlightAttributes[key];
} else {
originalValue = this._data[key];
} // If we went back to our original value, we shouldn't keep the attribute around anymore
if (value === originalValue) {
delete this._attributes[key];
}
}
getAttr(key) {
if (key in this._attributes) {
return this._attributes[key];
} else if (key in this._inFlightAttributes) {
return this._inFlightAttributes[key];
} else {
return this._data[key];
}
}
hasAttr(key) {
return key in this._attributes || key in this._inFlightAttributes || key in this._data;
}
unloadRecord() {
if (this.isDestroyed) {
return;
}
this._destroyRelationships();
this.reset();
if (!this._scheduledDestroy) {
this._scheduledDestroy = Ember.run.backburner.schedule('destroy', this, '_cleanupOrphanedRecordDatas');
}
}
_cleanupOrphanedRecordDatas() {
let relatedRecordDatas = this._allRelatedRecordDatas();
if (areAllModelsUnloaded(relatedRecordDatas)) {
for (let i = 0; i < relatedRecordDatas.length; ++i) {
let recordData = relatedRecordDatas[i];
if (!recordData.isDestroyed) {
recordData.destroy();
}
}
}
this._scheduledDestroy = null;
}
destroy() {
this._relationships.forEach((name, rel) => rel.destroy());
this.isDestroyed = true;
this.storeWrapper.disconnectRecord(this.modelName, this.id, this.clientId);
}
isRecordInUse() {
return this.storeWrapper.isRecordInUse(this.modelName, this.id, this.clientId);
}
/**
Computes the set of internal models reachable from `this` across exactly one
relationship.
@return {Array} An array containing the internal models that `this` belongs
to or has many.
*/
_directlyRelatedRecordDatas() {
let array = [];
this._relationships.forEach((name, rel) => {
let members = rel.members.list;
let canonicalMembers = rel.canonicalMembers.list;
array = array.concat(members, canonicalMembers);
});
return array;
}
/**
Computes the set of internal models reachable from this internal model.
Reachability is determined over the relationship graph (ie a graph where
nodes are internal models and edges are belongs to or has many
relationships).
@return {Array} An array including `this` and all internal models reachable
from `this`.
*/
_allRelatedRecordDatas() {
let array = [];
let queue = [];
let bfsId = nextBfsId++;
queue.push(this);
this._bfsId = bfsId;
while (queue.length > 0) {
let node = queue.shift();
array.push(node);
let related = node._directlyRelatedRecordDatas();
for (let i = 0; i < related.length; ++i) {
let recordData = related[i];
if (recordData instanceof RecordDataDefault) {
(true && Ember.assert('Internal Error: seen a future bfs iteration', recordData._bfsId <= bfsId));
if (recordData._bfsId < bfsId) {
queue.push(recordData);
recordData._bfsId = bfsId;
}
}
}
}
return array;
}
isAttrDirty(key) {
if (this._attributes[key] === undefined) {
return false;
}
let originalValue;
if (this._inFlightAttributes[key] !== undefined) {
originalValue = this._inFlightAttributes[key];
} else {
originalValue = this._data[key];
}
return originalValue !== this._attributes[key];
}
get _attributes() {
if (this.__attributes === null) {
this.__attributes = Object.create(null);
}
return this.__attributes;
}
set _attributes(v) {
this.__attributes = v;
}
get _relationships() {
if (this.__relationships === null) {
this.__relationships = new _create.default(this);
}
return this.__relationships;
}
get _data() {
if (this.__data === null) {
this.__data = Object.create(null);
}
return this.__data;
}
set _data(v) {
this.__data = v;
}
/*
implicit relationships are relationship which have not been declared but the inverse side exists on
another record somewhere
For example if there was
```app/models/comment.js
import Model, { attr } from '@ember-data/model';
export default Model.extend({
name: attr()
});
```
but there is also
```app/models/post.js
import Model, { attr, hasMany } from '@ember-data/model';
export default Model.extend({
name: attr(),
comments: hasMany('comment')
});
```
would have a implicit post relationship in order to be do things like remove ourselves from the post
when we are deleted
*/
get _implicitRelationships() {
if (this.__implicitRelationships === null) {
let relationships = Object.create(null);
this.__implicitRelationships = relationships;
return relationships;
}
return this.__implicitRelationships;
}
get _inFlightAttributes() {
if (this.__inFlightAttributes === null) {
this.__inFlightAttributes = Object.create(null);
}
return this.__inFlightAttributes;
}
set _inFlightAttributes(v) {
this.__inFlightAttributes = v;
}
/**
* Receives options passed to `store.createRecord` and is given the opportunity
* to handle them.
*
* The return value is an object of options to pass to `Record.create()`
*
* @param options
* @private
*/
_initRecordCreateOptions(options) {
let createOptions = {};
if (options !== undefined) {
let {
modelName,
storeWrapper
} = this;
let attributeDefs = storeWrapper.attributesDefinitionFor(modelName);
let relationshipDefs = storeWrapper.relationshipsDefinitionFor(modelName);
let relationships = this._relationships;
let propertyNames = Object.keys(options);
for (let i = 0; i < propertyNames.length; i++) {
let name = propertyNames[i];
let propertyValue = options[name];
if (name === 'id') {
this.id = propertyValue;
continue;
}
let fieldType = relationshipDefs[name] || attributeDefs[name];
let kind = fieldType !== undefined ? fieldType.kind : null;
let relationship;
switch (kind) {
case 'attribute':
this.setDirtyAttribute(name, propertyValue);
break;
case 'belongsTo':
this.setDirtyBelongsTo(name, propertyValue);
relationship = relationships.get(name);
relationship.setHasAnyRelationshipData(true);
relationship.setRelationshipIsEmpty(false);
break;
case 'hasMany':
this.setDirtyHasMany(name, propertyValue);
relationship = relationships.get(name);
relationship.setHasAnyRelationshipData(true);
relationship.setRelationshipIsEmpty(false);
break;
default:
// reflect back (pass-thru) unknown properties
createOptions[name] = propertyValue;
}
}
}
return createOptions;
}
/*
TODO IGOR AND DAVID this shouldn't be public
This method should only be called by records in the `isNew()` state OR once the record
has been deleted and that deletion has been persisted.
It will remove this record from any associated relationships.
If `isNew` is true (default false), it will also completely reset all
relationships to an empty state as well.
@method removeFromInverseRelationships
@param {Boolean} isNew whether to unload from the `isNew` perspective
@private
*/
removeFromInverseRelationships(isNew = false) {
this._relationships.forEach((name, rel) => {
rel.removeCompletelyFromInverse();
if (isNew === true) {
rel.clear();
}
});
let implicitRelationships = this._implicitRelationships;
this.__implicitRelationships = null;
Object.keys(implicitRelationships).forEach(key => {
let rel = implicitRelationships[key];
rel.removeCompletelyFromInverse();
if (isNew === true) {
rel.clear();
}
});
}
_destroyRelationships() {
let relationships = this._relationships;
relationships.forEach((name, rel) => destroyRelationship(rel));
let implicitRelationships = this._implicitRelationships;
this.__implicitRelationships = null;
Object.keys(implicitRelationships).forEach(key => {
let rel = implicitRelationships[key];
destroyRelationship(rel);
});
}
clientDidCreate() {
this._isNew = true;
}
/*
Ember Data has 3 buckets for storing the value of an attribute on an internalModel.
`_data` holds all of the attributes that have been acknowledged by
a backend via the adapter. When rollbackAttributes is called on a model all
attributes will revert to the record's state in `_data`.
`_attributes` holds any change the user has made to an attribute
that has not been acknowledged by the adapter. Any values in
`_attributes` are have priority over values in `_data`.
`_inFlightAttributes`. When a record is being synced with the
backend the values in `_attributes` are copied to
`_inFlightAttributes`. This way if the backend acknowledges the
save but does not return the new state Ember Data can copy the
values from `_inFlightAttributes` to `_data`. Without having to
worry about changes made to `_attributes` while the save was
happenign.
Changed keys builds a list of all of the values that may have been
changed by the backend after a successful save.
It does this by iterating over each key, value pair in the payload
returned from the server after a save. If the `key` is found in
`_attributes` then the user has a local changed to the attribute
that has not been synced with the server and the key is not
included in the list of changed keys.
If the value, for a key differs from the value in what Ember Data
believes to be the truth about the backend state (A merger of the
`_data` and `_inFlightAttributes` objects where
`_inFlightAttributes` has priority) then that means the backend
has updated the value and the key is added to the list of changed
keys.
@method _changedKeys
@private
*/
/*
TODO IGOR DAVID
There seems to be a potential bug here, where we will return keys that are not
in the schema
*/
_changedKeys(updates) {
let changedKeys = [];
if (updates) {
let original, i, value, key;
let keys = Object.keys(updates);
let length = keys.length;
let hasAttrs = this.hasChangedAttributes();
let attrs;
if (hasAttrs) {
attrs = this._attributes;
}
original = Ember.assign(Object.create(null), this._data, this.__inFlightAttributes);
for (i = 0; i < length; i++) {
key = keys[i];
value = updates[key]; // A value in _attributes means the user has a local change to
// this attributes. We never override this value when merging
// updates from the backend so we should not sent a change
// notification if the server value differs from the original.
if (hasAttrs === true && attrs[key] !== undefined) {
continue;
}
if (!Ember.isEqual(original[key], value)) {
changedKeys.push(key);
}
}
}
return changedKeys;
}
toString() {
return "<".concat(this.modelName, ":").concat(this.id, ">");
}
}
_exports.default = RecordDataDefault;
function assertRelationshipData(store, recordData, data, meta) {
(true && Ember.assert("A ".concat(recordData.modelName, " record was pushed into the store with the value of ").concat(meta.key, " being '").concat(JSON.stringify(data), "', but ").concat(meta.key, " is a belongsTo relationship so the value must not be an array. You should probably check your data payload or serializer."), !Array.isArray(data)));
(true && Ember.assert("Encountered a relationship identifier without a type for the ".concat(meta.kind, " relationship '").concat(meta.key, "' on ").concat(recordData, ", expected a json-api identifier with type '").concat(meta.type, "' but found '").concat(JSON.stringify(data), "'. Please check your serializer and make sure it is serializing the relationship payload into a JSON API format."), data === null || typeof data.type === 'string' && data.type.length));
(true && Ember.assert("Encountered a relationship identifier without an id for the ".concat(meta.kind, " relationship '").concat(meta.key, "' on ").concat(recordData, ", expected a json-api identifier but found '").concat(JSON.stringify(data), "'. Please check your serializer and make sure it is serializing the relationship payload into a JSON API format."), data === null || !!(0, _coerceId.default)(data.id)));
(true && Ember.assert("Encountered a relationship identifier with type '".concat(data.type, "' for the ").concat(meta.kind, " relationship '").concat(meta.key, "' on ").concat(recordData, ", Expected a json-api identifier with type '").concat(meta.type, "'. No model was found for '").concat(data.type, "'."), data === null || !data.type || store._hasModelFor(data.type)));
} // Handle dematerialization for relationship `rel`. In all cases, notify the
// relationship of the dematerialization: this is done so the relationship can
// notify its inverse which needs to update state
//
// If the inverse is sync, unloading this record is treated as a client-side
// delete, so we remove the inverse records from this relationship to
// disconnect the graph. Because it's not async, we don't need to keep around
// the internalModel as an id-wrapper for references and because the graph is
// disconnected we can actually destroy the internalModel when checking for
// orphaned models.
function destroyRelationship(rel) {
rel.recordDataDidDematerialize();
if (rel._inverseIsSync()) {
rel.removeAllRecordDatasFromOwn();
rel.removeAllCanonicalRecordDatasFromOwn();
}
}
function areAllModelsUnloaded(recordDatas) {
for (let i = 0; i < recordDatas.length; ++i) {
if (recordDatas[i].isRecordInUse()) {
return false;
}
}
return true;
}
});
define("@ember-data/store/-private/system/model/states", ["exports", "@ember-data/canary-features"], function (_exports, _canaryFeatures) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
/*
This file encapsulates the various states that a record can transition
through during its lifecycle.
*/
/**
### State
Each record has a `currentState` property that explicitly tracks what
state a record is in at any given time. For instance, if a record is
newly created and has not yet been sent to the adapter to be saved,
it would be in the `root.loaded.created.uncommitted` state. If a
record has had local modifications made to it that are in the
process of being saved, the record would be in the
`root.loaded.updated.inFlight` state. (This state path will be
explained in more detail below.)
Events are sent by the record or its store to the record's
`currentState` property. How the state reacts to these events is
dependent on which state it is in. In some states, certain events
will be invalid and will cause an exception to be raised.
States are hierarchical and every state is a sub-state of the
`RootState`. For example, a record can be in the
`root.deleted.uncommitted` state then transitions into the
`root.deleted.inFlight` state. If a child state does not implement
an event handler, the state manager will attempt to invoke the event
on all parent states until the root state is reached. The state
hierarchy of a record is described in terms of a path string. You
can determine a record's current state by getting the state's
`stateName` property:
```javascript
record.get('currentState.stateName');
//=> "root.created.uncommitted"
```
The hierarchy of valid states that ship with ember data looks like
this:
```text
* root
* deleted
* saved
* uncommitted
* inFlight
* empty
* loaded
* created
* uncommitted
* inFlight
* saved
* updated
* uncommitted
* inFlight
* loading
```
The `Model` states are themselves stateless. What that means is
that, the hierarchical states that each of *those* points to is a
shared data structure. For performance reasons, instead of each
record getting its own copy of the hierarchy of states, each record
points to this global, immutable shared instance. How does a state
know which record it should be acting on? We pass the record
instance into the state's event handlers as the first argument.
The record passed as the first parameter is where you should stash
state about the record if needed; you should never store data on the state
object itself.
### Events and Flags
A state may implement zero or more events and flags.
#### Events
Events are named functions that are invoked when sent to a record. The
record will first look for a method with the given name on the
current state. If no method is found, it will search the current
state's parent, and then its grandparent, and so on until reaching
the top of the hierarchy. If the root is reached without an event
handler being found, an exception will be raised. This can be very
helpful when debugging new features.
Here's an example implementation of a state with a `myEvent` event handler:
```javascript
aState: State.create({
myEvent: function(manager, param) {
console.log("Received myEvent with", param);
}
})
```
To trigger this event:
```javascript
record.send('myEvent', 'foo');
//=> "Received myEvent with foo"
```
Note that an optional parameter can be sent to a record's `send()` method,
which will be passed as the second parameter to the event handler.
Events should transition to a different state if appropriate. This can be
done by calling the record's `transitionTo()` method with a path to the
desired state. The state manager will attempt to resolve the state path
relative to the current state. If no state is found at that path, it will
attempt to resolve it relative to the current state's parent, and then its
parent, and so on until the root is reached. For example, imagine a hierarchy
like this:
* created
* uncommitted <-- currentState
* inFlight
* updated
* inFlight
If we are currently in the `uncommitted` state, calling
`transitionTo('inFlight')` would transition to the `created.inFlight` state,
while calling `transitionTo('updated.inFlight')` would transition to
the `updated.inFlight` state.
Remember that *only events* should ever cause a state transition. You should
never call `transitionTo()` from outside a state's event handler. If you are
tempted to do so, create a new event and send that to the state manager.
#### Flags
Flags are Boolean values that can be used to introspect a record's current
state in a more user-friendly way than examining its state path. For example,
instead of doing this:
```javascript
var statePath = record.get('stateManager.currentPath');
if (statePath === 'created.inFlight') {
doSomething();
}
```
You can say:
```javascript
if (record.get('isNew') && record.get('isSaving')) {
doSomething();
}
```
If your state does not set a value for a given flag, the value will
be inherited from its parent (or the first place in the state hierarchy
where it is defined).
The current set of flags are defined below. If you want to add a new flag,
in addition to the area below, you will also need to declare it in the
`Model` class.
* [isEmpty](DS.Model.html#property_isEmpty)
* [isLoading](DS.Model.html#property_isLoading)
* [isLoaded](DS.Model.html#property_isLoaded)
* [hasDirtyAttributes](DS.Model.html#property_hasDirtyAttributes)
* [isSaving](DS.Model.html#property_isSaving)
* [isDeleted](DS.Model.html#property_isDeleted)
* [isNew](DS.Model.html#property_isNew)
* [isValid](DS.Model.html#property_isValid)
@class RootState
*/
function didSetProperty(internalModel, context) {
if (context.isDirty) {
internalModel.send('becomeDirty');
} else {
internalModel.send('propertyWasReset');
}
internalModel.updateRecordArrays();
} // Implementation notes:
//
// Each state has a boolean value for all of the following flags:
//
// * isLoaded: The record has a populated `data` property. When a
// record is loaded via `store.find`, `isLoaded` is false
// until the adapter sets it. When a record is created locally,
// its `isLoaded` property is always true.
// * isDirty: The record has local changes that have not yet been
// saved by the adapter. This includes records that have been
// created (but not yet saved) or deleted.
// * isSaving: The record has been committed, but
// the adapter has not yet acknowledged that the changes have
// been persisted to the backend.
// * isDeleted: The record was marked for deletion. When `isDeleted`
// is true and `isDirty` is true, the record is deleted locally
// but the deletion was not yet persisted. When `isSaving` is
// true, the change is in-flight. When both `isDirty` and
// `isSaving` are false, the change has persisted.
// * isNew: The record was created on the client and the adapter
// did not yet report that it was successfully saved.
// * isValid: The adapter did not report any server-side validation
// failures.
// The dirty state is a abstract state whose functionality is
// shared between the `created` and `updated` states.
//
// The deleted state shares the `isDirty` flag with the
// subclasses of `DirtyState`, but with a very different
// implementation.
//
// Dirty states have three child states:
//
// `uncommitted`: the store has not yet handed off the record
// to be saved.
// `inFlight`: the store has handed off the record to be saved,
// but the adapter has not yet acknowledged success.
// `invalid`: the record has invalid information and cannot be
// sent to the adapter yet.
const DirtyState = {
initialState: 'uncommitted',
// FLAGS
isDirty: true,
// SUBSTATES
// When a record first becomes dirty, it is `uncommitted`.
// This means that there are local pending changes, but they
// have not yet begun to be saved, and are not invalid.
uncommitted: {
// EVENTS
didSetProperty,
//TODO(Igor) reloading now triggers a
//loadingData event, though it seems fine?
loadingData() {},
propertyWasReset(internalModel, name) {
if (!internalModel.hasChangedAttributes()) {
internalModel.send('rolledBack');
}
},
pushedData(internalModel) {
if (!internalModel.hasChangedAttributes()) {
internalModel.transitionTo('loaded.saved');
}
},
becomeDirty() {},
willCommit(internalModel) {
internalModel.transitionTo('inFlight');
},
reloadRecord(internalModel, {
resolve,
options
}) {
resolve(internalModel.store._reloadRecord(internalModel, options));
},
rolledBack(internalModel) {
internalModel.transitionTo('loaded.saved');
internalModel.triggerLater('rolledBack');
},
becameInvalid(internalModel) {
internalModel.transitionTo('invalid');
},
rollback(internalModel) {
internalModel.rollbackAttributes();
internalModel.triggerLater('ready');
}
},
// Once a record has been handed off to the adapter to be
// saved, it is in the 'in flight' state. Changes to the
// record cannot be made during this window.
inFlight: {
// FLAGS
isSaving: true,
// EVENTS
didSetProperty,
becomeDirty() {},
pushedData() {},
unloadRecord: assertAgainstUnloadRecord,
// TODO: More robust semantics around save-while-in-flight
willCommit() {},
didCommit(internalModel) {
internalModel.transitionTo('saved');
internalModel.send('invokeLifecycleCallbacks', this.dirtyType);
},
rolledBack(internalModel) {
internalModel.triggerLater('rolledBack');
},
becameInvalid(internalModel) {
internalModel.transitionTo('invalid');
internalModel.send('invokeLifecycleCallbacks');
},
becameError(internalModel) {
internalModel.transitionTo('uncommitted');
internalModel.triggerLater('becameError', internalModel);
}
},
// A record is in the `invalid` if the adapter has indicated
// the the record failed server-side invalidations.
invalid: {
// FLAGS
isValid: false,
// EVENTS
deleteRecord(internalModel) {
internalModel.transitionTo('deleted.uncommitted');
},
didSetProperty(internalModel, context) {
internalModel.removeErrorMessageFromAttribute(context.name);
didSetProperty(internalModel, context);
if (!internalModel.hasErrors()) {
this.becameValid(internalModel);
}
},
becameInvalid() {},
becomeDirty() {},
pushedData() {},
willCommit(internalModel) {
internalModel.clearErrorMessages();
internalModel.transitionTo('inFlight');
},
rolledBack(internalModel) {
internalModel.clearErrorMessages();
internalModel.transitionTo('loaded.saved');
internalModel.triggerLater('ready');
},
becameValid(internalModel) {
internalModel.transitionTo('uncommitted');
},
invokeLifecycleCallbacks(internalModel) {
internalModel.triggerLater('becameInvalid', internalModel);
}
}
}; // The created and updated states are created outside the state
// chart so we can reopen their substates and add mixins as
// necessary.
function deepClone(object) {
const clone = {};
let value;
for (let prop in object) {
value = object[prop];
if (value && typeof value === 'object') {
clone[prop] = deepClone(value);
} else {
clone[prop] = value;
}
}
return clone;
}
function mixin(original, hash) {
for (let prop in hash) {
original[prop] = hash[prop];
}
return original;
}
function dirtyState(options) {
var newState = deepClone(DirtyState);
return mixin(newState, options);
}
const createdState = dirtyState({
dirtyType: 'created',
// FLAGS
isNew: true
});
createdState.invalid.rolledBack = function (internalModel) {
internalModel.transitionTo('deleted.saved');
internalModel.triggerLater('rolledBack');
};
createdState.uncommitted.rolledBack = function (internalModel) {
internalModel.transitionTo('deleted.saved');
internalModel.triggerLater('rolledBack');
};
const updatedState = dirtyState({
dirtyType: 'updated'
});
function createdStateDeleteRecord(internalModel) {
internalModel.transitionTo('deleted.saved');
internalModel.send('invokeLifecycleCallbacks');
}
createdState.uncommitted.deleteRecord = createdStateDeleteRecord;
createdState.invalid.deleteRecord = createdStateDeleteRecord;
createdState.uncommitted.rollback = function (internalModel) {
DirtyState.uncommitted.rollback.apply(this, arguments);
internalModel.transitionTo('deleted.saved');
};
createdState.uncommitted.pushedData = function (internalModel) {
internalModel.transitionTo('loaded.updated.uncommitted');
internalModel.triggerLater('didLoad');
};
createdState.uncommitted.propertyWasReset = function () {};
function assertAgainstUnloadRecord(internalModel) {
(true && Ember.assert('You can only unload a record which is not inFlight. `' + internalModel + '`', false));
}
updatedState.invalid.becameValid = function (internalModel) {
// we're eagerly transition into the loaded.saved state, even though we could
// be still dirty; but the setup hook of the loaded.saved state checks for
// dirty attributes and transitions into the corresponding dirty state
internalModel.transitionTo('loaded.saved');
};
updatedState.inFlight.unloadRecord = assertAgainstUnloadRecord;
updatedState.uncommitted.deleteRecord = function (internalModel) {
internalModel.transitionTo('deleted.uncommitted');
};
updatedState.invalid.rolledBack = function (internalModel) {
internalModel.clearErrorMessages();
internalModel.transitionTo('loaded.saved');
internalModel.triggerLater('rolledBack');
};
const RootState = {
// FLAGS
isEmpty: false,
isLoading: false,
isLoaded: false,
isDirty: false,
isSaving: false,
isDeleted: false,
isNew: false,
isValid: true,
// DEFAULT EVENTS
// Trying to roll back if you're not in the dirty state
// doesn't change your state. For example, if you're in the
// in-flight state, rolling back the record doesn't move
// you out of the in-flight state.
rolledBack() {},
unloadRecord(internalModel) {},
propertyWasReset() {},
// SUBSTATES
// A record begins its lifecycle in the `empty` state.
// If its data will come from the adapter, it will
// transition into the `loading` state. Otherwise, if
// the record is being created on the client, it will
// transition into the `created` state.
empty: {
isEmpty: true,
// EVENTS
loadingData(internalModel, promise) {
if (!_canaryFeatures.REQUEST_SERVICE) {
internalModel._promiseProxy = promise;
}
internalModel.transitionTo('loading');
},
loadedData(internalModel) {
internalModel.transitionTo('loaded.created.uncommitted');
internalModel.triggerLater('ready');
},
pushedData(internalModel) {
internalModel.transitionTo('loaded.saved');
internalModel.triggerLater('didLoad');
internalModel.triggerLater('ready');
}
},
// A record enters this state when the store asks
// the adapter for its data. It remains in this state
// until the adapter provides the requested data.
//
// Usually, this process is asynchronous, using an
// XHR to retrieve the data.
loading: {
// FLAGS
isLoading: true,
exit(internalModel) {
internalModel._promiseProxy = null;
},
loadingData() {},
// EVENTS
pushedData(internalModel) {
internalModel.transitionTo('loaded.saved');
internalModel.triggerLater('didLoad');
internalModel.triggerLater('ready'); //TODO this seems out of place here
internalModel.didCleanError();
},
becameError(internalModel) {
internalModel.triggerLater('becameError', internalModel);
},
notFound(internalModel) {
internalModel.transitionTo('empty');
}
},
// A record enters this state when its data is populated.
// Most of a record's lifecycle is spent inside substates
// of the `loaded` state.
loaded: {
initialState: 'saved',
// FLAGS
isLoaded: true,
//TODO(Igor) Reloading now triggers a loadingData event,
//but it should be ok?
loadingData() {},
// SUBSTATES
// If there are no local changes to a record, it remains
// in the `saved` state.
saved: {
setup(internalModel) {
if (internalModel.hasChangedAttributes()) {
internalModel.adapterDidDirty();
}
},
// EVENTS
didSetProperty,
pushedData() {},
becomeDirty(internalModel) {
internalModel.transitionTo('updated.uncommitted');
},
willCommit(internalModel) {
internalModel.transitionTo('updated.inFlight');
},
reloadRecord(internalModel, {
resolve,
options
}) {
if (!_canaryFeatures.REQUEST_SERVICE) {
resolve(internalModel.store._reloadRecord(internalModel, options));
}
},
deleteRecord(internalModel) {
internalModel.transitionTo('deleted.uncommitted');
},
unloadRecord(internalModel) {},
didCommit() {},
// loaded.saved.notFound would be triggered by a failed
// `reload()` on an unchanged record
notFound() {}
},
// A record is in this state after it has been locally
// created but before the adapter has indicated that
// it has been saved.
created: createdState,
// A record is in this state if it has already been
// saved to the server, but there are new local changes
// that have not yet been saved.
updated: updatedState
},
// A record is in this state if it was deleted from the store.
deleted: {
initialState: 'uncommitted',
dirtyType: 'deleted',
// FLAGS
isDeleted: true,
isLoaded: true,
isDirty: true,
// TRANSITIONS
setup(internalModel) {
internalModel.updateRecordArrays();
},
// SUBSTATES
// When a record is deleted, it enters the `start`
// state. It will exit this state when the record
// starts to commit.
uncommitted: {
// EVENTS
willCommit(internalModel) {
internalModel.transitionTo('inFlight');
},
rollback(internalModel) {
internalModel.rollbackAttributes();
internalModel.triggerLater('ready');
},
pushedData() {},
becomeDirty() {},
deleteRecord() {},
rolledBack(internalModel) {
internalModel.transitionTo('loaded.saved');
internalModel.triggerLater('ready');
internalModel.triggerLater('rolledBack');
}
},
// After a record starts committing, but
// before the adapter indicates that the deletion
// has saved to the server, a record is in the
// `inFlight` substate of `deleted`.
inFlight: {
// FLAGS
isSaving: true,
// EVENTS
unloadRecord: assertAgainstUnloadRecord,
// TODO: More robust semantics around save-while-in-flight
willCommit() {},
didCommit(internalModel) {
internalModel.transitionTo('saved');
internalModel.send('invokeLifecycleCallbacks');
},
becameError(internalModel) {
internalModel.transitionTo('uncommitted');
internalModel.triggerLater('becameError', internalModel);
},
becameInvalid(internalModel) {
internalModel.transitionTo('invalid');
internalModel.triggerLater('becameInvalid', internalModel);
}
},
// Once the adapter indicates that the deletion has
// been saved, the record enters the `saved` substate
// of `deleted`.
saved: {
// FLAGS
isDirty: false,
setup(internalModel) {
internalModel.removeFromInverseRelationships();
},
invokeLifecycleCallbacks(internalModel) {
internalModel.triggerLater('didDelete', internalModel);
internalModel.triggerLater('didCommit', internalModel);
},
willCommit() {},
didCommit() {},
pushedData() {}
},
invalid: {
isValid: false,
didSetProperty(internalModel, context) {
internalModel.removeErrorMessageFromAttribute(context.name);
didSetProperty(internalModel, context);
if (!internalModel.hasErrors()) {
this.becameValid(internalModel);
}
},
becameInvalid() {},
becomeDirty() {},
deleteRecord() {},
willCommit() {},
rolledBack(internalModel) {
internalModel.clearErrorMessages();
internalModel.transitionTo('loaded.saved');
internalModel.triggerLater('ready');
},
becameValid(internalModel) {
internalModel.transitionTo('uncommitted');
}
}
},
invokeLifecycleCallbacks(internalModel, dirtyType) {
if (dirtyType === 'created') {
internalModel.triggerLater('didCreate', internalModel);
} else {
internalModel.triggerLater('didUpdate', internalModel);
}
internalModel.triggerLater('didCommit', internalModel);
}
};
function wireState(object, parent, name) {
// TODO: Use Object.create and copy instead
object = mixin(parent ? Object.create(parent) : {}, object);
object.parentState = parent;
object.stateName = name;
for (let prop in object) {
if (!object.hasOwnProperty(prop) || prop === 'parentState' || prop === 'stateName') {
continue;
}
if (typeof object[prop] === 'object') {
object[prop] = wireState(object[prop], object, name + '.' + prop);
}
}
return object;
}
var _default = wireState(RootState, null, 'root');
_exports.default = _default;
});
define("@ember-data/store/-private/system/record-arrays/adapter-populated-record-array", ["exports", "@ember-data/store/-private/system/record-arrays/record-array", "@ember-data/store/-private/system/clone-null"], function (_exports, _recordArray, _cloneNull) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
/**
Represents an ordered list of records whose order and membership is
determined by the adapter. For example, a query sent to the adapter
may trigger a search on the server, whose results would be loaded
into an instance of the `AdapterPopulatedRecordArray`.
---
If you want to update the array and get the latest records from the
adapter, you can invoke [`update()`](#method_update):
Example
```javascript
// GET /users?isAdmin=true
store.query('user', { isAdmin: true }).then(function(admins) {
admins.then(function() {
console.log(admins.get("length")); // 42
});
// somewhere later in the app code, when new admins have been created
// in the meantime
//
// GET /users?isAdmin=true
admins.update().then(function() {
admins.get('isUpdating'); // false
console.log(admins.get("length")); // 123
});
admins.get('isUpdating'); // true
}
```
@class AdapterPopulatedRecordArray
@extends RecordArray
*/
var _default = _recordArray.default.extend({
init() {
// yes we are touching `this` before super, but ArrayProxy has a bug that requires this.
this.set('content', this.get('content') || Ember.A());
this._super(...arguments);
this.query = this.query || null;
this.links = this.links || null;
if (true
/* DEBUG */
) {
this._getDeprecatedEventedInfo = () => "AdapterPopulatedRecordArray containing ".concat(this.modelName, " for query: ").concat(this.query);
}
},
replace() {
throw new Error("The result of a server query (on ".concat(this.modelName, ") is immutable."));
},
_update() {
let store = Ember.get(this, 'store');
let query = Ember.get(this, 'query');
return store._query(this.modelName, query, this);
},
/**
@method _setInternalModels
@param {Array} internalModels
@param {Object} payload normalized payload
@private
*/
_setInternalModels(internalModels, payload) {
// TODO: initial load should not cause change events at all, only
// subsequent. This requires changing the public api of adapter.query, but
// hopefully we can do that soon.
this.get('content').setObjects(internalModels);
this.setProperties({
isLoaded: true,
isUpdating: false,
meta: (0, _cloneNull.default)(payload.meta),
links: (0, _cloneNull.default)(payload.links)
});
this.manager._associateWithRecordArray(internalModels, this);
const _hasDidLoad = true
/* DEBUG */
? this._has('didLoad') : this.has('didLoad');
if (_hasDidLoad) {
// TODO: should triggering didLoad event be the last action of the runLoop?
Ember.run.once(this, 'trigger', 'didLoad');
}
}
});
_exports.default = _default;
});
define("@ember-data/store/-private/system/record-arrays/record-array", ["exports", "@ember-data/store/-private/system/deprecated-evented", "@ember-data/store/-private/system/promise-proxies", "@ember-data/store/-private/system/snapshot-record-array"], function (_exports, _deprecatedEvented, _promiseProxies, _snapshotRecordArray) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
/**
A record array is an array that contains records of a certain modelName. The record
array materializes records as needed when they are retrieved for the first
time. You should not create record arrays yourself. Instead, an instance of
`RecordArray` or its subclasses will be returned by your application's store
in response to queries.
@class RecordArray
@extends ArrayProxy
@uses Ember.Evented
*/
var _default = Ember.ArrayProxy.extend(_deprecatedEvented.default, {
init() {
this._super(...arguments);
if (true
/* DEBUG */
) {
this._getDeprecatedEventedInfo = () => "RecordArray containing ".concat(this.modelName);
}
/**
The array of client ids backing the record array. When a
record is requested from the record array, the record
for the client id at the same index is materialized, if
necessary, by the store.
@property content
@private
@type Ember.Array
*/
this.set('content', this.content || null);
/**
The flag to signal a `RecordArray` is finished loading data.
Example
```javascript
var people = store.peekAll('person');
people.get('isLoaded'); // true
```
@property isLoaded
@type Boolean
*/
this.isLoaded = this.isLoaded || false;
/**
The flag to signal a `RecordArray` is currently loading data.
Example
```javascript
var people = store.peekAll('person');
people.get('isUpdating'); // false
people.update();
people.get('isUpdating'); // true
```
@property isUpdating
@type Boolean
*/
this.isUpdating = false;
/**
The store that created this record array.
@property store
@private
@type Store
*/
this.store = this.store || null;
this._updatingPromise = null;
},
replace() {
throw new Error("The result of a server query (for all ".concat(this.modelName, " types) is immutable. To modify contents, use toArray()"));
},
/**
The modelClass represented by this record array.
@property type
@type Model
*/
type: Ember.computed('modelName', function () {
if (!this.modelName) {
return null;
}
return this.store.modelFor(this.modelName);
}).readOnly(),
/**
Retrieves an object from the content by index.
@method objectAtContent
@private
@param {Number} index
@return {Model} record
*/
objectAtContent(index) {
let internalModel = Ember.get(this, 'content').objectAt(index);
return internalModel && internalModel.getRecord();
},
/**
Used to get the latest version of all of the records in this array
from the adapter.
Example
```javascript
var people = store.peekAll('person');
people.get('isUpdating'); // false
people.update().then(function() {
people.get('isUpdating'); // false
});
people.get('isUpdating'); // true
```
@method update
*/
update() {
if (Ember.get(this, 'isUpdating')) {
return this._updatingPromise;
}
this.set('isUpdating', true);
let updatingPromise = this._update().finally(() => {
this._updatingPromise = null;
if (this.get('isDestroying') || this.get('isDestroyed')) {
return;
}
this.set('isUpdating', false);
});
this._updatingPromise = updatingPromise;
return updatingPromise;
},
/*
Update this RecordArray and return a promise which resolves once the update
is finished.
*/
_update() {
return this.store.findAll(this.modelName, {
reload: true
});
},
/**
Adds an internal model to the `RecordArray` without duplicates
@method _pushInternalModels
@private
@param {InternalModel} internalModel
*/
_pushInternalModels(internalModels) {
// pushObjects because the internalModels._recordArrays set was already
// consulted for inclusion, so addObject and its on .contains call is not
// required.
Ember.get(this, 'content').pushObjects(internalModels);
},
/**
Removes an internalModel to the `RecordArray`.
@method removeInternalModel
@private
@param {InternalModel} internalModel
*/
_removeInternalModels(internalModels) {
Ember.get(this, 'content').removeObjects(internalModels);
},
/**
Saves all of the records in the `RecordArray`.
Example
```javascript
var messages = store.peekAll('message');
messages.forEach(function(message) {
message.set('hasBeenSeen', true);
});
messages.save();
```
@method save
@return {PromiseArray} promise
*/
save() {
let promiseLabel = "DS: RecordArray#save ".concat(this.modelName);
let promise = Ember.RSVP.Promise.all(this.invoke('save'), promiseLabel).then(() => this, null, 'DS: RecordArray#save return RecordArray');
return _promiseProxies.PromiseArray.create({
promise
});
},
_dissociateFromOwnRecords() {
this.get('content').forEach(internalModel => {
let recordArrays = internalModel.__recordArrays;
if (recordArrays) {
recordArrays.delete(this);
}
});
},
/**
@method _unregisterFromManager
@private
*/
_unregisterFromManager() {
this.manager.unregisterRecordArray(this);
},
willDestroy() {
this._unregisterFromManager();
this._dissociateFromOwnRecords(); // TODO: we should not do work during destroy:
// * when objects are destroyed, they should simply be left to do
// * if logic errors do to this, that logic needs to be more careful during
// teardown (ember provides isDestroying/isDestroyed) for this reason
// * the exception being: if an dominator has a reference to this object,
// and must be informed to release e.g. e.g. removing itself from th
// recordArrayMananger
Ember.set(this, 'content', null);
Ember.set(this, 'length', 0);
this._super(...arguments);
},
/*
@method _createSnapshot
@private
*/
_createSnapshot(options) {
// this is private for users, but public for ember-data internals
return new _snapshotRecordArray.default(this, this.get('meta'), options);
},
/*
@method _takeSnapshot
@private
*/
_takeSnapshot() {
return Ember.get(this, 'content').map(internalModel => internalModel.createSnapshot());
}
});
_exports.default = _default;
});
define("@ember-data/store/-private/system/references/belongs-to", ["exports", "ember-data/-debug", "@ember-data/store/-private/system/model/model", "@ember-data/store/-private/system/references/reference", "@ember-data/store/-private/system/record-data-for"], function (_exports, _debug, _model, _reference, _recordDataFor) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
/**
A BelongsToReference is a low-level API that allows users and
addon author to perform meta-operations on a belongs-to
relationship.
@class BelongsToReference
@extends Reference
*/
class BelongsToReference extends _reference.default {
constructor(store, parentInternalModel, belongsToRelationship, key) {
super(store, parentInternalModel);
this.key = key;
this.belongsToRelationship = belongsToRelationship;
this.type = belongsToRelationship.relationshipMeta.type;
this.parent = parentInternalModel.recordReference;
this.parentInternalModel = parentInternalModel; // TODO inverse
}
/**
The `id` of the record that this reference refers to. Together, the
`type()` and `id()` methods form a composite key for the identity
map. This can be used to access the id of an async relationship
without triggering a fetch that would normally happen if you
attempted to use `record.get('relationship.id')`.
Example
```javascript
// models/blog.js
export default Model.extend({
user: belongsTo({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
data: { type: 'user', id: 1 }
}
}
}
});
let userRef = blog.belongsTo('user');
// get the identifier of the reference
if (userRef.remoteType() === "id") {
let id = userRef.id();
}
```
@method id
@return {String} The id of the record in this belongsTo relationship.
*/
id() {
let id = null;
let resource = this._resource();
if (resource && resource.data && resource.data.id) {
id = resource.data.id;
}
return id;
}
_resource() {
return this.recordData.getBelongsTo(this.key);
}
/**
`push` can be used to update the data in the relationship and Ember
Data will treat the new data as the conanical value of this
relationship on the backend.
Example
```app/models/blog.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
data: { type: 'user', id: 1 }
}
}
}
});
let userRef = blog.belongsTo('user');
// provide data for reference
userRef.push({
data: {
type: 'user',
id: 1,
attributes: {
username: "@user"
}
}
}).then(function(user) {
userRef.value() === user;
});
```
@method push
@param {Object|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
@return {Promise} A promise that resolves with the new value in this belongs-to relationship.
*/
push(objectOrPromise) {
return Ember.RSVP.resolve(objectOrPromise).then(data => {
let record; // TODO deprecate data as Model
if (data instanceof _model.default) {
record = data;
} else {
record = this.store.push(data);
}
(0, _debug.assertPolymorphicType)(this.internalModel, this.belongsToRelationship.relationshipMeta, record._internalModel, this.store); //TODO Igor cleanup, maybe move to relationship push
this.belongsToRelationship.setCanonicalRecordData((0, _recordDataFor.default)(record));
return record;
});
}
/**
`value()` synchronously returns the current value of the belongs-to
relationship. Unlike `record.get('relationshipName')`, calling
`value()` on a reference does not trigger a fetch if the async
relationship is not yet loaded. If the relationship is not loaded
it will always return `null`.
Example
```javascript
// models/blog.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
data: { type: 'user', id: 1 }
}
}
}
});
let userRef = blog.belongsTo('user');
userRef.value(); // null
// provide data for reference
userRef.push({
data: {
type: 'user',
id: 1,
attributes: {
username: "@user"
}
}
}).then(function(user) {
userRef.value(); // user
});
```
@method value
@return {Model} the record in this relationship
*/
value() {
let store = this.parentInternalModel.store;
let resource = this._resource();
if (resource && resource.data) {
let inverseInternalModel = store._internalModelForResource(resource.data);
if (inverseInternalModel && inverseInternalModel.isLoaded()) {
return inverseInternalModel.getRecord();
}
}
return null;
}
/**
Loads a record in a belongs to relationship if it is not already
loaded. If the relationship is already loaded this method does not
trigger a new load.
Example
```javascript
// models/blog.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
data: { type: 'user', id: 1 }
}
}
}
});
let userRef = blog.belongsTo('user');
userRef.value(); // null
userRef.load().then(function(user) {
userRef.value() === user
});
```
You may also pass in an options object whose properties will be
fed forward. This enables you to pass `adapterOptions` into the
request given to the adapter via the reference.
Example
```javascript
userRef.load({ adapterOptions: { isPrivate: true } }).then(function(user) {
userRef.value() === user;
});
```
```app/adapters/user.js
export default ApplicationAdapter.extend({
findRecord(store, type, id, snapshot) {
// In the adapter you will have access to adapterOptions.
let adapterOptions = snapshot.adapterOptions;
}
});
```
@method load
@param {Object} options the options to pass in.
@return {Promise} a promise that resolves with the record in this belongs-to relationship.
*/
load(options) {
return this.parentInternalModel.getBelongsTo(this.key, options);
}
/**
Triggers a reload of the value in this relationship. If the
remoteType is `"link"` Ember Data will use the relationship link to
reload the relationship. Otherwise it will reload the record by its
id.
Example
```javascript
// models/blog.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
data: { type: 'user', id: 1 }
}
}
}
});
let userRef = blog.belongsTo('user');
userRef.reload().then(function(user) {
userRef.value() === user
});
```
You may also pass in an options object whose properties will be
fed forward. This enables you to pass `adapterOptions` into the
request given to the adapter via the reference. A full example
can be found in the `load` method.
Example
```javascript
userRef.reload({ adapterOptions: { isPrivate: true } })
```
@method reload
@param {Object} options the options to pass in.
@return {Promise} a promise that resolves with the record in this belongs-to relationship after the reload has completed.
*/
reload(options) {
return this.parentInternalModel.reloadBelongsTo(this.key, options).then(internalModel => {
return this.value();
});
}
}
_exports.default = BelongsToReference;
});
define("@ember-data/store/-private/system/references/has-many", ["exports", "@ember-data/store/-private/system/references/reference", "ember-data/-debug", "@ember-data/store/-private/system/record-data-for"], function (_exports, _reference, _debug, _recordDataFor) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/**
@module @ember-data/store
*/
/**
A HasManyReference is a low-level API that allows users and addon
author to perform meta-operations on a has-many relationship.
@class HasManyReference
*/
class HasManyReference extends _reference.default {
constructor(store, parentInternalModel, hasManyRelationship, key) {
super(store, parentInternalModel);
this.key = key;
this.hasManyRelationship = hasManyRelationship;
this.type = hasManyRelationship.relationshipMeta.type;
this.parent = parentInternalModel.recordReference;
this.parentInternalModel = parentInternalModel; // TODO inverse
}
_resource() {
return this.recordData.getHasMany(this.key);
}
/**
This returns a string that represents how the reference will be
looked up when it is loaded. If the relationship has a link it will
use the "link" otherwise it defaults to "id".
Example
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
```
```javascript
let post = store.push({
data: {
type: 'post',
id: 1,
relationships: {
comments: {
data: [{ type: 'comment', id: 1 }]
}
}
}
});
let commentsRef = post.hasMany('comments');
// get the identifier of the reference
if (commentsRef.remoteType() === "ids") {
let ids = commentsRef.ids();
} else if (commentsRef.remoteType() === "link") {
let link = commentsRef.link();
}
```
@method remoteType
@return {String} The name of the remote type. This should either be `link` or `ids`
*/
remoteType() {
let value = this._resource();
if (value && value.links && value.links.related) {
return 'link';
}
return 'ids';
}
/**
`ids()` returns an array of the record IDs in this relationship.
Example
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
```
```javascript
let post = store.push({
data: {
type: 'post',
id: 1,
relationships: {
comments: {
data: [{ type: 'comment', id: 1 }]
}
}
}
});
let commentsRef = post.hasMany('comments');
commentsRef.ids(); // ['1']
```
@method ids
@return {Array} The ids in this has-many relationship
*/
ids() {
let resource = this._resource();
let ids = [];
if (resource.data) {
ids = resource.data.map(data => data.id);
}
return ids;
}
/**
`push` can be used to update the data in the relationship and Ember
Data will treat the new data as the canonical value of this
relationship on the backend.
Example
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
```
```
let post = store.push({
data: {
type: 'post',
id: 1,
relationships: {
comments: {
data: [{ type: 'comment', id: 1 }]
}
}
}
});
let commentsRef = post.hasMany('comments');
commentsRef.ids(); // ['1']
commentsRef.push([
[{ type: 'comment', id: 2 }],
[{ type: 'comment', id: 3 }],
])
commentsRef.ids(); // ['2', '3']
```
@method push
@param {Array|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
@return {ManyArray}
*/
push(objectOrPromise) {
return Ember.RSVP.resolve(objectOrPromise).then(payload => {
let array = payload;
if (typeof payload === 'object' && payload.data) {
array = payload.data;
}
let internalModels = array.map(obj => {
let record = this.store.push(obj);
if (true
/* DEBUG */
) {
let relationshipMeta = this.hasManyRelationship.relationshipMeta;
(0, _debug.assertPolymorphicType)(this.internalModel, relationshipMeta, record._internalModel, this.store);
}
return (0, _recordDataFor.default)(record);
});
this.hasManyRelationship.computeChanges(internalModels);
return this.internalModel.getHasMany(this.hasManyRelationship.key); // TODO IGOR it seems wrong that we were returning the many array here
//return this.hasManyRelationship.manyArray;
});
}
_isLoaded() {
let hasRelationshipDataProperty = Ember.get(this.hasManyRelationship, 'hasAnyRelationshipData');
if (!hasRelationshipDataProperty) {
return false;
}
let members = this.hasManyRelationship.members.toArray(); //TODO Igor cleanup
return members.every(recordData => {
let store = this.parentInternalModel.store;
let internalModel = store._internalModelForResource(recordData.getResourceIdentifier());
return internalModel.isLoaded() === true;
});
}
/**
`value()` synchronously returns the current value of the has-many
relationship. Unlike `record.get('relationshipName')`, calling
`value()` on a reference does not trigger a fetch if the async
relationship is not yet loaded. If the relationship is not loaded
it will always return `null`.
Example
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
```
```javascript
let post = store.push({
data: {
type: 'post',
id: 1,
relationships: {
comments: {
data: [{ type: 'comment', id: 1 }]
}
}
}
});
let commentsRef = post.hasMany('comments');
post.get('comments').then(function(comments) {
commentsRef.value() === comments
})
```
@method value
@return {ManyArray}
*/
value() {
if (this._isLoaded()) {
return this.internalModel.getManyArray(this.key);
}
return null;
}
/**
Loads the relationship if it is not already loaded. If the
relationship is already loaded this method does not trigger a new
load. This causes a request to the specified
relationship link or reloads all items currently in the relationship.
Example
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
```
```javascript
let post = store.push({
data: {
type: 'post',
id: 1,
relationships: {
comments: {
data: [{ type: 'comment', id: 1 }]
}
}
}
});
let commentsRef = post.hasMany('comments');
commentsRef.load().then(function(comments) {
//...
});
```
You may also pass in an options object whose properties will be
fed forward. This enables you to pass `adapterOptions` into the
request given to the adapter via the reference.
Example
```javascript
commentsRef.load({ adapterOptions: { isPrivate: true } })
.then(function(comments) {
//...
});
```
```app/adapters/comment.js
export default ApplicationAdapter.extend({
findMany(store, type, id, snapshots) {
// In the adapter you will have access to adapterOptions.
let adapterOptions = snapshots[0].adapterOptions;
}
});
```
@method load
@param {Object} options the options to pass in.
@return {Promise} a promise that resolves with the ManyArray in
this has-many relationship.
*/
load(options) {
return this.internalModel.getHasMany(this.key, options);
}
/**
Reloads this has-many relationship. This causes a request to the specified
relationship link or reloads all items currently in the relationship.
Example
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
```
```javascript
let post = store.push({
data: {
type: 'post',
id: 1,
relationships: {
comments: {
data: [{ type: 'comment', id: 1 }]
}
}
}
});
let commentsRef = post.hasMany('comments');
commentsRef.reload().then(function(comments) {
//...
});
```
You may also pass in an options object whose properties will be
fed forward. This enables you to pass `adapterOptions` into the
request given to the adapter via the reference. A full example
can be found in the `load` method.
Example
```javascript
commentsRef.reload({ adapterOptions: { isPrivate: true } })
```
@method reload
@param {Object} options the options to pass in.
@return {Promise} a promise that resolves with the ManyArray in this has-many relationship.
*/
reload(options) {
return this.internalModel.reloadHasMany(this.key, options);
}
}
_exports.default = HasManyReference;
});
define("@ember-data/store/-private/system/references/record", ["exports", "@ember-data/store/-private/system/references/reference"], function (_exports, _reference) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
/**
An RecordReference is a low-level API that allows users and
addon author to perform meta-operations on a record.
@class RecordReference
*/
class RecordReference extends _reference.default {
constructor(...args) {
super(...args);
_defineProperty(this, "type", this.internalModel.modelName);
}
get _id() {
return this.internalModel.id;
}
/**
The `id` of the record that this reference refers to.
Together, the `type` and `id` properties form a composite key for
the identity map.
Example
```javascript
let userRef = store.getReference('user', 1);
userRef.id(); // '1'
```
@method id
@return {String} The id of the record.
*/
id() {
return this._id;
}
/**
How the reference will be looked up when it is loaded: Currently
this always return `identity` to signifying that a record will be
loaded by the `type` and `id`.
Example
```javascript
const userRef = store.getReference('user', 1);
userRef.remoteType(); // 'identity'
```
@method remoteType
@return {String} 'identity'
*/
remoteType() {
return 'identity';
}
/**
This API allows you to provide a reference with new data. The
simplest usage of this API is similar to `store.push`: you provide a
normalized hash of data and the object represented by the reference
will update.
If you pass a promise to `push`, Ember Data will not ask the adapter
for the data if another attempt to fetch it is made in the
interim. When the promise resolves, the underlying object is updated
with the new data, and the promise returned by *this function* is resolved
with that object.
For example, `recordReference.push(promise)` will be resolved with a
record.
Example
```javascript
let userRef = store.getReference('user', 1);
// provide data for reference
userRef.push({
data: {
id: "1",
type: "user",
attributes: {
username: "@user"
}
}
}).then(function(user) {
userRef.value() === user;
});
```
@method push
@param objectOrPromise a JSON:API ResourceDocument or a promise resolving to one
@return a promise for the value (record or relationship)
*/
push(objectOrPromise) {
return Ember.RSVP.resolve(objectOrPromise).then(data => {
return this.store.push(data);
});
}
/**
If the entity referred to by the reference is already loaded, it is
present as `reference.value`. Otherwise the value returned by this function
is `null`.
Example
```javascript
let userRef = store.getReference('user', 1);
userRef.value(); // user
```
@method value
@return {Model} the record for this RecordReference
*/
value() {
if (this.internalModel.hasRecord) {
return this.internalModel.getRecord();
}
return null;
}
/**
Triggers a fetch for the backing entity based on its `remoteType`
(see `remoteType` definitions per reference type).
Example
```javascript
let userRef = store.getReference('user', 1);
// load user (via store.find)
userRef.load().then(...)
```
@method load
@return {Promise} the record for this RecordReference
*/
load() {
if (this._id !== null) {
return this.store.findRecord(this.type, this._id);
}
throw new Error("Unable to fetch record of type ".concat(this.type, " without an id"));
}
/**
Reloads the record if it is already loaded. If the record is not
loaded it will load the record via `store.findRecord`
Example
```javascript
let userRef = store.getReference('user', 1);
// or trigger a reload
userRef.reload().then(...)
```
@method reload
@return {Promise} the record for this RecordReference
*/
reload() {
let record = this.value();
if (record) {
return record.reload();
}
return this.load();
}
}
_exports.default = RecordReference;
});
define("@ember-data/store/-private/system/references/reference", ["exports", "@ember-data/store/-private/system/record-data-for"], function (_exports, _recordDataFor) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function isResourceIdentiferWithRelatedLinks(value) {
return value && value.links && value.links.related;
}
/**
This is the baseClass for the different References
like RecordReference/HasManyReference/BelongsToReference
@class Reference
*/
class Reference {
constructor(store, internalModel) {
this.store = store;
this.internalModel = internalModel;
_defineProperty(this, "recordData", void 0);
this.recordData = (0, _recordDataFor.default)(this);
}
_resource() {}
/**
This returns a string that represents how the reference will be
looked up when it is loaded. If the relationship has a link it will
use the "link" otherwise it defaults to "id".
Example
```app/models/post.js
import Model, { hasMany } from '@ember-data/model';
export default Model.extend({
comments: hasMany({ async: true })
});
```
```javascript
let post = store.push({
data: {
type: 'post',
id: 1,
relationships: {
comments: {
data: [{ type: 'comment', id: 1 }]
}
}
}
});
let commentsRef = post.hasMany('comments');
// get the identifier of the reference
if (commentsRef.remoteType() === "ids") {
let ids = commentsRef.ids();
} else if (commentsRef.remoteType() === "link") {
let link = commentsRef.link();
}
```
@method remoteType
@return {String} The name of the remote type. This should either be "link" or "ids"
*/
remoteType() {
let value = this._resource();
if (isResourceIdentiferWithRelatedLinks(value)) {
return 'link';
}
return 'id';
}
/**
The link Ember Data will use to fetch or reload this belongs-to
relationship.
Example
```javascript
// models/blog.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
links: {
related: '/articles/1/author'
}
}
}
}
});
let userRef = blog.belongsTo('user');
// get the identifier of the reference
if (userRef.remoteType() === "link") {
let link = userRef.link();
}
```
@method link
@return {String} The link Ember Data will use to fetch or reload this belongs-to relationship.
*/
link() {
let link = null;
let resource = this._resource();
if (isResourceIdentiferWithRelatedLinks(resource)) {
if (resource.links) {
link = resource.links.related;
}
}
return link;
}
/**
The meta data for the belongs-to relationship.
Example
```javascript
// models/blog.js
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
user: belongsTo({ async: true })
});
let blog = store.push({
data: {
type: 'blog',
id: 1,
relationships: {
user: {
links: {
related: {
href: '/articles/1/author',
meta: {
lastUpdated: 1458014400000
}
}
}
}
}
}
});
let userRef = blog.belongsTo('user');
userRef.meta() // { lastUpdated: 1458014400000 }
```
@method meta
@return {Object} The meta information for the belongs-to relationship.
*/
meta() {
let meta = null;
let resource = this._resource();
if (resource && resource.meta && typeof resource.meta === 'object') {
meta = resource.meta;
}
return meta;
}
}
_exports.default = Reference;
});
define("@ember-data/store/-private/system/relationships/ext", ["exports", "@ember-data/store/-private/system/relationship-meta"], function (_exports, _relationshipMeta) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.relationshipsByNameDescriptor = _exports.relationshipsObjectDescriptor = _exports.relatedTypesDescriptor = _exports.relationshipsDescriptor = void 0;
/**
@module @ember-data/store
*/
const relationshipsDescriptor = Ember.computed(function () {
let map = new Map();
let relationshipsByName = Ember.get(this, 'relationshipsByName'); // Loop through each computed property on the class
relationshipsByName.forEach(desc => {
let {
type
} = desc;
if (!map.has(type)) {
map.set(type, []);
}
map.get(type).push(desc);
});
return map;
}).readOnly();
_exports.relationshipsDescriptor = relationshipsDescriptor;
const relatedTypesDescriptor = Ember.computed(function () {
let parentModelName = this.modelName;
let types = Ember.A(); // Loop through each computed property on the class,
// and create an array of the unique types involved
// in relationships
this.eachComputedProperty((name, meta) => {
if (meta.isRelationship) {
meta.key = name;
let modelName = (0, _relationshipMeta.typeForRelationshipMeta)(meta);
(true && Ember.assert("You specified a hasMany (".concat(meta.type, ") on ").concat(parentModelName, " but ").concat(meta.type, " was not found."), modelName));
if (!types.includes(modelName)) {
(true && Ember.assert("Trying to sideload ".concat(name, " on ").concat(this.toString(), " but the type doesn't exist."), !!modelName));
types.push(modelName);
}
}
});
return types;
}).readOnly();
_exports.relatedTypesDescriptor = relatedTypesDescriptor;
const relationshipsObjectDescriptor = Ember.computed(function () {
let relationships = Object.create(null);
let modelName = this.modelName;
this.eachComputedProperty((name, meta) => {
if (meta.isRelationship) {
meta.key = name;
meta.name = name;
meta.parentModelName = modelName;
relationships[name] = (0, _relationshipMeta.relationshipFromMeta)(meta);
}
});
return relationships;
});
_exports.relationshipsObjectDescriptor = relationshipsObjectDescriptor;
const relationshipsByNameDescriptor = Ember.computed(function () {
let map = new Map();
let rels = Ember.get(this, 'relationshipsObject');
let relationships = Object.keys(rels);
for (let i = 0; i < relationships.length; i++) {
let key = relationships[i];
let value = rels[key];
map.set(value.key, value);
}
return map;
}).readOnly();
_exports.relationshipsByNameDescriptor = relationshipsByNameDescriptor;
});
define("@ember-data/store/-private/system/store/common", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports._bind = _bind;
_exports._guard = _guard;
_exports._objectIsAlive = _objectIsAlive;
_exports.guardDestroyedStore = guardDestroyedStore;
/**
@module @ember-data/store
*/
function _bind(fn, ...args) {
return function () {
return fn.apply(undefined, args);
};
}
function _guard(promise, test) {
let guarded = promise.finally(() => {
if (!test()) {
guarded._subscribers.length = 0;
}
});
return guarded;
}
function _objectIsAlive(object) {
return !(Ember.get(object, 'isDestroyed') || Ember.get(object, 'isDestroying'));
}
function guardDestroyedStore(promise, store, label) {
let token;
if (true
/* DEBUG */
) {
token = store._trackAsyncRequestStart(label);
}
let wrapperPromise = Ember.RSVP.resolve(promise, label).then(v => promise);
return _guard(wrapperPromise, () => {
if (true
/* DEBUG */
) {
store._trackAsyncRequestEnd(token);
}
return _objectIsAlive(store);
});
}
});
define("@ember-data/store/-private/system/store/finders", ["exports", "@ember-data/store/-private/system/coerce-id", "@ember-data/store/-private/system/store/common", "@ember-data/store/-private/system/store/serializer-response", "@ember-data/store/-private/system/store/serializers", "@ember-data/canary-features"], function (_exports, _coerceId, _common, _serializerResponse, _serializers, _canaryFeatures) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports._find = _find;
_exports._findMany = _findMany;
_exports._findHasMany = _findHasMany;
_exports._findBelongsTo = _findBelongsTo;
_exports._findAll = _findAll;
_exports._query = _query;
_exports._queryRecord = _queryRecord;
/**
@module @ember-data/store
*/
function payloadIsNotBlank(adapterPayload) {
if (Array.isArray(adapterPayload)) {
return true;
} else {
return Object.keys(adapterPayload || {}).length;
}
}
function _find(adapter, store, modelClass, id, internalModel, options) {
if (_canaryFeatures.REQUEST_SERVICE) {// assert here
}
let snapshot = internalModel.createSnapshot(options);
let {
modelName
} = internalModel;
let promise = Ember.RSVP.Promise.resolve().then(() => {
return adapter.findRecord(store, modelClass, id, snapshot);
});
let label = "DS: Handle Adapter#findRecord of '".concat(modelName, "' with id: '").concat(id, "'");
const {
identifier
} = internalModel;
promise = (0, _common.guardDestroyedStore)(promise, store, label);
return promise.then(adapterPayload => {
(true && Ember.assert("You made a 'findRecord' request for a '".concat(modelName, "' with id '").concat(id, "', but the adapter's response did not have any data"), payloadIsNotBlank(adapterPayload)));
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, id, 'findRecord');
(true && Ember.assert("Ember Data expected the primary data returned from a 'findRecord' response to be an object but instead it found an array.", !Array.isArray(payload.data)));
(true && Ember.warn("You requested a record of type '".concat(modelName, "' with id '").concat(id, "' but the adapter returned a payload with primary data having an id of '").concat(payload.data.id, "'. Use 'store.findRecord()' when the requested id is the same as the one returned by the adapter. In other cases use 'store.queryRecord()' instead https://emberjs.com/api/data/classes/DS.Store.html#method_queryRecord"), (0, _coerceId.default)(payload.data.id) === (0, _coerceId.default)(id), {
id: 'ds.store.findRecord.id-mismatch'
}));
if (_canaryFeatures.IDENTIFIERS) {
// ensure that regardless of id returned we assign to the correct record
payload.data.lid = identifier.lid;
}
return store._push(payload);
}, error => {
internalModel.notFound();
if (internalModel.isEmpty()) {
internalModel.unloadRecord();
}
throw error;
}, "DS: Extract payload of '".concat(modelName, "'"));
}
function _findMany(adapter, store, modelName, ids, internalModels, optionsMap) {
let snapshots = Ember.A(internalModels.map(internalModel => internalModel.createSnapshot(optionsMap.get(internalModel))));
let modelClass = store.modelFor(modelName); // `adapter.findMany` gets the modelClass still
let promise = adapter.findMany(store, modelClass, ids, snapshots);
let label = "DS: Handle Adapter#findMany of '".concat(modelName, "'");
if (promise === undefined) {
throw new Error('adapter.findMany returned undefined, this was very likely a mistake');
}
promise = (0, _common.guardDestroyedStore)(promise, store, label);
return promise.then(adapterPayload => {
(true && Ember.assert("You made a 'findMany' request for '".concat(modelName, "' records with ids '[").concat(ids, "]', but the adapter's response did not have any data"), payloadIsNotBlank(adapterPayload)));
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, null, 'findMany');
return store._push(payload);
}, null, "DS: Extract payload of ".concat(modelName));
}
function iterateData(data, fn) {
if (Array.isArray(data)) {
return data.map(fn);
} else {
return fn(data);
}
} // sync
// iterate over records in payload.data
// for each record
// assert that record.relationships[inverse] is either undefined (so we can fix it)
// or provide a data: {id, type} that matches the record that requested it
// return the relationship data for the parent
function syncRelationshipDataFromLink(store, payload, parentInternalModel, relationship) {
// ensure the right hand side (incoming payload) points to the parent record that
// requested this relationship
let relationshipData = iterateData(payload.data, (data, index) => {
const {
id,
type
} = data;
ensureRelationshipIsSetToParent(data, parentInternalModel, store, relationship, index);
return {
id,
type
};
}); // now, push the left hand side (the parent record) to ensure things are in sync, since
// the payload will be pushed with store._push
store.push({
data: {
id: parentInternalModel.id,
type: parentInternalModel.modelName,
relationships: {
[relationship.key]: {
data: relationshipData
}
}
}
});
}
function ensureRelationshipIsSetToParent(payload, parentInternalModel, store, parentRelationship, index) {
let {
id,
type
} = payload;
if (!payload.relationships) {
payload.relationships = {};
}
let {
relationships
} = payload;
let inverse = getInverse(store, parentInternalModel, parentRelationship, type);
if (inverse) {
let {
inverseKey,
kind
} = inverse;
let relationshipData = relationships[inverseKey] && relationships[inverseKey].data;
if (true
/* DEBUG */
&& typeof relationshipData !== 'undefined' && !relationshipDataPointsToParent(relationshipData, parentInternalModel)) {
let quotedType = Ember.inspect(type);
let quotedInverse = Ember.inspect(inverseKey);
let expected = Ember.inspect({
id: parentInternalModel.id,
type: parentInternalModel.modelName
});
let expectedModel = Ember.inspect(parentInternalModel);
let got = Ember.inspect(relationshipData);
let prefix = typeof index === 'number' ? "data[".concat(index, "]") : "data";
let path = "".concat(prefix, ".relationships.").concat(inverse, ".data");
let other = relationshipData ? "<".concat(relationshipData.type, ":").concat(relationshipData.id, ">") : null;
let relationshipFetched = "".concat(Ember.inspect(parentInternalModel), ".").concat(parentRelationship.kind, "(\"").concat(parentRelationship.name, "\")");
let includedRecord = "<".concat(type, ":").concat(id, ">");
let message = ["Encountered mismatched relationship: Ember Data expected ".concat(path, " in the payload from ").concat(relationshipFetched, " to include ").concat(expected, " but got ").concat(got, " instead.\n"), "The ".concat(includedRecord, " record loaded at ").concat(prefix, " in the payload specified ").concat(other, " as its ").concat(quotedInverse, ", but should have specified ").concat(expectedModel, " (the record the relationship is being loaded from) as its ").concat(quotedInverse, " instead."), "This could mean that the response for ".concat(relationshipFetched, " may have accidentally returned ").concat(quotedType, " records that aren't related to ").concat(expectedModel, " and could be related to a different ").concat(parentInternalModel.modelName, " record instead."), "Ember Data has corrected the ".concat(includedRecord, " record's ").concat(quotedInverse, " relationship to ").concat(expectedModel, " so that ").concat(relationshipFetched, " will include ").concat(includedRecord, "."), "Please update the response from the server or change your serializer to either ensure that the response for only includes ".concat(quotedType, " records that specify ").concat(expectedModel, " as their ").concat(quotedInverse, ", or omit the ").concat(quotedInverse, " relationship from the response.")].join('\n'); // this should eventually throw instead of deprecating.
(true && !(false) && Ember.deprecate(message + '\n', false, {
id: 'mismatched-inverse-relationship-data-from-payload',
until: '3.8'
}));
}
if (kind !== 'hasMany' || typeof relationshipData !== 'undefined') {
relationships[inverseKey] = relationships[inverseKey] || {};
relationships[inverseKey].data = fixRelationshipData(relationshipData, kind, parentInternalModel);
}
}
}
function getInverse(store, parentInternalModel, parentRelationship, type) {
return recordDataFindInverseRelationshipInfo(store, parentInternalModel, parentRelationship, type);
}
function recordDataFindInverseRelationshipInfo({
_storeWrapper
}, parentInternalModel, parentRelationship, type) {
let {
name: lhs_relationshipName
} = parentRelationship;
let {
modelName
} = parentInternalModel;
let inverseKey = _storeWrapper.inverseForRelationship(modelName, lhs_relationshipName);
if (inverseKey) {
let {
meta: {
kind
}
} = _storeWrapper.relationshipsDefinitionFor(type)[inverseKey];
return {
inverseKey,
kind
};
}
}
function relationshipDataPointsToParent(relationshipData, internalModel) {
if (relationshipData === null) {
return false;
}
if (Array.isArray(relationshipData)) {
if (relationshipData.length === 0) {
return false;
}
for (let i = 0; i < relationshipData.length; i++) {
let entry = relationshipData[i];
if (validateRelationshipEntry(entry, internalModel)) {
return true;
}
}
} else {
return validateRelationshipEntry(relationshipData, internalModel);
}
return false;
}
function fixRelationshipData(relationshipData, relationshipKind, {
id,
modelName
}) {
let parentRelationshipData = {
id,
type: modelName
};
let payload;
if (relationshipKind === 'hasMany') {
payload = relationshipData || [];
payload.push(parentRelationshipData);
} else {
payload = relationshipData || {};
Ember.assign(payload, parentRelationshipData);
}
return payload;
}
function validateRelationshipEntry({
id
}, {
id: parentModelID
}) {
return id && id.toString() === parentModelID;
}
function _findHasMany(adapter, store, internalModel, link, relationship, options) {
let snapshot = internalModel.createSnapshot(options);
let modelClass = store.modelFor(relationship.type);
let promise = adapter.findHasMany(store, snapshot, link, relationship);
let label = "DS: Handle Adapter#findHasMany of '".concat(internalModel.modelName, "' : '").concat(relationship.type, "'");
promise = (0, _common.guardDestroyedStore)(promise, store, label);
promise = (0, _common._guard)(promise, (0, _common._bind)(_common._objectIsAlive, internalModel));
return promise.then(adapterPayload => {
(true && Ember.assert("You made a 'findHasMany' request for a ".concat(internalModel.modelName, "'s '").concat(relationship.key, "' relationship, using link '").concat(link, "' , but the adapter's response did not have any data"), payloadIsNotBlank(adapterPayload)));
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, relationship.type);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, null, 'findHasMany');
syncRelationshipDataFromLink(store, payload, internalModel, relationship);
let internalModelArray = store._push(payload);
internalModelArray.meta = payload.meta;
return internalModelArray;
}, null, "DS: Extract payload of '".concat(internalModel.modelName, "' : hasMany '").concat(relationship.type, "'"));
}
function _findBelongsTo(adapter, store, internalModel, link, relationship, options) {
let snapshot = internalModel.createSnapshot(options);
let modelClass = store.modelFor(relationship.type);
let promise = adapter.findBelongsTo(store, snapshot, link, relationship);
let label = "DS: Handle Adapter#findBelongsTo of ".concat(internalModel.modelName, " : ").concat(relationship.type);
promise = (0, _common.guardDestroyedStore)(promise, store, label);
promise = (0, _common._guard)(promise, (0, _common._bind)(_common._objectIsAlive, internalModel));
return promise.then(adapterPayload => {
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, relationship.type);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, null, 'findBelongsTo');
if (!payload.data) {
return null;
}
syncRelationshipDataFromLink(store, payload, internalModel, relationship);
return store._push(payload);
}, null, "DS: Extract payload of ".concat(internalModel.modelName, " : ").concat(relationship.type));
}
function _findAll(adapter, store, modelName, options) {
let modelClass = store.modelFor(modelName); // adapter.findAll depends on the class
let recordArray = store.peekAll(modelName);
let snapshotArray = recordArray._createSnapshot(options);
let promise = Ember.RSVP.Promise.resolve().then(() => adapter.findAll(store, modelClass, null, snapshotArray));
let label = 'DS: Handle Adapter#findAll of ' + modelClass;
promise = (0, _common.guardDestroyedStore)(promise, store, label);
return promise.then(adapterPayload => {
(true && Ember.assert("You made a 'findAll' request for '".concat(modelName, "' records, but the adapter's response did not have any data"), payloadIsNotBlank(adapterPayload)));
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, null, 'findAll');
store._push(payload);
store._didUpdateAll(modelName);
return recordArray;
}, null, 'DS: Extract payload of findAll ${modelName}');
}
function _query(adapter, store, modelName, query, recordArray, options) {
let modelClass = store.modelFor(modelName); // adapter.query needs the class
recordArray = recordArray || store.recordArrayManager.createAdapterPopulatedRecordArray(modelName, query);
let promise = Ember.RSVP.Promise.resolve().then(() => adapter.query(store, modelClass, query, recordArray, options));
let label = "DS: Handle Adapter#query of ".concat(modelName);
promise = (0, _common.guardDestroyedStore)(promise, store, label);
return promise.then(adapterPayload => {
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, null, 'query');
let internalModels = store._push(payload);
(true && Ember.assert('The response to store.query is expected to be an array but it was a single record. Please wrap your response in an array or use `store.queryRecord` to query for a single record.', Array.isArray(internalModels)));
if (recordArray) {
recordArray._setInternalModels(internalModels, payload);
} else {
recordArray = store.recordArrayManager.createAdapterPopulatedRecordArray(modelName, query, internalModels, payload);
}
return recordArray;
}, null, "DS: Extract payload of query ".concat(modelName));
}
function _queryRecord(adapter, store, modelName, query, options) {
let modelClass = store.modelFor(modelName); // adapter.queryRecord needs the class
let promise = Ember.RSVP.Promise.resolve().then(() => adapter.queryRecord(store, modelClass, query, options));
let label = "DS: Handle Adapter#queryRecord of ".concat(modelName);
promise = (0, _common.guardDestroyedStore)(promise, store, label);
return promise.then(adapterPayload => {
let serializer = (0, _serializers.serializerForAdapter)(store, adapter, modelName);
let payload = (0, _serializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, null, 'queryRecord');
(true && Ember.assert("Expected the primary data returned by the serializer for a 'queryRecord' response to be a single object or null but instead it was an array.", !Array.isArray(payload.data), {
id: 'ds.store.queryRecord-array-response'
}));
return store._push(payload);
}, null, "DS: Extract payload of queryRecord ".concat(modelName));
}
});
define("@ember-data/store/-private/system/store/internal-model-factory", ["exports", "@ember-data/store/-private/system/coerce-id", "@ember-data/store/-private/identifiers/cache", "@ember-data/store/-private/system/model/internal-model", "@ember-data/store/-private/system/identity-map", "@ember-data/canary-features", "@ember-data/store/-private/utils/has-valid-id"], function (_exports, _coerceId, _cache, _internalModel, _identityMap, _canaryFeatures, _hasValidId) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.recordIdentifierFor = recordIdentifierFor;
_exports.setRecordIdentifier = setRecordIdentifier;
_exports.internalModelFactoryFor = internalModelFactoryFor;
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
const FactoryCache = new WeakMap();
const RecordCache = new WeakMap();
function recordIdentifierFor(record) {
let identifier = RecordCache.get(record);
if (true
/* DEBUG */
&& identifier === undefined) {
throw new Error("".concat(record, " is not a record instantiated by @ember-data/store"));
}
return identifier;
}
function setRecordIdentifier(record, identifier) {
if (true
/* DEBUG */
&& RecordCache.has(record)) {
throw new Error("".concat(record, " was already assigned an identifier"));
}
/*
It would be nice to do a reverse check here that an identifier has not
previously been assigned a record; however, unload + rematerialization
prevents us from having a great way of doing so when CustomRecordClasses
don't necessarily give us access to a `isDestroyed` for dematerialized
instance.
*/
RecordCache.set(record, identifier);
}
function internalModelFactoryFor(store) {
let factory = FactoryCache.get(store);
if (factory === undefined) {
factory = new InternalModelFactory(store);
FactoryCache.set(store, factory);
}
return factory;
}
/**
* The InternalModelFactory handles the lifecyle of
* instantiating, caching, and destroying InternalModel
* instances.
*
* @internal
*/
class InternalModelFactory {
constructor(store) {
this.store = store;
_defineProperty(this, "_identityMap", void 0);
_defineProperty(this, "_newlyCreated", void 0);
_defineProperty(this, "identifierCache", void 0);
this.identifierCache = (0, _cache.identifierCacheFor)(store);
this.identifierCache.__configureMerge((identifier, matchedIdentifier, resourceData) => {
const intendedIdentifier = identifier.id === resourceData.id ? identifier : matchedIdentifier;
const altIdentifier = identifier.id === resourceData.id ? matchedIdentifier : identifier; // check for duplicate InternalModel's
const map = this.modelMapFor(identifier.type);
let im = map.get(intendedIdentifier.lid);
let otherIm = map.get(altIdentifier.lid); // we cannot merge internalModels when both have records
// (this may not be strictly true, we could probably swap the internalModel the record points at)
if (im && otherIm && im.hasRecord && otherIm.hasRecord) {
throw new Error("Failed to update the 'id' for the RecordIdentifier '".concat(identifier, "' to '").concat(resourceData.id, "', because that id is already in use by '").concat(matchedIdentifier, "'"));
} // remove otherIm from cache
if (otherIm) {
map.remove(otherIm, altIdentifier.lid);
}
if (im === null && otherIm === null) {
// nothing more to do
return intendedIdentifier; // only the other has an InternalModel
// OR only the other has a Record
} else if (im === null && otherIm !== null || im && !im.hasRecord && otherIm && otherIm.hasRecord) {
if (im) {
// TODO check if we are retained in any async relationships
map.remove(im, intendedIdentifier.lid); // im.destroy();
}
im = otherIm; // TODO do we need to notify the id change?
im._id = intendedIdentifier.id;
map.add(im, intendedIdentifier.lid); // just use im
} else {// otherIm.destroy();
}
return intendedIdentifier;
});
this._identityMap = new _identityMap.default();
if (!_canaryFeatures.IDENTIFIERS) {
this._newlyCreated = new _identityMap.default();
}
}
/**
* Retrieve the InternalModel for a given { type, id, lid }.
*
* If an InternalModel does not exist, it instantiates one.
*
* If an InternalModel does exist bus has a scheduled destroy,
* the scheduled destroy will be cancelled.
*
* @internal
*/
lookup(modelName, id, clientId, data) {
if (_canaryFeatures.IDENTIFIERS && data !== undefined) {
// if we've been given data associated with this lookup
// we must first give secondary-caches for LIDs the
// opportunity to populate based on it
this.identifierCache.getOrCreateRecordIdentifier(data);
}
let trueId = id === null ? null : (0, _coerceId.default)(id);
if (!(0, _hasValidId.default)(trueId, clientId)) {
throw new Error("Either id or clientId must be a valid id");
}
let internalModel = this.peek(modelName, trueId, clientId);
if (internalModel) {
// unloadRecord is async, if one attempts to unload + then sync push,
// we must ensure the unload is canceled before continuing
// The createRecord path will take _existingInternalModelForId()
// which will call `destroySync` instead for this unload + then
// sync createRecord scenario. Once we have true client-side
// delete signaling, we should never call destroySync
if (internalModel.hasScheduledDestroy()) {
internalModel.cancelDestroy();
}
return internalModel;
}
return this._build(modelName, trueId, clientId, false);
}
/**
* Peek the InternalModel for a given { type, id, lid }.
*
* If an InternalModel does not exist, return `null`.
*
* @internal
*/
peek(modelName, id, clientId) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error("Either id or clientId must be a valid id");
}
if (_canaryFeatures.IDENTIFIERS) {
let resource = {
type: modelName,
id
};
if (clientId) {
resource.lid = clientId;
}
const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
return this.modelMapFor(modelName).get(identifier.lid);
} else {
let internalModel = null;
if (clientId) {
internalModel = this._newlyCreatedModelsFor(modelName).get(clientId);
}
if (!internalModel && id) {
internalModel = this.modelMapFor(modelName).get(id);
}
return internalModel;
}
}
getByResource(resource) {
if (_canaryFeatures.IDENTIFIERS) {
if (!(0, _hasValidId.default)(resource.id, resource.lid)) {
throw new Error("Either id or lid must be a valid id");
}
return this.lookup(resource.type, resource.id, resource.lid || null);
} else {
let res = resource;
let internalModel = null;
if (res.clientId) {
internalModel = this._newlyCreatedModelsFor(resource.type).get(res.clientId);
}
if (internalModel === null) {
internalModel = this.lookup(res.type, res.id, resource.lid);
}
return internalModel;
}
}
setRecordId(type, id, lid) {
const internalModel = this.peek(type, id, lid);
if (internalModel === null) {
throw new Error("Cannot set the id ".concat(id, " on the record ").concat(type, ":").concat(lid, " as there is no such record in the cache."));
}
let oldId = internalModel.id;
let modelName = internalModel.modelName; // ID absolutely can't be missing if the oldID is empty (missing Id in response for a new record)
(true && Ember.assert("'".concat(modelName, "' was saved to the server, but the response does not have an id and your record does not either."), !(id === null && oldId === null))); // ID absolutely can't be different than oldID if oldID is not null
// TODO this assertion and restriction may not strictly be needed in the identifiers world
(true && Ember.assert("Cannot update the id for '".concat(modelName, ":").concat(lid, "' from '").concat(oldId, "' to '").concat(id, "'."), !(oldId !== null && id !== oldId))); // ID can be null if oldID is not null (altered ID in response for a record)
// however, this is more than likely a developer error.
if (oldId !== null && id === null) {
(true && Ember.warn("Your ".concat(modelName, " record was saved to the server, but the response does not have an id."), !(oldId !== null && id === null)));
return;
}
let existingInternalModel = this.peekById(modelName, id);
(true && Ember.assert("'".concat(modelName, "' was saved to the server, but the response returned the new id '").concat(id, "', which has already been used with another record.'"), Ember.isNone(existingInternalModel) || existingInternalModel === internalModel));
if (!_canaryFeatures.IDENTIFIERS) {
this.modelMapFor(internalModel.modelName).set(id, internalModel);
this._newlyCreatedModelsFor(internalModel.modelName).remove(internalModel, lid);
}
const identifier = this.identifierCache.getOrCreateRecordIdentifier({
type: modelName,
id,
lid
});
if (identifier.id === null) {
this.identifierCache.updateRecordIdentifier(identifier, {
type: modelName,
id
});
}
internalModel.setId(id);
}
peekById(modelName, id) {
const identifier = this.identifierCache.peekRecordIdentifier({
type: modelName,
id
});
let internalModel;
if (_canaryFeatures.IDENTIFIERS) {
internalModel = identifier ? this.modelMapFor(modelName).get(identifier.lid) : null;
} else {
internalModel = this.modelMapFor(modelName).get(id);
}
if (internalModel && internalModel.hasScheduledDestroy()) {
// unloadRecord is async, if one attempts to unload + then sync create,
// we must ensure the unload is complete before starting the create
// The push path will take this.lookup()
// which will call `cancelDestroy` instead for this unload + then
// sync push scenario. Once we have true client-side
// delete signaling, we should never call destroySync
internalModel.destroySync();
internalModel = null;
}
return internalModel;
}
build(modelName, id) {
return this._build(modelName, id, null, true);
}
_build(type, id, lid, isCreate = false) {
if (id) {
let existingInternalModel = this.peekById(type, id);
(true && Ember.assert("The id ".concat(id, " has already been used with another record for modelClass '").concat(type, "'."), !existingInternalModel));
}
const {
identifierCache
} = this;
let identifier;
if (isCreate === true) {
identifier = identifierCache.createIdentifierForNewRecord({
type,
id
});
} else {
let resource = {
type,
id: id
};
if (lid) {
resource.lid = lid;
}
identifier = identifierCache.getOrCreateRecordIdentifier(resource);
} // lookupFactory should really return an object that creates
// instances with the injections applied
let internalModel = new _internalModel.default(this.store, identifier);
if (_canaryFeatures.IDENTIFIERS) {
this.modelMapFor(type).add(internalModel, identifier.lid);
} else {
if (isCreate === true) {
this._newlyCreatedModelsFor(identifier.type).add(internalModel, identifier.lid);
} // TODO @runspired really?!
this.modelMapFor(type).add(internalModel, identifier.id);
}
return internalModel;
}
remove(internalModel) {
let recordMap = this.modelMapFor(internalModel.modelName);
let clientId = internalModel.identifier.lid;
if (_canaryFeatures.IDENTIFIERS) {
recordMap.remove(internalModel, clientId);
} else {
if (internalModel.id) {
recordMap.remove(internalModel, internalModel.id);
}
this._newlyCreatedModelsFor(internalModel.modelName).remove(internalModel, clientId);
}
const {
identifier
} = internalModel;
this.identifierCache.forgetRecordIdentifier(identifier);
}
modelMapFor(modelName) {
return this._identityMap.retrieve(modelName);
}
_newlyCreatedModelsFor(modelName) {
return this._newlyCreated.retrieve(modelName);
}
clear(modelName) {
if (modelName === undefined) {
this._identityMap.clear();
} else {
this.modelMapFor(modelName).clear();
}
}
}
_exports.default = InternalModelFactory;
});
define("@ember-data/store/-private/system/store/record-data-store-wrapper", ["exports", "@ember-data/store/-private/ts-interfaces/utils/brand", "@ember-data/store/-private/system/ts-upgrade-map", "@ember-data/store/-private/system/store/internal-model-factory", "@ember-data/store/-private/utils/has-valid-id", "@ember-data/canary-features", "@ember-data/store/-private/identifiers/cache"], function (_exports, _brand, _tsUpgradeMap, _internalModelFactory, _hasValidId, _canaryFeatures, _cache) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const MISSING_ID_ARG_ERROR_MESSAGE = "Either an id or a clientId is required as an argument.";
class RecordDataStoreWrapper {
constructor(store) {
_defineProperty(this, _brand.BRAND_SYMBOL, void 0);
_defineProperty(this, "_store", void 0);
_defineProperty(this, "_willUpdateManyArrays", void 0);
_defineProperty(this, "_pendingManyArrayUpdates", void 0);
this._store = store;
this._willUpdateManyArrays = false;
this._pendingManyArrayUpdates = [];
}
get identifierCache() {
if (!_canaryFeatures.IDENTIFIERS) {
throw new Error("Store.identifierCache is unavailable in this build of EmberData");
}
return (0, _cache.identifierCacheFor)(this._store);
}
/**
* Exists so that DefaultRecordData can check for model types
* in DEBUG for relationships. Should be refactored away.
*
* @internal
*/
_hasModelFor(modelName) {
return this._store._hasModelFor(modelName);
}
/**
* @internal
*/
_scheduleManyArrayUpdate(modelName, id, clientId, key) {
let pending = this._pendingManyArrayUpdates = this._pendingManyArrayUpdates || [];
pending.push(modelName, id, clientId, key);
if (this._willUpdateManyArrays === true) {
return;
}
this._willUpdateManyArrays = true;
let backburner = this._store._backburner;
backburner.join(() => {
backburner.schedule('syncRelationships', this, this._flushPendingManyArrayUpdates);
});
}
notifyErrorsChange(modelName, id, clientId) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error(MISSING_ID_ARG_ERROR_MESSAGE);
}
let internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this._store).peek(modelName, id, clientId);
if (internalModel) {
internalModel.notifyErrorsChange();
}
}
_flushPendingManyArrayUpdates() {
if (this._willUpdateManyArrays === false) {
return;
}
let pending = this._pendingManyArrayUpdates;
this._pendingManyArrayUpdates = [];
this._willUpdateManyArrays = false;
const factory = (0, _internalModelFactory.internalModelFactoryFor)(this._store);
for (let i = 0; i < pending.length; i += 4) {
let modelName = pending[i];
let id = pending[i + 1] || null;
let clientId = pending[i + 2];
let key = pending[i + 3];
let internalModel = factory.peek(modelName, id, clientId);
if (internalModel) {
internalModel.notifyHasManyChange(key);
}
}
}
attributesDefinitionFor(modelName) {
return this._store._attributesDefinitionFor(modelName);
}
relationshipsDefinitionFor(modelName) {
return this._store._relationshipsDefinitionFor(modelName);
}
inverseForRelationship(modelName, key) {
const modelClass = this._store.modelFor(modelName);
const definition = (0, _tsUpgradeMap.upgradeForInternal)(this.relationshipsDefinitionFor(modelName)[key]);
return definition._inverseKey(this._store, modelClass);
}
inverseIsAsyncForRelationship(modelName, key) {
const modelClass = this._store.modelFor(modelName);
const definition = (0, _tsUpgradeMap.upgradeForInternal)(this.relationshipsDefinitionFor(modelName)[key]);
return definition._inverseIsAsync(this._store, modelClass);
}
notifyPropertyChange(modelName, id, clientId, key) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error(MISSING_ID_ARG_ERROR_MESSAGE);
}
let internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this._store).peek(modelName, id, clientId);
if (internalModel) {
internalModel.notifyPropertyChange(key);
}
}
notifyHasManyChange(modelName, id, clientId, key) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error(MISSING_ID_ARG_ERROR_MESSAGE);
}
this._scheduleManyArrayUpdate(modelName, id, clientId, key);
}
notifyBelongsToChange(modelName, id, clientId, key) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error(MISSING_ID_ARG_ERROR_MESSAGE);
}
let internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this._store).peek(modelName, id, clientId);
if (internalModel) {
internalModel.notifyBelongsToChange(key);
}
}
notifyStateChange(modelName, id, clientId, key) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error(MISSING_ID_ARG_ERROR_MESSAGE);
}
let internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this._store).peek(modelName, id, clientId);
if (internalModel) {
internalModel.notifyStateChange(key);
}
}
recordDataFor(modelName, id, clientId) {
return this._store.recordDataFor(modelName, id, clientId);
}
setRecordId(modelName, id, clientId) {
this._store.setRecordId(modelName, id, clientId);
}
isRecordInUse(modelName, id, clientId) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error(MISSING_ID_ARG_ERROR_MESSAGE);
}
let internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this._store).peek(modelName, id, clientId);
if (!internalModel) {
return false;
}
return internalModel.isRecordInUse();
}
disconnectRecord(modelName, id, clientId) {
if (!(0, _hasValidId.default)(id, clientId)) {
throw new Error(MISSING_ID_ARG_ERROR_MESSAGE);
}
let internalModel = (0, _internalModelFactory.internalModelFactoryFor)(this._store).peek(modelName, id, clientId);
if (internalModel) {
internalModel.destroyFromRecordData();
}
}
}
_exports.default = RecordDataStoreWrapper;
});
define("@ember-data/store/-private/system/store/serializer-response", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.validateDocumentStructure = validateDocumentStructure;
_exports.normalizeResponseHelper = normalizeResponseHelper;
/**
@module @ember-data/store
*/
/*
This is a helper method that validates a JSON API top-level document
The format of a document is described here:
http://jsonapi.org/format/#document-top-level
@method validateDocumentStructure
@param {Object} doc JSON API document
@return {array} An array of errors found in the document structure
*/
function validateDocumentStructure(doc) {
let errors = [];
if (!doc || typeof doc !== 'object') {
errors.push('Top level of a JSON API document must be an object');
} else {
if (!('data' in doc) && !('errors' in doc) && !('meta' in doc)) {
errors.push('One or more of the following keys must be present: "data", "errors", "meta".');
} else {
if ('data' in doc && 'errors' in doc) {
errors.push('Top level keys "errors" and "data" cannot both be present in a JSON API document');
}
}
if ('data' in doc) {
if (!(doc.data === null || Array.isArray(doc.data) || typeof doc.data === 'object')) {
errors.push('data must be null, an object, or an array');
}
}
if ('meta' in doc) {
if (typeof doc.meta !== 'object') {
errors.push('meta must be an object');
}
}
if ('errors' in doc) {
if (!Array.isArray(doc.errors)) {
errors.push('errors must be an array');
}
}
if ('links' in doc) {
if (typeof doc.links !== 'object') {
errors.push('links must be an object');
}
}
if ('jsonapi' in doc) {
if (typeof doc.jsonapi !== 'object') {
errors.push('jsonapi must be an object');
}
}
if ('included' in doc) {
if (typeof doc.included !== 'object') {
errors.push('included must be an array');
}
}
}
return errors;
}
/*
This is a helper method that always returns a JSON-API Document.
@method normalizeResponseHelper
@param {Serializer} serializer
@param {Store} store
@param {subclass of Model} modelClass
@param {Object} payload
@param {String|Number} id
@param {String} requestType
@return {Object} JSON-API Document
*/
function normalizeResponseHelper(serializer, store, modelClass, payload, id, requestType) {
let normalizedResponse = serializer.normalizeResponse(store, modelClass, payload, id, requestType);
let validationErrors = [];
if (true
/* DEBUG */
) {
validationErrors = validateDocumentStructure(normalizedResponse);
}
(true && Ember.assert("normalizeResponse must return a valid JSON API document:\n\t* ".concat(validationErrors.join('\n\t* ')), validationErrors.length === 0));
return normalizedResponse;
}
});
define("@ember-data/store/-private/system/store/serializers", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.serializerForAdapter = serializerForAdapter;
/**
@module @ember-data/store
*/
function serializerForAdapter(store, adapter, modelName) {
let serializer = adapter.serializer;
if (serializer === undefined) {
serializer = store.serializerFor(modelName);
}
if (serializer === null || serializer === undefined) {
serializer = {
extract(store, type, payload) {
return payload;
}
};
}
return serializer;
}
});
define("@ember-data/store/-private/ts-interfaces/utils/brand", ["exports", "@ember-data/store/-private/ts-interfaces/utils/symbol"], function (_exports, _symbol) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.BRAND_SYMBOL = void 0;
/**
@module @ember-data/store
*/
/**
* Use this brand to assign a string key to an interface
* for mapping the interface to a tightly coupled internal
* class implementation.
*
* This allows us to expose the interface publicly but
* seamlessly upgrade these interfaces for our own use
* internally when internal methods and properties are
* needed.
*
* @internal
*/
const BRAND_SYMBOL = (0, _symbol.symbol)('DEBUG-ts-brand');
_exports.BRAND_SYMBOL = BRAND_SYMBOL;
});
define("@ember-data/store/-private/ts-interfaces/utils/symbol", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.symbol = void 0;
/**
@module @ember-data/store
*/
/**
* This symbol provides a Symbol replacement for browsers that do not have it
* (eg. IE 11).
*
* The replacement is different from the native Symbol in some ways. It is a
* function that produces an output:
* - iterable;
* - that is a string, not a symbol.
*
* @internal
*/
const symbol = typeof Symbol !== 'undefined' ? Symbol : key => "__".concat(key).concat(Math.floor(Math.random() * Date.now()), "__");
_exports.symbol = symbol;
});
define("@ember-data/store/-private/system/relationships/state/belongs-to", ["exports", "ember-data/-debug", "@ember-data/store/-private/system/relationships/state/relationship"], function (_exports, _debug, _relationship) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
class BelongsToRelationship extends _relationship.default {
constructor(store, inverseKey, relationshipMeta, recordData, inverseIsAsync) {
super(store, inverseKey, relationshipMeta, recordData, inverseIsAsync);
_defineProperty(this, "inverseRecordData", void 0);
_defineProperty(this, "canonicalState", void 0);
_defineProperty(this, "key", void 0);
this.key = relationshipMeta.key;
this.inverseRecordData = null;
this.canonicalState = null;
this.key = relationshipMeta.key;
}
setRecordData(recordData) {
if (recordData) {
this.addRecordData(recordData);
} else if (this.inverseRecordData) {
this.removeRecordData(this.inverseRecordData);
}
this.setHasAnyRelationshipData(true);
this.setRelationshipIsStale(false);
this.setRelationshipIsEmpty(false);
}
setCanonicalRecordData(recordData) {
if (recordData) {
this.addCanonicalRecordData(recordData);
} else if (this.canonicalState) {
this.removeCanonicalRecordData(this.canonicalState);
}
this.flushCanonicalLater();
}
setInitialCanonicalRecordData(recordData) {
if (!recordData) {
return;
} // When we initialize a belongsTo relationship, we want to avoid work like
// notifying our internalModel that we've "changed" and excessive thrash on
// setting up inverse relationships
this.canonicalMembers.add(recordData);
this.members.add(recordData);
this.inverseRecordData = this.canonicalState = recordData;
this.setupInverseRelationship(recordData);
}
addCanonicalRecordData(recordData) {
if (this.canonicalMembers.has(recordData)) {
return;
}
if (this.canonicalState) {
this.removeCanonicalRecordData(this.canonicalState);
}
this.canonicalState = recordData;
super.addCanonicalRecordData(recordData);
this.setHasAnyRelationshipData(true);
this.setRelationshipIsEmpty(false);
}
inverseDidDematerialize() {
super.inverseDidDematerialize(this.inverseRecordData);
this.notifyBelongsToChange();
}
removeCompletelyFromOwn(recordData) {
super.removeCompletelyFromOwn(recordData);
if (this.canonicalState === recordData) {
this.canonicalState = null;
}
if (this.inverseRecordData === recordData) {
this.inverseRecordData = null;
this.notifyBelongsToChange();
}
}
removeCompletelyFromInverse() {
super.removeCompletelyFromInverse();
this.inverseRecordData = null;
}
flushCanonical() {
//temporary fix to not remove newly created records if server returned null.
//TODO remove once we have proper diffing
if (this.inverseRecordData && this.inverseRecordData.isNew() && !this.canonicalState) {
this.willSync = false;
return;
}
if (this.inverseRecordData !== this.canonicalState) {
this.inverseRecordData = this.canonicalState;
this.notifyBelongsToChange();
}
super.flushCanonical();
}
addRecordData(recordData) {
if (this.members.has(recordData)) {
return;
} // TODO Igor cleanup
(0, _debug.assertPolymorphicType)(this.recordData, this.relationshipMeta, recordData, this.store);
if (this.inverseRecordData) {
this.removeRecordData(this.inverseRecordData);
}
this.inverseRecordData = recordData;
super.addRecordData(recordData);
this.notifyBelongsToChange();
}
removeRecordDataFromOwn(recordData) {
if (!this.members.has(recordData)) {
return;
}
this.inverseRecordData = null;
super.removeRecordDataFromOwn(recordData);
this.notifyBelongsToChange();
}
removeAllRecordDatasFromOwn() {
super.removeAllRecordDatasFromOwn();
this.inverseRecordData = null;
this.notifyBelongsToChange();
}
notifyBelongsToChange() {
let recordData = this.recordData;
let storeWrapper = this.recordData.storeWrapper;
storeWrapper.notifyBelongsToChange(recordData.modelName, recordData.id, recordData.clientId, this.key);
}
removeCanonicalRecordDataFromOwn(recordData) {
if (!this.canonicalMembers.has(recordData)) {
return;
}
this.canonicalState = null;
this.setHasAnyRelationshipData(true);
this.setRelationshipIsEmpty(true);
super.removeCanonicalRecordDataFromOwn(recordData);
}
removeAllCanonicalRecordDatasFromOwn() {
super.removeAllCanonicalRecordDatasFromOwn();
this.canonicalState = null;
}
getData() {
let data;
let payload = {};
if (this.inverseRecordData) {
data = this.inverseRecordData.getResourceIdentifier();
}
if (this.inverseRecordData === null && this.hasAnyRelationshipData) {
data = null;
}
if (this.link) {
payload.links = {
related: this.link
};
}
if (data !== undefined) {
payload.data = data;
}
if (this.meta) {
payload.meta = this.meta;
}
payload._relationship = this;
return payload;
}
/**
* Flag indicating whether all inverse records are available
*
* true if the inverse exists and is loaded (not empty)
* true if there is no inverse
* false if the inverse exists and is not loaded (empty)
*
* @return {boolean}
*/
get allInverseRecordsAreLoaded() {
let recordData = this.inverseRecordData;
let isEmpty = recordData !== null && recordData.isEmpty();
return !isEmpty;
}
updateData(data, initial) {
let recordData;
if (Ember.isNone(data)) {
recordData = null;
}
(true && Ember.assert("Ember Data expected the data for the ".concat(this.key, " relationship on a ").concat(this.recordData.toString(), " to be in a JSON API format and include an `id` and `type` property but it found ").concat(Ember.inspect(data), ". Please check your serializer and make sure it is serializing the relationship payload into a JSON API format."), data === null || data.id !== undefined && data.type !== undefined));
if (recordData !== null) {
recordData = this.recordData.storeWrapper.recordDataFor(data.type, data.id);
}
if (initial) {
this.setInitialCanonicalRecordData(recordData);
} else {
this.setCanonicalRecordData(recordData);
}
}
}
_exports.default = BelongsToRelationship;
});
define("@ember-data/store/-private/system/relationships/state/create", ["exports", "@ember-data/store/-private/system/relationships/state/has-many", "@ember-data/store/-private/system/relationships/state/belongs-to", "@ember-data/store/-private/system/ts-upgrade-map"], function (_exports, _hasMany, _belongsTo, _tsUpgradeMap) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
function createRelationshipFor(relationshipMeta, store, recordData, key) {
let inverseKey = recordData.storeWrapper.inverseForRelationship(recordData.modelName, key);
let inverseIsAsync = recordData.storeWrapper.inverseIsAsyncForRelationship(recordData.modelName, key);
if (relationshipMeta.kind === 'hasMany') {
return new _hasMany.default(store, inverseKey, relationshipMeta, recordData, inverseIsAsync);
} else {
return new _belongsTo.default(store, inverseKey, relationshipMeta, recordData, inverseIsAsync);
}
}
class Relationships {
constructor(recordData) {
this.recordData = recordData;
_defineProperty(this, "_store", void 0);
_defineProperty(this, "_storeWrapper", void 0);
_defineProperty(this, "initializedRelationships", void 0);
this.initializedRelationships = Object.create(null);
this._storeWrapper = (0, _tsUpgradeMap.upgradeForInternal)(recordData.storeWrapper);
this._store = this._storeWrapper._store;
}
has(key) {
return !!this.initializedRelationships[key];
}
forEach(cb) {
let rels = this.initializedRelationships;
Object.keys(rels).forEach(name => {
cb(name, rels[name]);
});
}
get(key) {
let relationships = this.initializedRelationships;
let relationship = relationships[key];
if (!relationship) {
let recordData = this.recordData;
let rel = this.recordData.storeWrapper.relationshipsDefinitionFor(this.recordData.modelName)[key];
if (rel) {
relationship = relationships[key] = createRelationshipFor(rel, this._store, recordData, key);
}
}
return relationship;
}
}
_exports.default = Relationships;
});
define("@ember-data/store/-private/system/relationships/state/has-many", ["exports", "ember-data/-debug", "@ember-data/store/-private/system/relationships/state/relationship", "@ember-data/store/-private/system/ordered-set"], function (_exports, _debug, _relationship, _orderedSet) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
@module @ember-data/store
*/
class ManyRelationship extends _relationship.default {
constructor(store, inverseKey, relationshipMeta, recordData, inverseIsAsync) {
super(store, inverseKey, relationshipMeta, recordData, inverseIsAsync);
_defineProperty(this, "canonicalState", void 0);
_defineProperty(this, "currentState", void 0);
_defineProperty(this, "_willUpdateManyArray", void 0);
_defineProperty(this, "_pendingManyArrayUpdates", void 0);
_defineProperty(this, "key", void 0);
this.canonicalState = [];
this.currentState = [];
this._willUpdateManyArray = false;
this._pendingManyArrayUpdates = null;
this.key = relationshipMeta.key;
}
addCanonicalRecordData(recordData, idx) {
if (this.canonicalMembers.has(recordData)) {
return;
}
if (idx !== undefined) {
this.canonicalState.splice(idx, 0, recordData);
} else {
this.canonicalState.push(recordData);
}
super.addCanonicalRecordData(recordData, idx);
}
inverseDidDematerialize(inverseRecordData) {
super.inverseDidDematerialize(inverseRecordData);
if (this.isAsync) {
this.notifyManyArrayIsStale();
}
}
addRecordData(recordData, idx) {
if (this.members.has(recordData)) {
return;
} // TODO Type this
(0, _debug.assertPolymorphicType)(this.recordData, this.relationshipMeta, recordData, this.store);
super.addRecordData(recordData, idx); // make lazy later
if (idx === undefined) {
idx = this.currentState.length;
}
this.currentState.splice(idx, 0, recordData); // TODO Igor consider making direct to remove the indirection
// We are not lazily accessing the manyArray here because the change is coming from app side
// this.manyArray.flushCanonical(this.currentState);
this.notifyHasManyChange();
}
removeCanonicalRecordDataFromOwn(recordData, idx) {
let i = idx;
if (!this.canonicalMembers.has(recordData)) {
return;
}
if (i === undefined) {
i = this.canonicalState.indexOf(recordData);
}
if (i > -1) {
this.canonicalState.splice(i, 1);
}
super.removeCanonicalRecordDataFromOwn(recordData, idx); //TODO(Igor) Figure out what to do here
}
removeAllCanonicalRecordDatasFromOwn() {
super.removeAllCanonicalRecordDatasFromOwn();
this.canonicalMembers.clear();
this.canonicalState.splice(0, this.canonicalState.length);
super.removeAllCanonicalRecordDatasFromOwn();
} //TODO(Igor) DO WE NEED THIS?
removeCompletelyFromOwn(recordData) {
super.removeCompletelyFromOwn(recordData); // TODO SkEPTICAL
const canonicalIndex = this.canonicalState.indexOf(recordData);
if (canonicalIndex !== -1) {
this.canonicalState.splice(canonicalIndex, 1);
}
this.removeRecordDataFromOwn(recordData);
}
flushCanonical() {
let toSet = this.canonicalState; //a hack for not removing new records
//TODO remove once we have proper diffing
let newRecordDatas = this.currentState.filter( // only add new internalModels which are not yet in the canonical state of this
// relationship (a new internalModel can be in the canonical state if it has
// been 'acknowleged' to be in the relationship via a store.push)
//TODO Igor deal with this
recordData => recordData.isNew() && toSet.indexOf(recordData) === -1);
toSet = toSet.concat(newRecordDatas);
/*
if (this._manyArray) {
this._manyArray.flushCanonical(toSet);
}
*/
this.currentState = toSet;
super.flushCanonical(); // Once we clean up all the flushing, we will be left with at least the notifying part
this.notifyHasManyChange();
} //TODO(Igor) idx not used currently, fix
removeRecordDataFromOwn(recordData, idx) {
super.removeRecordDataFromOwn(recordData, idx);
let index = idx || this.currentState.indexOf(recordData); //TODO IGOR DAVID INVESTIGATE
if (index === -1) {
return;
}
this.currentState.splice(index, 1); // TODO Igor consider making direct to remove the indirection
// We are not lazily accessing the manyArray here because the change is coming from app side
this.notifyHasManyChange(); // this.manyArray.flushCanonical(this.currentState);
}
notifyRecordRelationshipAdded() {
this.notifyHasManyChange();
}
computeChanges(recordDatas = []) {
let members = this.canonicalMembers;
let recordDatasToRemove = [];
let recordDatasSet = setForArray(recordDatas);
members.forEach(member => {
if (recordDatasSet.has(member)) {
return;
}
recordDatasToRemove.push(member);
});
this.removeCanonicalRecordDatas(recordDatasToRemove);
for (let i = 0, l = recordDatas.length; i < l; i++) {
let recordData = recordDatas[i];
this.removeCanonicalRecordData(recordData);
this.addCanonicalRecordData(recordData, i);
}
}
setInitialRecordDatas(recordDatas) {
if (Array.isArray(recordDatas) === false || !recordDatas || recordDatas.length === 0) {
return;
}
for (let i = 0; i < recordDatas.length; i++) {
let recordData = recordDatas[i];
if (this.canonicalMembers.has(recordData)) {
continue;
}
this.canonicalMembers.add(recordData);
this.members.add(recordData);
this.setupInverseRelationship(recordData);
}
this.canonicalState = this.canonicalMembers.toArray();
}
/*
This is essentially a "sync" version of
notifyHasManyChange. We should work to unify
these worlds
- @runspired
*/
notifyManyArrayIsStale() {
let recordData = this.recordData;
let storeWrapper = recordData.storeWrapper;
storeWrapper.notifyPropertyChange(recordData.modelName, recordData.id, recordData.clientId, this.key);
}
notifyHasManyChange() {
let recordData = this.recordData;
let storeWrapper = recordData.storeWrapper;
storeWrapper.notifyHasManyChange(recordData.modelName, recordData.id, recordData.clientId, this.key);
}
getData() {
let payload = {};
if (this.hasAnyRelationshipData) {
payload.data = this.currentState.map(recordData => recordData.getResourceIdentifier());
}
if (this.link) {
payload.links = {
related: this.link
};
}
if (this.meta) {
payload.meta = this.meta;
} // TODO @runspired: the @igor refactor is too limiting for relationship state
// we should reconsider where we fetch from.
payload._relationship = this;
return payload;
}
updateData(data, initial) {
let recordDatas;
if (Ember.isNone(data)) {
recordDatas = undefined;
} else {
recordDatas = new Array(data.length);
for (let i = 0; i < data.length; i++) {
recordDatas[i] = this.recordData.storeWrapper.recordDataFor(data[i].type, data[i].id);
}
}
if (initial) {
this.setInitialRecordDatas(recordDatas);
} else {
this.updateRecordDatasFromAdapter(recordDatas);
}
}
/**
* Flag indicating whether all inverse records are available
*
* true if inverse records exist and are all loaded (all not empty)
* true if there are no inverse records
* false if the inverse records exist and any are not loaded (any empty)
*
* @return {boolean}
*/
get allInverseRecordsAreLoaded() {
// check currentState for unloaded records
let hasEmptyRecords = this.currentState.reduce((hasEmptyModel, i) => {
return hasEmptyModel || i.isEmpty();
}, false); // check un-synced state for unloaded records
if (!hasEmptyRecords && this.willSync) {
hasEmptyRecords = this.canonicalState.reduce((hasEmptyModel, i) => {
return hasEmptyModel || !i.isEmpty();
}, false);
}
return !hasEmptyRecords;
}
}
_exports.default = ManyRelationship;
function setForArray(array) {
var set = new _orderedSet.default();
if (array) {
for (var i = 0, l = array.length; i < l; i++) {
set.add(array[i]);
}
}
return set;
}
});
define("@ember-data/store/-private/system/relationships/state/relationship", ["exports", "@ember-data/store/-private/system/record-data-for", "@ember-data/store/-private/system/ordered-set", "@ember-data/store/-private/system/normalize-link"], function (_exports, _recordDataFor, _orderedSet, _normalizeLink2) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class Relationship {
constructor(store, inverseKey, relationshipMeta, recordData, inverseIsAsync) {
_defineProperty(this, "inverseIsAsync", void 0);
_defineProperty(this, "kind", void 0);
_defineProperty(this, "recordData", void 0);
_defineProperty(this, "members", void 0);
_defineProperty(this, "canonicalMembers", void 0);
_defineProperty(this, "store", void 0);
_defineProperty(this, "key", void 0);
_defineProperty(this, "inverseKey", void 0);
_defineProperty(this, "isAsync", void 0);
_defineProperty(this, "isPolymorphic", void 0);
_defineProperty(this, "relationshipMeta", void 0);
_defineProperty(this, "inverseKeyForImplicit", void 0);
_defineProperty(this, "meta", void 0);
_defineProperty(this, "__inverseMeta", void 0);
_defineProperty(this, "_tempModelName", void 0);
_defineProperty(this, "shouldForceReload", false);
_defineProperty(this, "relationshipIsStale", void 0);
_defineProperty(this, "hasDematerializedInverse", void 0);
_defineProperty(this, "hasAnyRelationshipData", void 0);
_defineProperty(this, "relationshipIsEmpty", void 0);
_defineProperty(this, "hasFailedLoadAttempt", false);
_defineProperty(this, "link", void 0);
_defineProperty(this, "willSync", void 0);
this.inverseIsAsync = inverseIsAsync;
this.kind = relationshipMeta.kind;
let async = relationshipMeta.options.async;
let polymorphic = relationshipMeta.options.polymorphic;
this.recordData = recordData;
this.members = new _orderedSet.default();
this.canonicalMembers = new _orderedSet.default();
this.store = store;
this.key = relationshipMeta.key || null;
this.inverseKey = inverseKey;
this.isAsync = typeof async === 'undefined' ? true : async;
this.isPolymorphic = typeof polymorphic === 'undefined' ? false : polymorphic;
this.relationshipMeta = relationshipMeta; //This probably breaks for polymorphic relationship in complex scenarios, due to
//multiple possible modelNames
this.inverseKeyForImplicit = this._tempModelName + this.key;
this.meta = null;
this.__inverseMeta = undefined;
/*
This flag forces fetch. `true` for a single request once `reload()`
has been called `false` at all other times.
*/
// this.shouldForceReload = false;
/*
This flag indicates whether we should
re-fetch the relationship the next time
it is accessed.
The difference between this flag and `shouldForceReload`
is in how we treat the presence of partially missing data:
- for a forced reload, we will reload the link or EVERY record
- for a stale reload, we will reload the link (if present) else only MISSING records
Ideally these flags could be merged, but because we don't give the
request layer the option of deciding how to resolve the data being queried
we are forced to differentiate for now.
It is also possible for a relationship to remain stale after a forced reload; however,
in this case `hasFailedLoadAttempt` ought to be `true`.
false when
=> recordData.isNew() on initial setup
=> a previously triggered request has resolved
=> we get relationship data via push
true when
=> !recordData.isNew() on initial setup
=> an inverse has been unloaded
=> we get a new link for the relationship
TODO @runspired unskip the acceptance tests and fix these flags
*/
this.relationshipIsStale = false;
/*
This flag indicates whether we should
**partially** re-fetch the relationship the
next time it is accessed.
false when
=> initial setup
=> a previously triggered request has resolved
true when
=> an inverse has been unloaded
*/
this.hasDematerializedInverse = false;
/*
This flag indicates whether we should consider the content
of this relationship "known".
If we have no relationship knowledge, and the relationship
is `async`, we will attempt to fetch the relationship on
access if it is also stale.
Snapshot uses this to tell the difference between unknown
(`undefined`) or empty (`null`). The reason for this is that
we wouldn't want to serialize unknown relationships as `null`
as that might overwrite remote state.
All relationships for a newly created (`store.createRecord()`) are
considered known (`hasAnyRelationshipData === true`).
true when
=> we receive a push with either new data or explicit empty (`[]` or `null`)
=> the relationship is a belongsTo and we have received data from
the other side.
false when
=> we have received no signal about what data belongs in this relationship
=> the relationship is a hasMany and we have only received data from
the other side.
*/
this.hasAnyRelationshipData = false;
/*
Flag that indicates whether an empty relationship is explicitly empty
(signaled by push giving us an empty array or null relationship)
e.g. an API response has told us that this relationship is empty.
Thus far, it does not appear that we actually need this flag; however,
@runspired has found it invaluable when debugging relationship tests
to determine whether (and why if so) we are in an incorrect state.
true when
=> we receive a push with explicit empty (`[]` or `null`)
=> we have received no signal about what data belongs in this relationship
=> on initial create (as no signal is known yet)
false at all other times
*/
this.relationshipIsEmpty = true;
/*
Flag def here for reference, defined as getter in has-many.js / belongs-to.js
true when
=> hasAnyRelationshipData is true
AND
=> members (NOT canonicalMembers) @each !isEmpty
TODO, consider changing the conditional here from !isEmpty to !hiddenFromRecordArrays
*/
// TODO do we want this anymore? Seems somewhat useful
// especially if we rename to `hasUpdatedLink`
// which would tell us slightly more about why the
// relationship is stale
// this.updatedLink = false;
}
get isNew() {
return this.recordData.isNew();
}
_inverseIsAsync() {
return !!this.inverseIsAsync;
}
_inverseIsSync() {
return !!(this.inverseKey && !this.inverseIsAsync);
}
_hasSupportForImplicitRelationships(recordData) {
return recordData._implicitRelationships !== undefined && recordData._implicitRelationships !== null;
}
_hasSupportForRelationships(recordData) {
return recordData._relationships !== undefined && recordData._relationships !== null;
}
get _inverseMeta() {
if (this.__inverseMeta === undefined) {
let inverseMeta = null;
if (this.inverseKey) {
// We know we have a full inverse relationship
let type = this.relationshipMeta.type;
let inverseModelClass = this.store.modelFor(type);
let inverseRelationships = Ember.get(inverseModelClass, 'relationshipsByName');
inverseMeta = inverseRelationships.get(this.inverseKey);
}
this.__inverseMeta = inverseMeta;
}
return this.__inverseMeta;
}
recordDataDidDematerialize() {
const inverseKey = this.inverseKey;
if (!inverseKey) {
return;
} // we actually want a union of members and canonicalMembers
// they should be disjoint but currently are not due to a bug
this.forAllMembers(inverseRecordData => {
if (!this._hasSupportForRelationships(inverseRecordData)) {
return;
}
let relationship = (0, _recordDataFor.relationshipStateFor)(inverseRecordData, inverseKey);
let belongsToRelationship = inverseRecordData.getBelongsTo(inverseKey)._relationship; // For canonical members, it is possible that inverseRecordData has already been associated to
// to another record. For such cases, do not dematerialize the inverseRecordData
if (!belongsToRelationship || !belongsToRelationship.inverseRecordData || this.recordData === belongsToRelationship.inverseRecordData) {
relationship.inverseDidDematerialize(this.recordData);
}
});
}
forAllMembers(callback) {
let seen = Object.create(null);
for (let i = 0; i < this.members.list.length; i++) {
const inverseInternalModel = this.members.list[i];
const id = Ember.guidFor(inverseInternalModel);
if (!seen[id]) {
seen[id] = true;
callback(inverseInternalModel);
}
}
for (let i = 0; i < this.canonicalMembers.list.length; i++) {
const inverseInternalModel = this.canonicalMembers.list[i];
const id = Ember.guidFor(inverseInternalModel);
if (!seen[id]) {
seen[id] = true;
callback(inverseInternalModel);
}
}
}
inverseDidDematerialize(inverseRecordData) {
if (!this.isAsync || inverseRecordData && inverseRecordData.isNew()) {
// unloading inverse of a sync relationship is treated as a client-side
// delete, so actually remove the models don't merely invalidate the cp
// cache.
// if the record being unloaded only exists on the client, we similarly
// treat it as a client side delete
this.removeRecordDataFromOwn(inverseRecordData);
this.removeCanonicalRecordDataFromOwn(inverseRecordData);
this.setRelationshipIsEmpty(true);
} else {
this.setHasDematerializedInverse(true);
}
}
updateMeta(meta) {
this.meta = meta;
}
clear() {
let members = this.members.list;
while (members.length > 0) {
let member = members[0];
this.removeRecordData(member);
}
let canonicalMembers = this.canonicalMembers.list;
while (canonicalMembers.length > 0) {
let member = canonicalMembers[0];
this.removeCanonicalRecordData(member);
}
}
removeAllRecordDatasFromOwn() {
this.setRelationshipIsStale(true);
this.members.clear();
}
removeAllCanonicalRecordDatasFromOwn() {
this.canonicalMembers.clear();
this.flushCanonicalLater();
}
removeRecordDatas(recordDatas) {
recordDatas.forEach(recordData => this.removeRecordData(recordData));
}
addRecordDatas(recordDatas, idx) {
recordDatas.forEach(recordData => {
this.addRecordData(recordData, idx);
if (idx !== undefined) {
idx++;
}
});
}
addCanonicalRecordDatas(recordDatas, idx) {
for (let i = 0; i < recordDatas.length; i++) {
if (idx !== undefined) {
this.addCanonicalRecordData(recordDatas[i], i + idx);
} else {
this.addCanonicalRecordData(recordDatas[i]);
}
}
}
addCanonicalRecordData(recordData, idx) {
if (!this.canonicalMembers.has(recordData)) {
this.canonicalMembers.add(recordData);
this.setupInverseRelationship(recordData);
}
this.flushCanonicalLater();
this.setHasAnyRelationshipData(true);
}
setupInverseRelationship(recordData) {
if (this.inverseKey) {
if (!this._hasSupportForRelationships(recordData)) {
return;
}
let relationship = (0, _recordDataFor.relationshipStateFor)(recordData, this.inverseKey); // if we have only just initialized the inverse relationship, then it
// already has this.recordData in its canonicalMembers, so skip the
// unnecessary work. The exception to this is polymorphic
// relationships whose members are determined by their inverse, as those
// relationships cannot efficiently find their inverse payloads.
relationship.addCanonicalRecordData(this.recordData);
} else {
if (!this._hasSupportForImplicitRelationships(recordData)) {
return;
}
let relationships = recordData._implicitRelationships;
let relationship = relationships[this.inverseKeyForImplicit];
if (!relationship) {
relationship = relationships[this.inverseKeyForImplicit] = new Relationship(this.store, // we know we are not an implicit relationship here
this.key, {
options: {
async: this.isAsync
}
}, recordData);
}
relationship.addCanonicalRecordData(this.recordData);
}
}
removeCanonicalRecordDatas(recordDatas, idx) {
for (let i = 0; i < recordDatas.length; i++) {
if (idx !== undefined) {
this.removeCanonicalRecordData(recordDatas[i], i + idx);
} else {
this.removeCanonicalRecordData(recordDatas[i]);
}
}
}
removeCanonicalRecordData(recordData, idx) {
if (this.canonicalMembers.has(recordData)) {
this.removeCanonicalRecordDataFromOwn(recordData);
if (this.inverseKey) {
this.removeCanonicalRecordDataFromInverse(recordData);
} else {
if (this._hasSupportForImplicitRelationships(recordData) && recordData._implicitRelationships[this.inverseKeyForImplicit]) {
recordData._implicitRelationships[this.inverseKeyForImplicit].removeCanonicalRecordData(this.recordData);
}
}
}
this.flushCanonicalLater();
}
addRecordData(recordData, idx) {
if (!this.members.has(recordData)) {
this.members.addWithIndex(recordData, idx);
this.notifyRecordRelationshipAdded(recordData, idx);
if (this._hasSupportForRelationships(recordData) && this.inverseKey) {
(0, _recordDataFor.relationshipStateFor)(recordData, this.inverseKey).addRecordData(this.recordData);
} else {
if (this._hasSupportForImplicitRelationships(recordData)) {
if (!recordData._implicitRelationships[this.inverseKeyForImplicit]) {
recordData._implicitRelationships[this.inverseKeyForImplicit] = new Relationship(this.store, // we know we are not an implicit relationship here
this.key, {
options: {
async: this.isAsync
}
}, recordData, this.isAsync);
}
recordData._implicitRelationships[this.inverseKeyForImplicit].addRecordData(this.recordData);
}
}
}
this.setHasAnyRelationshipData(true);
}
removeRecordData(recordData) {
if (this.members.has(recordData)) {
this.removeRecordDataFromOwn(recordData);
if (this.inverseKey) {
this.removeRecordDataFromInverse(recordData);
} else {
if (this._hasSupportForImplicitRelationships(recordData) && recordData._implicitRelationships[this.inverseKeyForImplicit]) {
recordData._implicitRelationships[this.inverseKeyForImplicit].removeRecordData(this.recordData);
}
}
}
}
removeRecordDataFromInverse(recordData) {
if (!this._hasSupportForRelationships(recordData)) {
return;
}
let inverseRelationship = (0, _recordDataFor.relationshipStateFor)(recordData, this.inverseKey); //Need to check for existence, as the record might unloading at the moment
if (inverseRelationship) {
inverseRelationship.removeRecordDataFromOwn(this.recordData);
}
}
removeRecordDataFromOwn(recordData, idx) {
this.members.delete(recordData);
}
removeCanonicalRecordDataFromInverse(recordData) {
if (!this._hasSupportForRelationships(recordData)) {
return;
}
let inverseRelationship = (0, _recordDataFor.relationshipStateFor)(recordData, this.inverseKey); //Need to check for existence, as the record might unloading at the moment
if (inverseRelationship) {
inverseRelationship.removeCanonicalRecordDataFromOwn(this.recordData);
}
}
removeCanonicalRecordDataFromOwn(recordData, idx) {
this.canonicalMembers.delete(recordData);
this.flushCanonicalLater();
}
/*
Call this method once a record deletion has been persisted
to purge it from BOTH current and canonical state of all
relationships.
@method removeCompletelyFromInverse
@private
*/
removeCompletelyFromInverse() {
if (!this.inverseKey) {
return;
} // we actually want a union of members and canonicalMembers
// they should be disjoint but currently are not due to a bug
let seen = Object.create(null);
const recordData = this.recordData;
const unload = inverseRecordData => {
const id = Ember.guidFor(inverseRecordData);
if (this._hasSupportForRelationships(inverseRecordData) && seen[id] === undefined) {
const relationship = (0, _recordDataFor.relationshipStateFor)(inverseRecordData, this.inverseKey);
relationship.removeCompletelyFromOwn(recordData);
seen[id] = true;
}
};
this.members.forEach(unload);
this.canonicalMembers.forEach(unload);
if (!this.isAsync) {
this.clear();
}
}
/*
Removes the given RecordData from BOTH canonical AND current state.
This method is useful when either a deletion or a rollback on a new record
needs to entirely purge itself from an inverse relationship.
*/
removeCompletelyFromOwn(recordData) {
this.canonicalMembers.delete(recordData);
this.members.delete(recordData);
}
flushCanonical() {
let list = this.members.list;
this.willSync = false; //a hack for not removing new RecordDatas
//TODO remove once we have proper diffing
let newRecordDatas = [];
for (let i = 0; i < list.length; i++) {
// TODO Igor deal with this
if (list[i].isNew()) {
newRecordDatas.push(list[i]);
}
} //TODO(Igor) make this less abysmally slow
this.members = this.canonicalMembers.copy();
for (let i = 0; i < newRecordDatas.length; i++) {
this.members.add(newRecordDatas[i]);
}
}
flushCanonicalLater() {
if (this.willSync) {
return;
}
this.willSync = true; // Reaching back into the store to use ED's runloop
this.store._updateRelationshipState(this);
}
updateLink(link) {
(true && Ember.warn("You pushed a record of type '".concat(this.recordData.modelName, "' with a relationship '").concat(this.key, "' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. EmberData will treat this relationship as known-to-be-empty."), this.isAsync || this.hasAnyRelationshipData, {
id: 'ds.store.push-link-for-sync-relationship'
}));
(true && Ember.assert("You have pushed a record of type '".concat(this.recordData.modelName, "' with '").concat(this.key, "' as a link, but the value of that link is not a string."), typeof link === 'string' || link === null));
this.link = link;
}
updateRecordDatasFromAdapter(recordDatas) {
this.setHasAnyRelationshipData(true); //TODO(Igor) move this to a proper place
//TODO Once we have adapter support, we need to handle updated and canonical changes
this.computeChanges(recordDatas);
}
computeChanges(recordDatas) {}
notifyRecordRelationshipAdded(recordData, idxs) {}
setHasAnyRelationshipData(value) {
this.hasAnyRelationshipData = value;
}
setHasDematerializedInverse(value) {
this.hasDematerializedInverse = value;
}
setRelationshipIsStale(value) {
this.relationshipIsStale = value;
}
setRelationshipIsEmpty(value) {
this.relationshipIsEmpty = value;
}
setShouldForceReload(value) {
this.shouldForceReload = value;
}
setHasFailedLoadAttempt(value) {
this.hasFailedLoadAttempt = value;
}
/*
`push` for a relationship allows the store to push a JSON API Relationship
Object onto the relationship. The relationship will then extract and set the
meta, data and links of that relationship.
`push` use `updateMeta`, `updateData` and `updateLink` to update the state
of the relationship.
*/
push(payload, initial) {
let hasRelationshipDataProperty = false;
let hasLink = false;
if (payload.meta) {
this.updateMeta(payload.meta);
}
if (payload.data !== undefined) {
hasRelationshipDataProperty = true;
this.updateData(payload.data, initial);
} else if (this.isAsync === false && !this.hasAnyRelationshipData) {
hasRelationshipDataProperty = true;
let data = this.kind === 'hasMany' ? [] : null;
this.updateData(data, initial);
}
if (payload.links && payload.links.related) {
let relatedLink = (0, _normalizeLink2.default)(payload.links.related);
if (relatedLink && relatedLink.href && relatedLink.href !== this.link) {
hasLink = true;
this.updateLink(relatedLink.href);
}
}
/*
Data being pushed into the relationship might contain only data or links,
or a combination of both.
IF contains only data
IF contains both links and data
relationshipIsEmpty -> true if is empty array (has-many) or is null (belongs-to)
hasAnyRelationshipData -> true
hasDematerializedInverse -> false
relationshipIsStale -> false
allInverseRecordsAreLoaded -> run-check-to-determine
IF contains only links
relationshipIsStale -> true
*/
this.setHasFailedLoadAttempt(false);
if (hasRelationshipDataProperty) {
let relationshipIsEmpty = payload.data === null || Array.isArray(payload.data) && payload.data.length === 0;
this.setHasAnyRelationshipData(true);
this.setRelationshipIsStale(false);
this.setHasDematerializedInverse(false);
this.setRelationshipIsEmpty(relationshipIsEmpty);
} else if (hasLink) {
this.setRelationshipIsStale(true);
if (!initial) {
let recordData = this.recordData;
let storeWrapper = this.recordData.storeWrapper;
storeWrapper.notifyPropertyChange(recordData.modelName, recordData.id, recordData.clientId, // We know we are not an implicit relationship here
this.key);
}
}
}
localStateIsEmpty() {}
updateData(payload, initial) {}
destroy() {}
}
_exports.default = Relationship;
});
define('ember-data/version', ['exports'], function (exports) {
exports.default = '3.13.1';
});
define("ember-load-initializers/index", ["exports", "require"], function (_exports, _require) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = loadInitializers;
function resolveInitializer(moduleName) {
var module = (0, _require.default)(moduleName, null, null, true);
if (!module) {
throw new Error(moduleName + ' must export an initializer.');
}
var initializer = module['default'];
if (!initializer.name) {
initializer.name = moduleName.slice(moduleName.lastIndexOf('/') + 1);
}
return initializer;
}
function registerInitializers(app, moduleNames) {
for (var i = 0; i < moduleNames.length; i++) {
app.initializer(resolveInitializer(moduleNames[i]));
}
}
function registerInstanceInitializers(app, moduleNames) {
for (var i = 0; i < moduleNames.length; i++) {
app.instanceInitializer(resolveInitializer(moduleNames[i]));
}
}
function _endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
/**
* Configure your application as it boots
*/
function loadInitializers(app, prefix) {
var initializerPrefix = prefix + '/initializers/';
var instanceInitializerPrefix = prefix + '/instance-initializers/';
var initializers = [];
var instanceInitializers = []; // this is 2 pass because generally the first pass is the problem
// and is reduced, and resolveInitializer has potential to deopt
var moduleNames = Object.keys(self.requirejs._eak_seen);
for (var i = 0; i < moduleNames.length; i++) {
var moduleName = moduleNames[i];
if (moduleName.lastIndexOf(initializerPrefix, 0) === 0) {
if (!_endsWith(moduleName, '-test')) {
initializers.push(moduleName);
}
} else if (moduleName.lastIndexOf(instanceInitializerPrefix, 0) === 0) {
if (!_endsWith(moduleName, '-test')) {
instanceInitializers.push(moduleName);
}
}
}
registerInitializers(app, initializers);
registerInstanceInitializers(app, instanceInitializers);
}
});
/*
* This is a stub file, it must be on disk b/c babel-plugin-debug-macros
* does not strip the module require when the transpiled variable usage is
* stripped.
*/
define("ember-resolver/features", [], function () {
"use strict";
});
define("ember-resolver/index", ["exports", "ember-resolver/resolvers/classic"], function (_exports, _classic) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _classic.default;
}
});
});
define("ember-resolver/resolver", ["exports", "ember-resolver/resolvers/classic"], function (_exports, _classic) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _classic.default;
}
});
});
define("ember-resolver/utils/class-factory", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = classFactory;
function classFactory(klass) {
return {
create(injections) {
if (typeof klass.extend === 'function') {
return klass.extend(injections);
} else {
return klass;
}
}
};
}
});
define("ember-resolver/utils/make-dictionary", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = makeDictionary;
function makeDictionary() {
let cache = Object.create(null);
cache['_dict'] = null;
delete cache['_dict'];
return cache;
}
});
define("ember-resolver/resolvers/classic/container-debug-adapter", ["exports", "ember-resolver/resolvers/classic/index"], function (_exports, _index) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function getPod(type, key, prefix) {
let match = key.match(new RegExp('^/?' + prefix + '/(.+)/' + type + '$'));
if (match !== null) {
return match[1];
}
}
/*
* This module defines a subclass of Ember.ContainerDebugAdapter that adds
* support for resolving from modules.
*
*/
var _default = Ember.ContainerDebugAdapter.extend({
_moduleRegistry: null,
init() {
this._super(...arguments);
if (!this._moduleRegistry) {
this._moduleRegistry = new _index.ModuleRegistry();
}
},
/**
The container of the application being debugged.
This property will be injected
on creation.
@property container
@default null
*/
/**
The resolver instance of the application
being debugged. This property will be injected
on creation.
@property resolver
@default null
*/
/**
Returns true if it is possible to catalog a list of available
classes in the resolver for a given type.
@method canCatalogEntriesByType
@param {string} type The type. e.g. "model", "controller", "route"
@return {boolean} whether a list is available for this type.
*/
canCatalogEntriesByType(type) {
if (type === 'model') {
return true;
}
return this._super(...arguments);
},
/**
Returns the available classes a given type.
@method catalogEntriesByType
@param {string} type The type. e.g. "model", "controller", "route"
@return {Array} An array of classes.
*/
catalogEntriesByType(type) {
let moduleNames = this._moduleRegistry.moduleNames();
let types = Ember.A();
let prefix = this.namespace.modulePrefix;
for (let i = 0, l = moduleNames.length; i < l; i++) {
let key = moduleNames[i];
if (key.indexOf(type) !== -1) {
// Check if it's a pod module
let name = getPod(type, key, this.namespace.podModulePrefix || prefix);
if (!name) {
// Not pod
name = key.split(type + 's/').pop(); // Support for different prefix (such as ember-cli addons).
// Uncomment the code below when
// https://github.com/ember-cli/ember-resolver/pull/80 is merged.
//let match = key.match('^/?(.+)/' + type);
//if (match && match[1] !== prefix) {
// Different prefix such as an addon
//name = match[1] + '@' + name;
//}
}
types.addObject(name);
}
}
return types;
}
});
_exports.default = _default;
});
define("ember-resolver/resolvers/classic/index", ["exports", "ember-resolver/utils/class-factory", "ember-resolver/utils/make-dictionary"], function (_exports, _classFactory, _makeDictionary) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = _exports.ModuleRegistry = void 0;
if (typeof requirejs.entries === 'undefined') {
requirejs.entries = requirejs._eak_seen;
}
class ModuleRegistry {
constructor(entries) {
this._entries = entries || requirejs.entries;
}
moduleNames() {
return Object.keys(this._entries);
}
has(moduleName) {
return moduleName in this._entries;
}
get(moduleName) {
return require(moduleName);
}
}
/**
* This module defines a subclass of Ember.DefaultResolver that adds two
* important features:
*
* 1) The resolver makes the container aware of es6 modules via the AMD
* output. The loader's _moduleEntries is consulted so that classes can be
* resolved directly via the module loader, without needing a manual
* `import`.
* 2) is able to provide injections to classes that implement `extend`
* (as is typical with Ember).
*/
_exports.ModuleRegistry = ModuleRegistry;
function parseName(fullName) {
if (fullName.parsedName === true) {
return fullName;
}
let prefix, type, name;
let fullNameParts = fullName.split('@');
if (fullNameParts.length === 2) {
let prefixParts = fullNameParts[0].split(':');
if (prefixParts.length === 2) {
if (prefixParts[1].length === 0) {
type = prefixParts[0];
name = "@".concat(fullNameParts[1]);
} else {
prefix = prefixParts[1];
type = prefixParts[0];
name = fullNameParts[1];
}
} else {
let nameParts = fullNameParts[1].split(':');
prefix = fullNameParts[0];
type = nameParts[0];
name = nameParts[1];
}
if (type === 'template' && prefix.lastIndexOf('components/', 0) === 0) {
name = "components/".concat(name);
prefix = prefix.slice(11);
}
} else {
fullNameParts = fullName.split(':');
type = fullNameParts[0];
name = fullNameParts[1];
}
let fullNameWithoutType = name;
let namespace = Ember.get(this, 'namespace');
let root = namespace;
return {
parsedName: true,
fullName: fullName,
prefix: prefix || this.prefix({
type: type
}),
type: type,
fullNameWithoutType: fullNameWithoutType,
name: name,
root: root,
resolveMethodName: "resolve" + Ember.String.classify(type)
};
}
function resolveOther(parsedName) {
(true && Ember.assert('`modulePrefix` must be defined', this.namespace.modulePrefix));
let normalizedModuleName = this.findModuleName(parsedName);
if (normalizedModuleName) {
let defaultExport = this._extractDefaultExport(normalizedModuleName, parsedName);
if (defaultExport === undefined) {
throw new Error(" Expected to find: '".concat(parsedName.fullName, "' within '").concat(normalizedModuleName, "' but got 'undefined'. Did you forget to 'export default' within '").concat(normalizedModuleName, "'?"));
}
if (this.shouldWrapInClassFactory(defaultExport, parsedName)) {
defaultExport = (0, _classFactory.default)(defaultExport);
}
return defaultExport;
}
}
const Resolver = Ember.Object.extend({
resolveOther,
parseName,
pluralizedTypes: null,
moduleRegistry: null,
makeToString(factory, fullName) {
return '' + this.namespace.modulePrefix + '@' + fullName + ':';
},
shouldWrapInClassFactory()
/* module, parsedName */
{
return false;
},
init() {
this._super();
this.moduleBasedResolver = true;
if (!this._moduleRegistry) {
this._moduleRegistry = new ModuleRegistry();
}
this._normalizeCache = (0, _makeDictionary.default)();
this.pluralizedTypes = this.pluralizedTypes || (0, _makeDictionary.default)();
if (!this.pluralizedTypes.config) {
this.pluralizedTypes.config = 'config';
}
this._deprecatedPodModulePrefix = false;
},
normalize(fullName) {
return this._normalizeCache[fullName] || (this._normalizeCache[fullName] = this._normalize(fullName));
},
resolve(fullName) {
let parsedName = this.parseName(fullName);
let resolveMethodName = parsedName.resolveMethodName;
let resolved;
if (typeof this[resolveMethodName] === 'function') {
resolved = this[resolveMethodName](parsedName);
}
if (resolved == null) {
resolved = this.resolveOther(parsedName);
}
return resolved;
},
_normalize(fullName) {
// A) Convert underscores to dashes
// B) Convert camelCase to dash-case, except for components (their
// templates) and helpers where we want to avoid shadowing camelCase
// expressions
// C) replace `.` with `/` in order to make nested controllers work in the following cases
// 1. `needs: ['posts/post']`
// 2. `{{render "posts/post"}}`
// 3. `this.render('posts/post')` from Route
let split = fullName.split(':');
if (split.length > 1) {
let type = split[0];
if (type === 'component' || type === 'helper' || type === 'template' && split[1].indexOf('components/') === 0) {
return type + ':' + split[1].replace(/_/g, '-');
} else {
return type + ':' + Ember.String.dasherize(split[1].replace(/\./g, '/'));
}
} else {
return fullName;
}
},
pluralize(type) {
return this.pluralizedTypes[type] || (this.pluralizedTypes[type] = type + 's');
},
podBasedLookupWithPrefix(podPrefix, parsedName) {
let fullNameWithoutType = parsedName.fullNameWithoutType;
if (parsedName.type === 'template') {
fullNameWithoutType = fullNameWithoutType.replace(/^components\//, '');
}
return podPrefix + '/' + fullNameWithoutType + '/' + parsedName.type;
},
podBasedModuleName(parsedName) {
let podPrefix = this.namespace.podModulePrefix || this.namespace.modulePrefix;
return this.podBasedLookupWithPrefix(podPrefix, parsedName);
},
podBasedComponentsInSubdir(parsedName) {
let podPrefix = this.namespace.podModulePrefix || this.namespace.modulePrefix;
podPrefix = podPrefix + '/components';
if (parsedName.type === 'component' || /^components/.test(parsedName.fullNameWithoutType)) {
return this.podBasedLookupWithPrefix(podPrefix, parsedName);
}
},
resolveEngine(parsedName) {
let engineName = parsedName.fullNameWithoutType;
let engineModule = engineName + '/engine';
if (this._moduleRegistry.has(engineModule)) {
return this._extractDefaultExport(engineModule);
}
},
resolveRouteMap(parsedName) {
let engineName = parsedName.fullNameWithoutType;
let engineRoutesModule = engineName + '/routes';
if (this._moduleRegistry.has(engineRoutesModule)) {
let routeMap = this._extractDefaultExport(engineRoutesModule);
(true && Ember.assert("The route map for ".concat(engineName, " should be wrapped by 'buildRoutes' before exporting."), routeMap.isRouteMap));
return routeMap;
}
},
resolveTemplate(parsedName) {
let resolved = this.resolveOther(parsedName);
if (resolved == null) {
resolved = Ember.TEMPLATES[parsedName.fullNameWithoutType];
}
return resolved;
},
mainModuleName(parsedName) {
if (parsedName.fullNameWithoutType === 'main') {
// if router:main or adapter:main look for a module with just the type first
return parsedName.prefix + '/' + parsedName.type;
}
},
defaultModuleName(parsedName) {
return parsedName.prefix + '/' + this.pluralize(parsedName.type) + '/' + parsedName.fullNameWithoutType;
},
nestedColocationComponentModuleName(parsedName) {
if (parsedName.type === 'component') {
return parsedName.prefix + '/' + this.pluralize(parsedName.type) + '/' + parsedName.fullNameWithoutType + '/index';
}
},
prefix(parsedName) {
let tmpPrefix = this.namespace.modulePrefix;
if (this.namespace[parsedName.type + 'Prefix']) {
tmpPrefix = this.namespace[parsedName.type + 'Prefix'];
}
return tmpPrefix;
},
/**
A listing of functions to test for moduleName's based on the provided
`parsedName`. This allows easy customization of additional module based
lookup patterns.
@property moduleNameLookupPatterns
@returns {Ember.Array}
*/
moduleNameLookupPatterns: Ember.computed(function () {
return [this.podBasedModuleName, this.podBasedComponentsInSubdir, this.mainModuleName, this.defaultModuleName, this.nestedColocationComponentModuleName];
}).readOnly(),
findModuleName(parsedName, loggingDisabled) {
let moduleNameLookupPatterns = this.get('moduleNameLookupPatterns');
let moduleName;
for (let index = 0, length = moduleNameLookupPatterns.length; index < length; index++) {
let item = moduleNameLookupPatterns[index];
let tmpModuleName = item.call(this, parsedName); // allow treat all dashed and all underscored as the same thing
// supports components with dashes and other stuff with underscores.
if (tmpModuleName) {
tmpModuleName = this.chooseModuleName(tmpModuleName, parsedName);
}
if (tmpModuleName && this._moduleRegistry.has(tmpModuleName)) {
moduleName = tmpModuleName;
}
if (!loggingDisabled) {
this._logLookup(moduleName, parsedName, tmpModuleName);
}
if (moduleName) {
return moduleName;
}
}
},
chooseModuleName(moduleName, parsedName) {
let underscoredModuleName = Ember.String.underscore(moduleName);
if (moduleName !== underscoredModuleName && this._moduleRegistry.has(moduleName) && this._moduleRegistry.has(underscoredModuleName)) {
throw new TypeError("Ambiguous module names: '".concat(moduleName, "' and '").concat(underscoredModuleName, "'"));
}
if (this._moduleRegistry.has(moduleName)) {
return moduleName;
} else if (this._moduleRegistry.has(underscoredModuleName)) {
return underscoredModuleName;
} // workaround for dasherized partials:
// something/something/-something => something/something/_something
let partializedModuleName = moduleName.replace(/\/-([^/]*)$/, '/_$1');
if (this._moduleRegistry.has(partializedModuleName)) {
(true && !(false) && Ember.deprecate('Modules should not contain underscores. ' + 'Attempted to lookup "' + moduleName + '" which ' + 'was not found. Please rename "' + partializedModuleName + '" ' + 'to "' + moduleName + '" instead.', false, {
id: 'ember-resolver.underscored-modules',
until: '3.0.0'
}));
return partializedModuleName;
}
if (true
/* DEBUG */
) {
let isCamelCaseHelper = parsedName.type === 'helper' && /[a-z]+[A-Z]+/.test(moduleName);
if (isCamelCaseHelper) {
this._camelCaseHelperWarnedNames = this._camelCaseHelperWarnedNames || [];
let alreadyWarned = this._camelCaseHelperWarnedNames.indexOf(parsedName.fullName) > -1;
if (!alreadyWarned && this._moduleRegistry.has(Ember.String.dasherize(moduleName))) {
this._camelCaseHelperWarnedNames.push(parsedName.fullName);
(true && Ember.warn('Attempted to lookup "' + parsedName.fullName + '" which ' + 'was not found. In previous versions of ember-resolver, a bug would have ' + 'caused the module at "' + Ember.String.dasherize(moduleName) + '" to be ' + 'returned for this camel case helper name. This has been fixed. ' + 'Use the dasherized name to resolve the module that would have been ' + 'returned in previous versions.', false, {
id: 'ember-resolver.camelcase-helper-names',
until: '3.0.0'
}));
}
}
}
},
// used by Ember.DefaultResolver.prototype._logLookup
lookupDescription(fullName) {
let parsedName = this.parseName(fullName);
let moduleName = this.findModuleName(parsedName, true);
return moduleName;
},
// only needed until 1.6.0-beta.2 can be required
_logLookup(found, parsedName, description) {
if (!Ember.ENV.LOG_MODULE_RESOLVER && !parsedName.root.LOG_RESOLVER) {
return;
}
let padding;
let symbol = found ? '[✓]' : '[ ]';
if (parsedName.fullName.length > 60) {
padding = '.';
} else {
padding = new Array(60 - parsedName.fullName.length).join('.');
}
if (!description) {
description = this.lookupDescription(parsedName);
}
/* eslint-disable no-console */
if (console && console.info) {
console.info(symbol, parsedName.fullName, padding, description);
}
},
knownForType(type) {
let moduleKeys = this._moduleRegistry.moduleNames();
let items = (0, _makeDictionary.default)();
for (let index = 0, length = moduleKeys.length; index < length; index++) {
let moduleName = moduleKeys[index];
let fullname = this.translateToContainerFullname(type, moduleName);
if (fullname) {
items[fullname] = true;
}
}
return items;
},
translateToContainerFullname(type, moduleName) {
let prefix = this.prefix({
type
}); // Note: using string manipulation here rather than regexes for better performance.
// pod modules
// '^' + prefix + '/(.+)/' + type + '$'
let podPrefix = prefix + '/';
let podSuffix = '/' + type;
let start = moduleName.indexOf(podPrefix);
let end = moduleName.indexOf(podSuffix);
if (start === 0 && end === moduleName.length - podSuffix.length && moduleName.length > podPrefix.length + podSuffix.length) {
return type + ':' + moduleName.slice(start + podPrefix.length, end);
} // non-pod modules
// '^' + prefix + '/' + pluralizedType + '/(.+)$'
let pluralizedType = this.pluralize(type);
let nonPodPrefix = prefix + '/' + pluralizedType + '/';
if (moduleName.indexOf(nonPodPrefix) === 0 && moduleName.length > nonPodPrefix.length) {
return type + ':' + moduleName.slice(nonPodPrefix.length);
}
},
_extractDefaultExport(normalizedModuleName) {
let module = require(normalizedModuleName, null, null, true
/* force sync */
);
if (module && module['default']) {
module = module['default'];
}
return module;
}
});
Resolver.reopenClass({
moduleBasedResolver: true
});
var _default = Resolver;
_exports.default = _default;
});
define("ember-resolver/ember-config", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = generateConfig;
/*
* This config describes canonical Ember, as described in the
* module unification spec:
*
* https://github.com/emberjs/rfcs/blob/master/text/0143-module-unification.md
*
*/
function generateConfig(name) {
return {
app: {
name,
rootName: name
},
types: {
adapter: {
definitiveCollection: 'models'
},
application: {
definitiveCollection: 'main'
},
config: {
definitiveCollection: 'config'
},
controller: {
definitiveCollection: 'routes'
},
component: {
definitiveCollection: 'components'
},
'component-lookup': {
definitiveCollection: 'main'
},
'component-manager': {
definitiveCollection: 'component-managers'
},
event_dispatcher: {
definitiveCollection: 'main'
},
helper: {
definitiveCollection: 'components'
},
initializer: {
definitiveCollection: 'initializers'
},
'instance-initializers': {
definitiveCollection: 'instance-initializer'
},
location: {
definitiveCollection: 'main'
},
model: {
definitiveCollection: 'models'
},
modifier: {
definitiveCollection: 'components'
},
'modifier-manager': {
definitiveCollection: 'modifier-managers'
},
partial: {
definitiveCollection: 'partials'
},
renderer: {
definitiveCollection: 'main'
},
route: {
definitiveCollection: 'routes'
},
router: {
definitiveCollection: 'main'
},
'route-map': {
definitiveCollection: 'main'
},
serializer: {
definitiveCollection: 'models'
},
service: {
definitiveCollection: 'services'
},
template: {
definitiveCollection: 'components'
},
'template-compiler': {
definitiveCollection: 'main'
},
transform: {
definitiveCollection: 'transforms'
},
view: {
definitiveCollection: 'views'
},
'-view-registry': {
definitiveCollection: 'main'
},
'-bucket-cache': {
definitiveCollection: 'main'
},
'-environment': {
definitiveCollection: 'main'
},
'-application-instance': {
definitiveCollection: 'main'
}
},
collections: {
'main': {
types: ['router', '-bucket-cache', 'component-lookup', '-view-registry', 'event_dispatcher', 'application', 'location', 'renderer', '-environment', '-application-instance', 'route-map']
},
components: {
group: 'ui',
privateCollections: ['utils'],
types: ['component', 'helper', 'template', 'modifier']
},
'component-managers': {
types: ['component-manager']
},
config: {
unresolvable: true
},
initializers: {
group: 'init',
defaultType: 'initializer',
privateCollections: ['utils'],
types: ['initializer']
},
'instance-initializers': {
group: 'init',
defaultType: 'instance-initializer',
privateCollections: ['utils'],
types: ['instance-initializers']
},
models: {
group: 'data',
defaultType: 'model',
privateCollections: ['utils'],
types: ['model', 'adapter', 'serializer']
},
'modifier-managers': {
types: ['modifier-manager']
},
partials: {
group: 'ui',
defaultType: 'partial',
privateCollections: ['utils'],
types: ['partial']
},
routes: {
group: 'ui',
defaultType: 'route',
privateCollections: ['components', 'utils'],
types: ['route', 'controller', 'template']
},
services: {
defaultType: 'service',
privateCollections: ['utils'],
types: ['service']
},
utils: {
unresolvable: true
},
views: {
defaultType: 'view',
privateCollections: ['utils'],
types: ['view']
},
transforms: {
group: 'data',
defaultType: 'transform',
privateCollections: ['utils'],
types: ['transform']
}
}
};
}
});
define("ember-resolver/module-registries/requirejs", ["exports", "@glimmer/di"], function (_exports, _di) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
class RequireJSRegistry {
constructor(config, modulePrefix, require = self.requirejs) {
this._config = config;
this._modulePrefix = modulePrefix;
this._require = require;
}
_baseSegments(s) {
let collectionDefinition = this._config.collections[s.collection];
let group = collectionDefinition && collectionDefinition.group;
let segments = [s.rootName, this._modulePrefix];
if (group) {
segments.push(group);
} // Special case to handle definitiveCollection for templates
// eventually want to find a better way to address.
// Dgeb wants to find a better way to handle these
// in config without needing definitiveCollection.
let ignoreCollection = s.type === 'template' && s.collection === 'routes' && s.namespace === 'components';
if (s.collection !== 'main' && !ignoreCollection) {
segments.push(s.collection);
}
if (s.namespace) {
segments.push(s.namespace);
}
if (s.name !== 'main' || s.collection !== 'main') {
segments.push(s.name);
}
return segments;
}
_detectModule(specifier, lookupDefault, lookupNamed) {
let segments = this._baseSegments(specifier);
let basePath = "".concat(segments.join('/'));
let typedPath = "".concat(basePath, "/").concat(specifier.type);
let lookupResult = lookupDefault(typedPath);
if (!lookupResult) {
if (this._checkDefaultType(specifier)) {
lookupResult = lookupDefault(basePath);
} else {
lookupResult = lookupNamed(basePath);
}
}
return lookupResult;
}
_checkDefaultType(specifier) {
let collection = this._config.collections[specifier.collection];
return collection && collection.defaultType && collection.defaultType === specifier.type;
}
has(specifierString) {
let specifier = (0, _di.deserializeSpecifier)(specifierString);
/* return a boolean */
return this._detectModule(specifier, path => {
return path in this._require.entries;
}, path => {
if (path in this._require.entries) {
let result = this._require(path);
return specifier.type in result;
}
});
}
get(specifierString) {
let specifier = (0, _di.deserializeSpecifier)(specifierString);
/* return an export */
let moduleExport = this._detectModule(specifier, path => {
return path in this._require.entries && this._require(path).default;
}, path => {
return path in this._require.entries && this._require(path)[specifier.type];
});
return moduleExport;
}
}
_exports.default = RequireJSRegistry;
});
define("ember-resolver/resolvers/fallback/index", ["exports", "ember-resolver", "ember-resolver/resolvers/glimmer-wrapper"], function (_exports, _emberResolver, _glimmerWrapper) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
var _default = _glimmerWrapper.default.extend({
init(options) {
this._super(options);
this._fallback = _emberResolver.default.create(Ember.assign({
namespace: {
modulePrefix: this.config.app.name
}
}, options));
},
resolve(name) {
let result = this._super(name);
return result || this._fallback.resolve(this._fallback.normalize(name));
}
});
_exports.default = _default;
});
define("ember-resolver/resolvers/glimmer-wrapper/index", ["exports", "@glimmer/resolver/resolver", "ember-resolver/module-registries/requirejs"], function (_exports, _resolver, _requirejs) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
function slasherize(dotted) {
return dotted.replace(/\./g, '/');
}
const TEMPLATE_TO_PARTIAL = /^template:(.*\/)?_([\w-]+)/;
function isAbsoluteSpecifier(specifier) {
return specifier.indexOf(':/') !== -1;
}
function cleanupEmberSpecifier(specifier, source, _namespace) {
let [type, name] = specifier.split(':');
if (!name) {
return [specifier, null];
}
if (type === 'component' && name) {
specifier = "".concat(type, ":").concat(name);
} else if (type === 'service') {
/* Services may be camelCased */
specifier = "service:".concat(Ember.String.dasherize(name));
} else if (type === 'route') {
/* Routes may have.dot.paths */
specifier = "route:".concat(slasherize(name));
} else if (type === 'controller') {
/* Controllers may have.dot.paths */
specifier = "controller:".concat(slasherize(name));
} else if (type === 'template') {
if (name && name.indexOf('components/') === 0) {
let sliced = name.slice(11);
specifier = "template:".concat(sliced);
} else {
/*
* Ember partials are looked up as templates. Here we replace the template
* resolution with a partial resolute when appropriate. Try to keep this
* code as "pay-go" as possible.
*/
let match = TEMPLATE_TO_PARTIAL.exec(specifier);
if (match) {
let namespace = match[1] || '';
let name = match[2];
specifier = "partial:".concat(namespace).concat(name);
} else {
if (source) {
throw new Error("Cannot look up a route template ".concat(specifier, " with a source"));
}
/*
* Templates for routes must be looked up with a source. They may
* have dots.in.paths
*/
specifier = "template";
source = "route:/".concat(_namespace, "/routes/").concat(slasherize(name));
}
}
}
return [specifier, source];
}
const normalize = !true
/* DEBUG */
? null : function (fullName) {
// This method is called by `Registry#validateInjections` in dev mode.
// https://github.com/ember-cli/ember-resolver/issues/299
if (fullName) {
const [type, name] = fullName.split(':', 2);
if (name && (type === 'service' || type === 'controller')) {
return "".concat(type, ":").concat(Ember.String.dasherize(name));
}
}
return fullName;
};
/*
* Wrap the @glimmer/resolver in Ember's resolver API. Although
* this code extends from the DefaultResolver, it should never
* call `_super` or call into that code.
*/
const Resolver = Ember.DefaultResolver.extend({
init() {
this._super(...arguments);
this._configRootName = this.config.app.rootName || 'app';
if (!this.glimmerModuleRegistry) {
this.glimmerModuleRegistry = new _requirejs.default(this.config, 'src');
}
this._glimmerResolver = new _resolver.default(this.config, this.glimmerModuleRegistry);
},
normalize,
expandLocalLookup(specifier, source, namespace) {
if (isAbsoluteSpecifier(specifier)) {
return specifier; // specifier is absolute
}
if (source || namespace) {
let rootName = namespace || this._configRootName;
let [type] = specifier.split(':');
/*
* Ember components require their lookupString to be massaged. Make this
* as "pay-go" as possible.
*/
if (namespace) {
// This is only required because:
// https://github.com/glimmerjs/glimmer-di/issues/45
source = "".concat(type, ":/").concat(rootName, "/");
} else if (source) {
// make absolute
let parts = source.split(':src/ui/');
source = "".concat(parts[0], ":/").concat(rootName, "/").concat(parts[1]);
source = source.split('/template.hbs')[0];
}
let [_specifier, _source] = cleanupEmberSpecifier(specifier, source, rootName);
let absoluteSpecifier = this._glimmerResolver.identify(_specifier, _source);
if (absoluteSpecifier) {
return absoluteSpecifier;
}
absoluteSpecifier = this._glimmerResolver.identify(_specifier);
if (absoluteSpecifier) {
return specifier;
}
}
return specifier;
},
resolve(specifier) {
let source = null;
if (!isAbsoluteSpecifier(specifier)) {
let [_specifier, _source] = cleanupEmberSpecifier(specifier, source, this._configRootName);
specifier = _specifier;
source = _source;
}
return this._glimmerResolver.resolve(specifier, source);
}
});
var _default = Resolver;
_exports.default = _default;
});