@converse/skeletor 3.1.6 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +55 -0
- package/dist/skeletor.d.ts +922 -931
- package/dist/skeletor.esm.js +366 -246
- package/dist/skeletor.esm.js.map +1 -1
- package/dist/skeletor.js +366 -245
- package/dist/skeletor.js.map +1 -1
- package/dist/skeletor.min.js +5 -5
- package/dist/skeletor.node.d.ts +947 -956
- package/dist/skeletor.node.js +366 -245
- package/dist/skeletor.node.mjs +366 -246
- package/package.json +7 -10
- package/src/collection.ts +82 -87
- package/src/helpers.ts +135 -25
- package/src/index.ts +4 -2
- package/src/model.ts +128 -100
package/dist/skeletor.esm.js
CHANGED
|
@@ -1668,19 +1668,6 @@ function __values(o) {
|
|
|
1668
1668
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
1669
1669
|
}
|
|
1670
1670
|
|
|
1671
|
-
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
1672
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
1673
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
1674
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
1678
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
1679
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
1680
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
1681
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
1671
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
1685
1672
|
var e = new Error(message);
|
|
1686
1673
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
@@ -8471,6 +8458,16 @@ function guid() {
|
|
|
8471
8458
|
// Generate a pseudo-GUID by concatenating random hexadecimal.
|
|
8472
8459
|
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
|
|
8473
8460
|
}
|
|
8461
|
+
// Helpers
|
|
8462
|
+
// -------
|
|
8463
|
+
/**
|
|
8464
|
+
* Whether `value` is a thenable (a promise, or a promise-like object such as
|
|
8465
|
+
* a jqXHR). Decides whether a `sync` return value can be bridged to the
|
|
8466
|
+
* promise that `fetch`/`save` hand back.
|
|
8467
|
+
*/
|
|
8468
|
+
function isThenable(value) {
|
|
8469
|
+
return value != null && typeof value.then === 'function';
|
|
8470
|
+
}
|
|
8474
8471
|
function getResolveablePromise() {
|
|
8475
8472
|
const wrapper = {
|
|
8476
8473
|
isResolved: false,
|
|
@@ -8515,6 +8512,65 @@ function wrapError(model, options, promise) {
|
|
|
8515
8512
|
promise && promise.reject && promise.reject(resp);
|
|
8516
8513
|
};
|
|
8517
8514
|
}
|
|
8515
|
+
/**
|
|
8516
|
+
* Run a `sync` and make sure `promise` settles from its outcome, however the
|
|
8517
|
+
* sync reports it.
|
|
8518
|
+
*
|
|
8519
|
+
* A `sync` may invoke `options.success`/`options.error` (the callback
|
|
8520
|
+
* contract), or settle the promise it returns without calling them: a
|
|
8521
|
+
* promise-only custom sync, or an async sync whose promise rejects because a
|
|
8522
|
+
* `'sync'`/`'error'` listener threw after the callbacks ran. `options.success`
|
|
8523
|
+
* and `options.error` must already be wired to resolve/reject `promise`. This
|
|
8524
|
+
* wraps them to record that they ran, calls `run()`, and bridges a thenable
|
|
8525
|
+
* result to them so the promise never hangs:
|
|
8526
|
+
*
|
|
8527
|
+
* - fulfilment with the callbacks unrun is delivered to `options.success`, so
|
|
8528
|
+
* the response is still merged into the model or collection;
|
|
8529
|
+
* - rejection with the callbacks unrun is delivered to `options.error`, so the
|
|
8530
|
+
* caller's `error` callback and the `'error'` event still fire;
|
|
8531
|
+
* - rejection after the callbacks ran rejects `promise` directly: a no-op if
|
|
8532
|
+
* they already settled it, and the only way to settle it if a listener threw
|
|
8533
|
+
* before they could;
|
|
8534
|
+
* - a throw from either delivery (a throwing `success`/`error` callback or
|
|
8535
|
+
* listener) rejects `promise` instead of surfacing as an unhandled rejection.
|
|
8536
|
+
*
|
|
8537
|
+
* A non-thenable result (a callback-only sync that returns nothing, or an
|
|
8538
|
+
* XHR) is left alone: such a sync settles `promise` through its callbacks, and
|
|
8539
|
+
* bridging it would fire `success` on the next microtask, before the sync is
|
|
8540
|
+
* done. Without a `promise`, `run()` is called as is.
|
|
8541
|
+
*/
|
|
8542
|
+
function bridgeSyncToPromise(options, promise, run) {
|
|
8543
|
+
if (!promise)
|
|
8544
|
+
return run();
|
|
8545
|
+
let settled = false;
|
|
8546
|
+
const success = options.success;
|
|
8547
|
+
const error = options.error;
|
|
8548
|
+
options.success = (...args) => {
|
|
8549
|
+
settled = true;
|
|
8550
|
+
return success && success(...args);
|
|
8551
|
+
};
|
|
8552
|
+
options.error = (...args) => {
|
|
8553
|
+
settled = true;
|
|
8554
|
+
return error && error(...args);
|
|
8555
|
+
};
|
|
8556
|
+
const result = run();
|
|
8557
|
+
if (isThenable(result)) {
|
|
8558
|
+
Promise.resolve(result)
|
|
8559
|
+
.then((value) => {
|
|
8560
|
+
if (!settled)
|
|
8561
|
+
options.success(value);
|
|
8562
|
+
}, (e) => {
|
|
8563
|
+
if (settled) {
|
|
8564
|
+
promise.reject(e);
|
|
8565
|
+
}
|
|
8566
|
+
else {
|
|
8567
|
+
options.error(e);
|
|
8568
|
+
}
|
|
8569
|
+
})
|
|
8570
|
+
.catch((e) => promise.reject(e));
|
|
8571
|
+
}
|
|
8572
|
+
return result;
|
|
8573
|
+
}
|
|
8518
8574
|
// Map from CRUD to HTTP for our default `sync` implementation.
|
|
8519
8575
|
const methodMap = {
|
|
8520
8576
|
create: 'POST',
|
|
@@ -8564,43 +8620,77 @@ function getSyncMethod(model) {
|
|
|
8564
8620
|
return store ? store.sync() : sync;
|
|
8565
8621
|
}
|
|
8566
8622
|
/**
|
|
8567
|
-
*
|
|
8568
|
-
*
|
|
8569
|
-
*
|
|
8570
|
-
* to the model's `url()`.
|
|
8571
|
-
*
|
|
8572
|
-
* Some possible customizations could be:
|
|
8573
|
-
*
|
|
8574
|
-
* - Use `setTimeout` to batch rapid-fire updates into a single request.
|
|
8575
|
-
* - Persist models via WebSockets instead of Ajax.
|
|
8576
|
-
* - Persist models to browser storage
|
|
8623
|
+
* The error the default `sync` hands to `options.error`, and rejects with, for
|
|
8624
|
+
* a response outside the 2xx range.
|
|
8625
|
+
* @public
|
|
8577
8626
|
*/
|
|
8627
|
+
class HTTPError extends Error {
|
|
8628
|
+
constructor(response, method, url) {
|
|
8629
|
+
const statusText = response.statusText ? ` ${response.statusText}` : '';
|
|
8630
|
+
super(`${method} ${url} failed with HTTP ${response.status}${statusText}`);
|
|
8631
|
+
this.name = 'HTTPError';
|
|
8632
|
+
this.status = response.status;
|
|
8633
|
+
this.response = response;
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8578
8636
|
/**
|
|
8637
|
+
* The default `sync`: a JSON request over `fetch()` to the model's `url()`.
|
|
8638
|
+
* Override it (or `Model.sync`/`Collection.sync`) to change how models are
|
|
8639
|
+
* persisted, for instance to batch rapid-fire updates with `setTimeout`, to
|
|
8640
|
+
* talk over WebSockets, or to write to browser storage.
|
|
8641
|
+
*
|
|
8642
|
+
* It honours both contracts a `sync` may use. On a 2xx response the JSON body
|
|
8643
|
+
* (`null` when the body is empty) is passed to `options.success` and the
|
|
8644
|
+
* returned promise resolves with it. On a non-2xx response, a network failure
|
|
8645
|
+
* or an unparsable body, `options.error` receives the error (an `HTTPError`
|
|
8646
|
+
* for the first) and the promise rejects with it.
|
|
8647
|
+
*
|
|
8648
|
+
* `options.data` is sent as query parameters on GET, which may not carry a
|
|
8649
|
+
* body, and as a JSON body otherwise. For create, update and patch the body
|
|
8650
|
+
* defaults to `options.attrs` or the model's `toJSON()`.
|
|
8579
8651
|
* @public
|
|
8580
8652
|
*/
|
|
8581
8653
|
function sync(method, model, options = {}) {
|
|
8654
|
+
const type = methodMap[method];
|
|
8582
8655
|
let data = options.data;
|
|
8583
8656
|
// Ensure that we have the appropriate request data.
|
|
8584
8657
|
if (data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
|
8585
8658
|
data = options.attrs || model.toJSON();
|
|
8586
8659
|
}
|
|
8587
|
-
|
|
8588
|
-
const params = {
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
const url = options.url || result(model, 'url') || urlError();
|
|
8598
|
-
const xhr = fetch(url, params);
|
|
8599
|
-
if (options) {
|
|
8600
|
-
options.xhr = xhr;
|
|
8660
|
+
let url = options.url || result(model, 'url') || urlError();
|
|
8661
|
+
const params = { method: type };
|
|
8662
|
+
if (data != null) {
|
|
8663
|
+
if (type === 'GET') {
|
|
8664
|
+
url += (url.includes('?') ? '&' : '?') + new URLSearchParams(data).toString();
|
|
8665
|
+
}
|
|
8666
|
+
else {
|
|
8667
|
+
params.body = JSON.stringify(data);
|
|
8668
|
+
params.headers = { 'Content-Type': 'application/json' };
|
|
8669
|
+
}
|
|
8601
8670
|
}
|
|
8671
|
+
const xhr = fetch(url, params);
|
|
8672
|
+
options.xhr = xhr;
|
|
8602
8673
|
model.trigger('request', model, xhr, { ...params, xhr });
|
|
8603
|
-
return xhr
|
|
8674
|
+
return Promise.resolve(xhr)
|
|
8675
|
+
.then(async (response) => {
|
|
8676
|
+
if (!response.ok)
|
|
8677
|
+
throw new HTTPError(response, type, url);
|
|
8678
|
+
const text = await response.text();
|
|
8679
|
+
return text ? JSON.parse(text) : null;
|
|
8680
|
+
})
|
|
8681
|
+
.then(
|
|
8682
|
+
// The callbacks run before the returned promise settles, so a caller that
|
|
8683
|
+
// bridges this promise (`Model.fetch`/`save`) sees the outcome as already
|
|
8684
|
+
// reported. A throw from `success` (a `'sync'` listener, say) is not
|
|
8685
|
+
// routed to `error`: it rejects the promise, which is what the bridge
|
|
8686
|
+
// expects once the callbacks have run.
|
|
8687
|
+
(resp) => {
|
|
8688
|
+
options.success?.(resp, options);
|
|
8689
|
+
return resp;
|
|
8690
|
+
}, (e) => {
|
|
8691
|
+
options.error?.(e);
|
|
8692
|
+
throw e;
|
|
8693
|
+
});
|
|
8604
8694
|
}
|
|
8605
8695
|
|
|
8606
8696
|
/**
|
|
@@ -9966,6 +10056,48 @@ var keyBy = createAggregator(function(result, value, key) {
|
|
|
9966
10056
|
baseAssignValue(result, key, value);
|
|
9967
10057
|
});
|
|
9968
10058
|
|
|
10059
|
+
/** Used to compose bitmasks for cloning. */
|
|
10060
|
+
var CLONE_DEEP_FLAG$1 = 1;
|
|
10061
|
+
|
|
10062
|
+
/**
|
|
10063
|
+
* Creates a function that performs a partial deep comparison between a given
|
|
10064
|
+
* object and `source`, returning `true` if the given object has equivalent
|
|
10065
|
+
* property values, else `false`.
|
|
10066
|
+
*
|
|
10067
|
+
* **Note:** The created function is equivalent to `_.isMatch` with `source`
|
|
10068
|
+
* partially applied.
|
|
10069
|
+
*
|
|
10070
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10071
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10072
|
+
* for a list of supported value comparisons.
|
|
10073
|
+
*
|
|
10074
|
+
* **Note:** Multiple values can be checked by combining several matchers
|
|
10075
|
+
* using `_.overSome`
|
|
10076
|
+
*
|
|
10077
|
+
* @static
|
|
10078
|
+
* @memberOf _
|
|
10079
|
+
* @since 3.0.0
|
|
10080
|
+
* @category Util
|
|
10081
|
+
* @param {Object} source The object of property values to match.
|
|
10082
|
+
* @returns {Function} Returns the new spec function.
|
|
10083
|
+
* @example
|
|
10084
|
+
*
|
|
10085
|
+
* var objects = [
|
|
10086
|
+
* { 'a': 1, 'b': 2, 'c': 3 },
|
|
10087
|
+
* { 'a': 4, 'b': 5, 'c': 6 }
|
|
10088
|
+
* ];
|
|
10089
|
+
*
|
|
10090
|
+
* _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
|
|
10091
|
+
* // => [{ 'a': 4, 'b': 5, 'c': 6 }]
|
|
10092
|
+
*
|
|
10093
|
+
* // Checking for several possible values
|
|
10094
|
+
* _.filter(objects, _.overSome([_.matches({ 'a': 1 }), _.matches({ 'a': 4 })]));
|
|
10095
|
+
* // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }]
|
|
10096
|
+
*/
|
|
10097
|
+
function matches(source) {
|
|
10098
|
+
return baseMatches(baseClone(source, CLONE_DEEP_FLAG$1));
|
|
10099
|
+
}
|
|
10100
|
+
|
|
9969
10101
|
/** Built-in value references. */
|
|
9970
10102
|
var spreadableSymbol = Symbol$1 ? Symbol$1.isConcatSpreadable : undefined;
|
|
9971
10103
|
|
|
@@ -10529,53 +10661,36 @@ function isEqual(value, other) {
|
|
|
10529
10661
|
return baseIsEqual(value, other);
|
|
10530
10662
|
}
|
|
10531
10663
|
|
|
10532
|
-
/** Used to compose bitmasks for cloning. */
|
|
10533
|
-
var CLONE_DEEP_FLAG$1 = 1;
|
|
10534
|
-
|
|
10535
10664
|
/**
|
|
10536
|
-
*
|
|
10537
|
-
*
|
|
10538
|
-
*
|
|
10539
|
-
*
|
|
10540
|
-
*
|
|
10665
|
+
* Performs a partial deep comparison between `object` and `source` to
|
|
10666
|
+
* determine if `object` contains equivalent property values.
|
|
10667
|
+
*
|
|
10668
|
+
* **Note:** This method is equivalent to `_.matches` when `source` is
|
|
10669
|
+
* partially applied.
|
|
10670
|
+
*
|
|
10671
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10672
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10673
|
+
* for a list of supported value comparisons.
|
|
10541
10674
|
*
|
|
10542
10675
|
* @static
|
|
10543
|
-
* @since 4.0.0
|
|
10544
10676
|
* @memberOf _
|
|
10545
|
-
* @
|
|
10546
|
-
* @
|
|
10547
|
-
* @
|
|
10677
|
+
* @since 3.0.0
|
|
10678
|
+
* @category Lang
|
|
10679
|
+
* @param {Object} object The object to inspect.
|
|
10680
|
+
* @param {Object} source The object of property values to match.
|
|
10681
|
+
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
|
|
10548
10682
|
* @example
|
|
10549
10683
|
*
|
|
10550
|
-
* var
|
|
10551
|
-
* { 'user': 'barney', 'age': 36, 'active': true },
|
|
10552
|
-
* { 'user': 'fred', 'age': 40, 'active': false }
|
|
10553
|
-
* ];
|
|
10554
|
-
*
|
|
10555
|
-
* // The `_.matches` iteratee shorthand.
|
|
10556
|
-
* _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
|
|
10557
|
-
* // => [{ 'user': 'barney', 'age': 36, 'active': true }]
|
|
10558
|
-
*
|
|
10559
|
-
* // The `_.matchesProperty` iteratee shorthand.
|
|
10560
|
-
* _.filter(users, _.iteratee(['user', 'fred']));
|
|
10561
|
-
* // => [{ 'user': 'fred', 'age': 40 }]
|
|
10684
|
+
* var object = { 'a': 1, 'b': 2 };
|
|
10562
10685
|
*
|
|
10563
|
-
*
|
|
10564
|
-
*
|
|
10565
|
-
* // => ['barney', 'fred']
|
|
10566
|
-
*
|
|
10567
|
-
* // Create custom iteratee shorthands.
|
|
10568
|
-
* _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
|
|
10569
|
-
* return !_.isRegExp(func) ? iteratee(func) : function(string) {
|
|
10570
|
-
* return func.test(string);
|
|
10571
|
-
* };
|
|
10572
|
-
* });
|
|
10686
|
+
* _.isMatch(object, { 'b': 2 });
|
|
10687
|
+
* // => true
|
|
10573
10688
|
*
|
|
10574
|
-
* _.
|
|
10575
|
-
* // =>
|
|
10689
|
+
* _.isMatch(object, { 'b': 1 });
|
|
10690
|
+
* // => false
|
|
10576
10691
|
*/
|
|
10577
|
-
function
|
|
10578
|
-
return
|
|
10692
|
+
function isMatch(object, source) {
|
|
10693
|
+
return object === source || baseIsMatch(object, source, getMatchData(source));
|
|
10579
10694
|
}
|
|
10580
10695
|
|
|
10581
10696
|
/**
|
|
@@ -10880,7 +10995,47 @@ var pick = flatRest(function(object, paths) {
|
|
|
10880
10995
|
return object == null ? {} : basePick(object, paths);
|
|
10881
10996
|
});
|
|
10882
10997
|
|
|
10883
|
-
|
|
10998
|
+
const INTERNALS = new WeakMap();
|
|
10999
|
+
function internals(model) {
|
|
11000
|
+
let priv = INTERNALS.get(model);
|
|
11001
|
+
if (!priv) {
|
|
11002
|
+
priv = { state: 'constructing' };
|
|
11003
|
+
INTERNALS.set(model, priv);
|
|
11004
|
+
}
|
|
11005
|
+
return priv;
|
|
11006
|
+
}
|
|
11007
|
+
/** Load the model from storage, marking it ready whether or not the read succeeds. */
|
|
11008
|
+
async function hydrate$1(model, priv) {
|
|
11009
|
+
ensureUnloadListener(PersistentStorage);
|
|
11010
|
+
try {
|
|
11011
|
+
await model.fetch({ fromStorage: true, promise: true });
|
|
11012
|
+
}
|
|
11013
|
+
finally {
|
|
11014
|
+
priv.state = 'ready';
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
/** Evaluate the model's declared computed properties once, seeding the cache. */
|
|
11018
|
+
function initComputed(model, priv) {
|
|
11019
|
+
const defs = result(model, 'computed');
|
|
11020
|
+
if (!defs || isEmpty(defs))
|
|
11021
|
+
return;
|
|
11022
|
+
priv.computedDefs = defs;
|
|
11023
|
+
priv.computedCache = {};
|
|
11024
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
11025
|
+
priv.computedCache[key] = def.fn(model);
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
/** Recompute one computed property, firing `change:key` when its value changed. */
|
|
11029
|
+
function updateComputed(model, priv, key, def, silent, options) {
|
|
11030
|
+
const newVal = def.fn(model);
|
|
11031
|
+
if (!isEqual(priv.computedCache[key], newVal)) {
|
|
11032
|
+
priv.computedCache[key] = newVal;
|
|
11033
|
+
if (!silent) {
|
|
11034
|
+
model.changed[key] = newVal;
|
|
11035
|
+
model.trigger('change:' + key, model, newVal, options);
|
|
11036
|
+
}
|
|
11037
|
+
}
|
|
11038
|
+
}
|
|
10884
11039
|
/**
|
|
10885
11040
|
* @public
|
|
10886
11041
|
* **Models** are the basic data object in the framework --
|
|
@@ -10895,15 +11050,12 @@ class Model extends EventEmitterObject {
|
|
|
10895
11050
|
*/
|
|
10896
11051
|
constructor(attributes, options) {
|
|
10897
11052
|
super();
|
|
10898
|
-
_Model_instances.add(this);
|
|
10899
|
-
_Model_computedCache.set(this, void 0);
|
|
10900
|
-
_Model_computedDefs.set(this, void 0);
|
|
10901
|
-
_Model_state.set(this, 'constructing');
|
|
10902
11053
|
this._changing = false;
|
|
10903
11054
|
this._pending = false;
|
|
10904
11055
|
this._url = '';
|
|
10905
11056
|
this.changed = {};
|
|
10906
11057
|
this.validationError = null;
|
|
11058
|
+
const priv = internals(this);
|
|
10907
11059
|
let attrs = attributes || {};
|
|
10908
11060
|
options = options || {};
|
|
10909
11061
|
this.preinitialize.apply(this, arguments);
|
|
@@ -10916,11 +11068,12 @@ class Model extends EventEmitterObject {
|
|
|
10916
11068
|
const default_attrs = result(this, 'defaults');
|
|
10917
11069
|
attrs = defaults(Object.assign({}, default_attrs, attrs), default_attrs);
|
|
10918
11070
|
this.set(attrs, options);
|
|
10919
|
-
|
|
11071
|
+
initComputed(this, priv);
|
|
10920
11072
|
this.attrs = new Proxy(this.attributes, {
|
|
10921
11073
|
get: (_target, key) => {
|
|
10922
|
-
|
|
10923
|
-
|
|
11074
|
+
const cache = priv.computedCache;
|
|
11075
|
+
if (typeof key !== 'symbol' && cache && key in cache) {
|
|
11076
|
+
return cache[key];
|
|
10924
11077
|
}
|
|
10925
11078
|
return this.attributes[key];
|
|
10926
11079
|
},
|
|
@@ -10938,16 +11091,16 @@ class Model extends EventEmitterObject {
|
|
|
10938
11091
|
// otherwise it's already ready and resolves immediately.
|
|
10939
11092
|
if (this.autoSync) {
|
|
10940
11093
|
if (getStorage(this) && !this.isNew()) {
|
|
10941
|
-
|
|
10942
|
-
this.hydrated =
|
|
11094
|
+
priv.state = 'hydrating';
|
|
11095
|
+
this.hydrated = hydrate$1(this, priv);
|
|
10943
11096
|
}
|
|
10944
11097
|
else {
|
|
10945
|
-
|
|
11098
|
+
priv.state = 'ready';
|
|
10946
11099
|
this.hydrated = Promise.resolve();
|
|
10947
11100
|
}
|
|
10948
11101
|
}
|
|
10949
11102
|
else {
|
|
10950
|
-
|
|
11103
|
+
priv.state = 'ready';
|
|
10951
11104
|
}
|
|
10952
11105
|
}
|
|
10953
11106
|
/**
|
|
@@ -11046,8 +11199,9 @@ class Model extends EventEmitterObject {
|
|
|
11046
11199
|
* Get the value of an attribute, or a computed property.
|
|
11047
11200
|
*/
|
|
11048
11201
|
get(attr) {
|
|
11049
|
-
|
|
11050
|
-
|
|
11202
|
+
const cache = internals(this).computedCache;
|
|
11203
|
+
if (cache && attr in cache) {
|
|
11204
|
+
return cache[attr];
|
|
11051
11205
|
}
|
|
11052
11206
|
return this.attributes[attr];
|
|
11053
11207
|
}
|
|
@@ -11089,10 +11243,20 @@ class Model extends EventEmitterObject {
|
|
|
11089
11243
|
return this.get(attr) != null;
|
|
11090
11244
|
}
|
|
11091
11245
|
/**
|
|
11092
|
-
*
|
|
11246
|
+
* Returns `true` if the model's attributes satisfy `attrs`.
|
|
11247
|
+
*
|
|
11248
|
+
* An attributes object is compared with lodash's `isMatch`: a partial, deep
|
|
11249
|
+
* comparison, so `{ form: { type: 'hidden' } }` matches a `form` attribute
|
|
11250
|
+
* that has `type: 'hidden'` alongside other keys. A function is called with
|
|
11251
|
+
* the attributes and its result coerced to a boolean.
|
|
11252
|
+
*
|
|
11253
|
+
* To scan a collection, prefer `Collection#find`, `filter` or `where`. They
|
|
11254
|
+
* build their matcher once per scan instead of once per model.
|
|
11093
11255
|
*/
|
|
11094
11256
|
matches(attrs) {
|
|
11095
|
-
|
|
11257
|
+
if (typeof attrs === 'function')
|
|
11258
|
+
return !!attrs(this.attributes);
|
|
11259
|
+
return isMatch(this.attributes, attrs);
|
|
11096
11260
|
}
|
|
11097
11261
|
/**
|
|
11098
11262
|
* Set a hash of model attributes on the object, firing `"change"`. This is
|
|
@@ -11113,9 +11277,10 @@ class Model extends EventEmitterObject {
|
|
|
11113
11277
|
attrs[key] = val;
|
|
11114
11278
|
}
|
|
11115
11279
|
options = options || {};
|
|
11280
|
+
const priv = internals(this);
|
|
11116
11281
|
// Guard must come after arg normalization: when called as set(obj, opts),
|
|
11117
11282
|
// fromStorage lives in `val` (second arg), not `options` (third arg).
|
|
11118
|
-
if (this.autoSync &&
|
|
11283
|
+
if (this.autoSync && priv.state === 'hydrating' && !options.fromStorage) {
|
|
11119
11284
|
throw new Error(`Skeletor: set() called on an autoSync model before hydration resolved. Await model.hydrated first.`);
|
|
11120
11285
|
}
|
|
11121
11286
|
// Run validation.
|
|
@@ -11159,10 +11324,10 @@ class Model extends EventEmitterObject {
|
|
|
11159
11324
|
}
|
|
11160
11325
|
}
|
|
11161
11326
|
// Recalculate computed properties whose deps changed.
|
|
11162
|
-
if (changes.length &&
|
|
11163
|
-
for (const [key, def] of Object.entries(
|
|
11327
|
+
if (changes.length && priv.computedDefs) {
|
|
11328
|
+
for (const [key, def] of Object.entries(priv.computedDefs)) {
|
|
11164
11329
|
if (def.deps.some((dep) => changes.includes(dep))) {
|
|
11165
|
-
|
|
11330
|
+
updateComputed(this, priv, key, def, silent, options);
|
|
11166
11331
|
}
|
|
11167
11332
|
}
|
|
11168
11333
|
}
|
|
@@ -11182,13 +11347,13 @@ class Model extends EventEmitterObject {
|
|
|
11182
11347
|
// Auto-save: schedule a debounced write when autoSync is on and there
|
|
11183
11348
|
// were actual changes that originated from application code (not a
|
|
11184
11349
|
// storage read or an explicit noAutoSave call).
|
|
11185
|
-
if (
|
|
11350
|
+
if (priv.state === 'ready' &&
|
|
11186
11351
|
this.autoSync &&
|
|
11187
11352
|
getStorage(this) &&
|
|
11188
11353
|
changes.length &&
|
|
11189
11354
|
!options.fromStorage &&
|
|
11190
11355
|
!options.noAutoSave) {
|
|
11191
|
-
scheduleAutoSave(this, () =>
|
|
11356
|
+
scheduleAutoSave(this, () => this.save(null, { noAutoSave: true }), this.autoSyncDelay);
|
|
11192
11357
|
}
|
|
11193
11358
|
return this;
|
|
11194
11359
|
}
|
|
@@ -11265,47 +11430,25 @@ class Model extends EventEmitterObject {
|
|
|
11265
11430
|
options = Object.assign({ parse: true }, options);
|
|
11266
11431
|
const success = options.success;
|
|
11267
11432
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
11268
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
11269
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
11270
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
11271
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
11272
|
-
// it's reliable by the time those handlers run.
|
|
11273
|
-
let settled = false;
|
|
11274
11433
|
options.success = (resp) => {
|
|
11275
|
-
settled = true;
|
|
11276
11434
|
const serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11277
11435
|
// fromStorage prevents the set() from triggering auto-save on reads
|
|
11278
|
-
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true })))
|
|
11436
|
+
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true }))) {
|
|
11437
|
+
// The fetched attributes failed validation (`'invalid'` has fired).
|
|
11438
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11439
|
+
promise && promise.reject(this.validationError);
|
|
11279
11440
|
return false;
|
|
11441
|
+
}
|
|
11280
11442
|
if (success)
|
|
11281
11443
|
success.call(options.context, this, resp, options);
|
|
11282
11444
|
this.trigger('sync', this, resp, options);
|
|
11283
11445
|
promise && promise.resolve(resp);
|
|
11284
11446
|
};
|
|
11285
11447
|
wrapError(this, options, promise);
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
onError(resp);
|
|
11291
|
-
};
|
|
11292
|
-
}
|
|
11293
|
-
const result = this.sync('read', this, options);
|
|
11294
|
-
if (promise) {
|
|
11295
|
-
Promise.resolve(result).then(
|
|
11296
|
-
// A sync that resolves its promise without invoking options.success:
|
|
11297
|
-
// treat the resolved value as the response, so the model is still
|
|
11298
|
-
// merged and the promise settles instead of hanging.
|
|
11299
|
-
(value) => {
|
|
11300
|
-
if (!settled)
|
|
11301
|
-
options.success(value);
|
|
11302
|
-
},
|
|
11303
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
11304
|
-
// twice is a harmless no-op.
|
|
11305
|
-
(e) => promise.reject(e));
|
|
11306
|
-
return promise;
|
|
11307
|
-
}
|
|
11308
|
-
return result;
|
|
11448
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11449
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11450
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
11451
|
+
return promise ?? result;
|
|
11309
11452
|
}
|
|
11310
11453
|
/**
|
|
11311
11454
|
* Set a hash of model attributes, and sync the model to the server.
|
|
@@ -11326,16 +11469,19 @@ class Model extends EventEmitterObject {
|
|
|
11326
11469
|
options = Object.assign({ validate: true, parse: true }, options);
|
|
11327
11470
|
const wait = options.wait;
|
|
11328
11471
|
const return_promise = options.promise;
|
|
11329
|
-
const promise = return_promise
|
|
11472
|
+
const promise = return_promise ? getResolveablePromise() : undefined;
|
|
11330
11473
|
// If we're not waiting and attributes exist, save acts as
|
|
11331
11474
|
// `set(attr).save(null, opts)` with validation. Otherwise, check if
|
|
11332
11475
|
// the model will be valid when the attributes, if any, are set.
|
|
11333
|
-
|
|
11334
|
-
|
|
11476
|
+
const valid = attrs && !wait ? !!this.set(attrs, options) : this._validate(attrs || {}, options);
|
|
11477
|
+
if (!valid) {
|
|
11478
|
+
// Nothing is synced (`'invalid'` has fired). With `promise`, hand back a
|
|
11479
|
+
// rejected promise rather than `false`, so the promise contract holds
|
|
11480
|
+
// for every outcome.
|
|
11481
|
+
if (!promise)
|
|
11335
11482
|
return false;
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
return false;
|
|
11483
|
+
promise.reject(this.validationError);
|
|
11484
|
+
return promise;
|
|
11339
11485
|
}
|
|
11340
11486
|
// After a successful server-side save, the client is (optionally)
|
|
11341
11487
|
// updated with the server-side state.
|
|
@@ -11348,8 +11494,12 @@ class Model extends EventEmitterObject {
|
|
|
11348
11494
|
let serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11349
11495
|
if (wait)
|
|
11350
11496
|
serverAttrs = Object.assign({}, attrs, serverAttrs);
|
|
11351
|
-
if (serverAttrs && !this.set(serverAttrs, options))
|
|
11497
|
+
if (serverAttrs && !this.set(serverAttrs, options)) {
|
|
11498
|
+
// The server attributes failed validation (`'invalid'` has fired).
|
|
11499
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11500
|
+
return_promise && promise.reject(this.validationError);
|
|
11352
11501
|
return false;
|
|
11502
|
+
}
|
|
11353
11503
|
if (success)
|
|
11354
11504
|
success.call(options.context, this, resp, options);
|
|
11355
11505
|
this.trigger('sync', this, resp, options);
|
|
@@ -11366,15 +11516,12 @@ class Model extends EventEmitterObject {
|
|
|
11366
11516
|
const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
|
11367
11517
|
if (method === 'patch' && !options.attrs)
|
|
11368
11518
|
options.attrs = attrs;
|
|
11369
|
-
|
|
11519
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11520
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11521
|
+
const xhr = bridgeSyncToPromise(options, promise, () => this.sync(method, this, options));
|
|
11370
11522
|
// Restore attributes.
|
|
11371
11523
|
this.attributes = attributes;
|
|
11372
|
-
|
|
11373
|
-
return promise;
|
|
11374
|
-
}
|
|
11375
|
-
else {
|
|
11376
|
-
return xhr;
|
|
11377
|
-
}
|
|
11524
|
+
return promise ?? xhr;
|
|
11378
11525
|
}
|
|
11379
11526
|
/**
|
|
11380
11527
|
* Destroy this model on the server if it was already persisted.
|
|
@@ -11487,40 +11634,40 @@ class Model extends EventEmitterObject {
|
|
|
11487
11634
|
return false;
|
|
11488
11635
|
}
|
|
11489
11636
|
}
|
|
11490
|
-
_Model_computedCache = new WeakMap(), _Model_computedDefs = new WeakMap(), _Model_state = new WeakMap(), _Model_instances = new WeakSet(), _Model_hydrate = async function _Model_hydrate() {
|
|
11491
|
-
ensureUnloadListener(PersistentStorage);
|
|
11492
|
-
try {
|
|
11493
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
11494
|
-
}
|
|
11495
|
-
finally {
|
|
11496
|
-
__classPrivateFieldSet(this, _Model_state, 'ready', "f");
|
|
11497
|
-
}
|
|
11498
|
-
}, _Model_doAutoSave = function _Model_doAutoSave() {
|
|
11499
|
-
return this.save(null, { noAutoSave: true });
|
|
11500
|
-
}, _Model_updateComputed = function _Model_updateComputed(key, def, silent, options) {
|
|
11501
|
-
const newVal = def.fn(this);
|
|
11502
|
-
if (!isEqual(__classPrivateFieldGet(this, _Model_computedCache, "f")[key], newVal)) {
|
|
11503
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = newVal;
|
|
11504
|
-
if (!silent) {
|
|
11505
|
-
this.changed[key] = newVal;
|
|
11506
|
-
this.trigger('change:' + key, this, newVal, options);
|
|
11507
|
-
}
|
|
11508
|
-
}
|
|
11509
|
-
}, _Model_initComputed = function _Model_initComputed() {
|
|
11510
|
-
const defs = result(this, 'computed');
|
|
11511
|
-
if (!defs || isEmpty(defs))
|
|
11512
|
-
return;
|
|
11513
|
-
__classPrivateFieldSet(this, _Model_computedDefs, defs, "f");
|
|
11514
|
-
__classPrivateFieldSet(this, _Model_computedCache, {}, "f");
|
|
11515
|
-
for (const [key, def] of Object.entries(defs)) {
|
|
11516
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = def.fn(this);
|
|
11517
|
-
}
|
|
11518
|
-
};
|
|
11519
11637
|
|
|
11520
|
-
var
|
|
11638
|
+
var _a;
|
|
11521
11639
|
// Default options for `Collection#set`.
|
|
11522
11640
|
const setOptions = { add: true, remove: true, merge: true };
|
|
11523
11641
|
const addOptions = { add: true, remove: false };
|
|
11642
|
+
/**
|
|
11643
|
+
* Normalizes the predicate forms accepted by the query methods into one
|
|
11644
|
+
* `(model) => value` function. A function is used as-is, a string reads that
|
|
11645
|
+
* attribute, and an attributes object becomes a lodash `matches` matcher.
|
|
11646
|
+
*
|
|
11647
|
+
* The matcher is built here, once per scan. Calling `Model#matches` per model
|
|
11648
|
+
* instead rebuilds the match data for every model visited, which made
|
|
11649
|
+
* `findWhere` an order of magnitude slower than a hand-written predicate.
|
|
11650
|
+
*/
|
|
11651
|
+
function toModelPredicate(pred) {
|
|
11652
|
+
if (isFunction(pred))
|
|
11653
|
+
return pred;
|
|
11654
|
+
if (isString(pred))
|
|
11655
|
+
return (m) => m.get(pred);
|
|
11656
|
+
const matcher = matches(pred);
|
|
11657
|
+
return (m) => matcher(m.attributes);
|
|
11658
|
+
}
|
|
11659
|
+
/**
|
|
11660
|
+
* Load the collection's members from storage. A module-level function rather
|
|
11661
|
+
* than a `#private` method so the published declarations carry no private
|
|
11662
|
+
* members, which TypeScript 6 rejects in anonymously extended classes (TS4094).
|
|
11663
|
+
*/
|
|
11664
|
+
async function hydrate(collection) {
|
|
11665
|
+
ensureUnloadListener(PersistentStorage);
|
|
11666
|
+
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
11667
|
+
// missing model record resolves to null, so hydration resolves rather
|
|
11668
|
+
// than rejecting.
|
|
11669
|
+
await collection.fetch({ fromStorage: true, promise: true });
|
|
11670
|
+
}
|
|
11524
11671
|
/**
|
|
11525
11672
|
* @public
|
|
11526
11673
|
* If models tend to represent a single row of data, a Collection is
|
|
@@ -11538,7 +11685,6 @@ class Collection extends EventEmitterObject {
|
|
|
11538
11685
|
*/
|
|
11539
11686
|
constructor(models, options) {
|
|
11540
11687
|
super();
|
|
11541
|
-
_Collection_instances.add(this);
|
|
11542
11688
|
this._url = '';
|
|
11543
11689
|
/**
|
|
11544
11690
|
* @public
|
|
@@ -11560,7 +11706,7 @@ class Collection extends EventEmitterObject {
|
|
|
11560
11706
|
// uniformly `await collection.hydrated`; it resolves immediately when
|
|
11561
11707
|
// there's no storage to hydrate from.
|
|
11562
11708
|
if (this.autoSync) {
|
|
11563
|
-
this.hydrated = getStorage(this) ?
|
|
11709
|
+
this.hydrated = getStorage(this) ? hydrate(this) : Promise.resolve();
|
|
11564
11710
|
}
|
|
11565
11711
|
}
|
|
11566
11712
|
get comparator() {
|
|
@@ -11849,17 +11995,13 @@ class Collection extends EventEmitterObject {
|
|
|
11849
11995
|
return this.models.slice(start, end);
|
|
11850
11996
|
}
|
|
11851
11997
|
filter(callback, thisArg) {
|
|
11852
|
-
return this.models.filter(
|
|
11853
|
-
? callback
|
|
11854
|
-
: (m) => m.matches(callback), thisArg);
|
|
11998
|
+
return this.models.filter(toModelPredicate(callback), thisArg);
|
|
11855
11999
|
}
|
|
11856
12000
|
every(pred) {
|
|
11857
12001
|
if (isFunction(pred)) {
|
|
11858
|
-
return this.models.
|
|
11859
|
-
}
|
|
11860
|
-
else {
|
|
11861
|
-
return this.models.every((m) => m.matches(pred));
|
|
12002
|
+
return this.models.every((m) => pred(m.attributes));
|
|
11862
12003
|
}
|
|
12004
|
+
return this.models.every(toModelPredicate(pred));
|
|
11863
12005
|
}
|
|
11864
12006
|
difference(values) {
|
|
11865
12007
|
return this.models.filter((m) => !values.includes(m));
|
|
@@ -11875,16 +12017,12 @@ class Collection extends EventEmitterObject {
|
|
|
11875
12017
|
}
|
|
11876
12018
|
some(pred) {
|
|
11877
12019
|
if (isFunction(pred)) {
|
|
11878
|
-
return this.models.
|
|
11879
|
-
}
|
|
11880
|
-
else {
|
|
11881
|
-
return this.models.some((m) => m.matches(pred));
|
|
12020
|
+
return this.models.some((m) => pred(m.attributes));
|
|
11882
12021
|
}
|
|
12022
|
+
return this.models.some(toModelPredicate(pred));
|
|
11883
12023
|
}
|
|
11884
12024
|
sortBy(iteratee) {
|
|
11885
|
-
return sortBy(this.models,
|
|
11886
|
-
? iteratee
|
|
11887
|
-
: (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)));
|
|
12025
|
+
return sortBy(this.models, toModelPredicate(iteratee));
|
|
11888
12026
|
}
|
|
11889
12027
|
isEmpty() {
|
|
11890
12028
|
return !this.models.length;
|
|
@@ -11905,26 +12043,22 @@ class Collection extends EventEmitterObject {
|
|
|
11905
12043
|
return this.models.length;
|
|
11906
12044
|
}
|
|
11907
12045
|
countBy(f) {
|
|
11908
|
-
return countBy(this.models,
|
|
12046
|
+
return countBy(this.models, toModelPredicate(f));
|
|
11909
12047
|
}
|
|
11910
12048
|
groupBy(pred) {
|
|
11911
|
-
return groupBy(this.models,
|
|
12049
|
+
return groupBy(this.models, toModelPredicate(pred));
|
|
11912
12050
|
}
|
|
11913
12051
|
indexOf(model, fromIndex) {
|
|
11914
12052
|
return this.models.indexOf(model, fromIndex);
|
|
11915
12053
|
}
|
|
11916
12054
|
findLastIndex(pred, fromIndex) {
|
|
11917
|
-
return this.models.findLastIndex(
|
|
11918
|
-
? pred
|
|
11919
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)), fromIndex);
|
|
12055
|
+
return this.models.findLastIndex(toModelPredicate(pred), fromIndex);
|
|
11920
12056
|
}
|
|
11921
12057
|
lastIndexOf(model, fromIndex) {
|
|
11922
12058
|
return this.models.lastIndexOf(model, fromIndex);
|
|
11923
12059
|
}
|
|
11924
12060
|
findIndex(pred) {
|
|
11925
|
-
return this.models.findIndex(
|
|
11926
|
-
? pred
|
|
11927
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
|
|
12061
|
+
return this.models.findIndex(toModelPredicate(pred));
|
|
11928
12062
|
}
|
|
11929
12063
|
last() {
|
|
11930
12064
|
const length = this.models == null ? 0 : this.models.length;
|
|
@@ -11937,9 +12071,7 @@ class Collection extends EventEmitterObject {
|
|
|
11937
12071
|
return this.head();
|
|
11938
12072
|
}
|
|
11939
12073
|
map(cb, thisArg) {
|
|
11940
|
-
return this.models.map(
|
|
11941
|
-
? cb
|
|
11942
|
-
: (m) => (isString(cb) ? m.get(cb) : m.matches(cb)), thisArg);
|
|
12074
|
+
return this.models.map(toModelPredicate(cb), thisArg);
|
|
11943
12075
|
}
|
|
11944
12076
|
reduce(callback, initialValue) {
|
|
11945
12077
|
return this.models.reduce(callback, initialValue || this.models[0]);
|
|
@@ -11990,10 +12122,7 @@ class Collection extends EventEmitterObject {
|
|
|
11990
12122
|
return this.where(attrs, true);
|
|
11991
12123
|
}
|
|
11992
12124
|
find(predicate, fromIndex) {
|
|
11993
|
-
|
|
11994
|
-
? predicate
|
|
11995
|
-
: (m) => m.matches(predicate);
|
|
11996
|
-
return this.models.find(pred, fromIndex);
|
|
12125
|
+
return this.models.find(toModelPredicate(predicate), fromIndex);
|
|
11997
12126
|
}
|
|
11998
12127
|
/**
|
|
11999
12128
|
* Force the collection to re-sort itself. You don't need to call this under
|
|
@@ -12036,46 +12165,22 @@ class Collection extends EventEmitterObject {
|
|
|
12036
12165
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
12037
12166
|
const collection = this;
|
|
12038
12167
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
12039
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
12040
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
12041
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
12042
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
12043
|
-
// it's reliable by the time those handlers run.
|
|
12044
|
-
let settled = false;
|
|
12045
12168
|
options.success = function (resp) {
|
|
12046
|
-
settled = true;
|
|
12047
12169
|
const method = options.reset ? 'reset' : 'set';
|
|
12048
12170
|
// fromStorage prevents individual model set() calls from triggering auto-save on reads
|
|
12049
12171
|
collection[method](resp, Object.assign({}, options, { fromStorage: true }));
|
|
12050
12172
|
if (success)
|
|
12051
12173
|
success.call(options.context, collection, resp, options);
|
|
12052
|
-
|
|
12174
|
+
// Fire 'sync' before resolving, as `Model.fetch`/`save` do, so a throwing
|
|
12175
|
+
// listener rejects the promise instead of being lost.
|
|
12053
12176
|
collection.trigger('sync', collection, resp, options);
|
|
12177
|
+
promise && promise.resolve();
|
|
12054
12178
|
};
|
|
12055
12179
|
wrapError(this, options, promise);
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
onError(resp);
|
|
12061
|
-
};
|
|
12062
|
-
}
|
|
12063
|
-
const result = this.sync('read', this, options);
|
|
12064
|
-
if (promise) {
|
|
12065
|
-
Promise.resolve(result).then(
|
|
12066
|
-
// A sync that resolves its promise without invoking options.success:
|
|
12067
|
-
// treat the resolved value as the response, so the collection is still
|
|
12068
|
-
// populated and the promise settles instead of hanging.
|
|
12069
|
-
(value) => {
|
|
12070
|
-
if (!settled)
|
|
12071
|
-
options.success(value);
|
|
12072
|
-
},
|
|
12073
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
12074
|
-
// twice is a harmless no-op.
|
|
12075
|
-
(e) => promise.reject(e));
|
|
12076
|
-
return promise;
|
|
12077
|
-
}
|
|
12078
|
-
return result;
|
|
12180
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
12181
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
12182
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
12183
|
+
return promise ?? result;
|
|
12079
12184
|
}
|
|
12080
12185
|
/**
|
|
12081
12186
|
* Create a new instance of a model in this collection. Add the model to the
|
|
@@ -12087,9 +12192,22 @@ class Collection extends EventEmitterObject {
|
|
|
12087
12192
|
const wait = options.wait;
|
|
12088
12193
|
const return_promise = options.promise;
|
|
12089
12194
|
const promise = return_promise && getResolveablePromise();
|
|
12195
|
+
// `_prepareModel` reports attributes that fail validation by firing
|
|
12196
|
+
// `'invalid'` and returning null. With `promise`, capture that error so a
|
|
12197
|
+
// rejected promise is handed back rather than `null`.
|
|
12198
|
+
let validationError;
|
|
12199
|
+
const captureInvalid = (_c, err) => (validationError = err);
|
|
12200
|
+
if (return_promise)
|
|
12201
|
+
this.on('invalid', captureInvalid);
|
|
12090
12202
|
const preparedModel = this._prepareModel(model, options);
|
|
12091
|
-
if (
|
|
12092
|
-
|
|
12203
|
+
if (return_promise)
|
|
12204
|
+
this.off('invalid', captureInvalid);
|
|
12205
|
+
if (!preparedModel) {
|
|
12206
|
+
if (!return_promise)
|
|
12207
|
+
return null;
|
|
12208
|
+
promise.reject(validationError);
|
|
12209
|
+
return promise;
|
|
12210
|
+
}
|
|
12093
12211
|
if (!wait)
|
|
12094
12212
|
this.add(preparedModel, options);
|
|
12095
12213
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
@@ -12111,8 +12229,15 @@ class Collection extends EventEmitterObject {
|
|
|
12111
12229
|
error && error.call(options.context, model, e, options);
|
|
12112
12230
|
return_promise && promise.reject(e);
|
|
12113
12231
|
};
|
|
12114
|
-
|
|
12232
|
+
// `options.promise` is passed through, so `save` installs its own bridge
|
|
12233
|
+
// from the sync's returned promise to its callbacks: a sync that resolves
|
|
12234
|
+
// without calling them still reaches the wired `options.success` above,
|
|
12235
|
+
// which resolves our promise with the model. Only a rejection that bypassed
|
|
12236
|
+
// the callbacks (a rejecting sync, or a throwing `'sync'`/`'error'`
|
|
12237
|
+
// listener) still needs bridging to ours.
|
|
12238
|
+
const result = preparedModel.save(null, options);
|
|
12115
12239
|
if (return_promise) {
|
|
12240
|
+
Promise.resolve(result).catch((e) => promise.reject(e));
|
|
12116
12241
|
return promise;
|
|
12117
12242
|
}
|
|
12118
12243
|
else {
|
|
@@ -12264,13 +12389,7 @@ class Collection extends EventEmitterObject {
|
|
|
12264
12389
|
this.trigger.apply(this, arguments);
|
|
12265
12390
|
}
|
|
12266
12391
|
}
|
|
12267
|
-
|
|
12268
|
-
ensureUnloadListener(PersistentStorage);
|
|
12269
|
-
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
12270
|
-
// missing model record resolves to null, so hydration resolves rather
|
|
12271
|
-
// than rejecting.
|
|
12272
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
12273
|
-
}, _a = Symbol.iterator;
|
|
12392
|
+
_a = Symbol.iterator;
|
|
12274
12393
|
// This "enum" defines the three possible kinds of values which can be emitted
|
|
12275
12394
|
// by a CollectionIterator that correspond to the values(), keys() and entries()
|
|
12276
12395
|
// methods on Collection, respectively.
|
|
@@ -12338,6 +12457,7 @@ const skeletor = {
|
|
|
12338
12457
|
EventEmitter,
|
|
12339
12458
|
Model,
|
|
12340
12459
|
sync,
|
|
12460
|
+
HTTPError,
|
|
12341
12461
|
};
|
|
12342
12462
|
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
|
|
12343
12463
|
// We use `self` instead of `window` for `WebWorker` support.
|
|
@@ -12365,5 +12485,5 @@ root.Skeletor = skeletor;
|
|
|
12365
12485
|
*/
|
|
12366
12486
|
const BrowserStorage = PersistentStorage;
|
|
12367
12487
|
|
|
12368
|
-
export { BrowserStorage, Collection, EventEmitter, Model, PersistentStorage, skeletor as default, getStorage, noConflict, sync };
|
|
12488
|
+
export { BrowserStorage, Collection, EventEmitter, HTTPError, Model, PersistentStorage, skeletor as default, getStorage, noConflict, sync };
|
|
12369
12489
|
//# sourceMappingURL=skeletor.esm.js.map
|