@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.js
CHANGED
|
@@ -1674,19 +1674,6 @@
|
|
|
1674
1674
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
1675
1675
|
}
|
|
1676
1676
|
|
|
1677
|
-
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
1678
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
1679
|
-
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");
|
|
1680
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
1684
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
1685
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
1686
|
-
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");
|
|
1687
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
1677
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
1691
1678
|
var e = new Error(message);
|
|
1692
1679
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
@@ -8477,6 +8464,16 @@
|
|
|
8477
8464
|
// Generate a pseudo-GUID by concatenating random hexadecimal.
|
|
8478
8465
|
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
|
|
8479
8466
|
}
|
|
8467
|
+
// Helpers
|
|
8468
|
+
// -------
|
|
8469
|
+
/**
|
|
8470
|
+
* Whether `value` is a thenable (a promise, or a promise-like object such as
|
|
8471
|
+
* a jqXHR). Decides whether a `sync` return value can be bridged to the
|
|
8472
|
+
* promise that `fetch`/`save` hand back.
|
|
8473
|
+
*/
|
|
8474
|
+
function isThenable(value) {
|
|
8475
|
+
return value != null && typeof value.then === 'function';
|
|
8476
|
+
}
|
|
8480
8477
|
function getResolveablePromise() {
|
|
8481
8478
|
const wrapper = {
|
|
8482
8479
|
isResolved: false,
|
|
@@ -8521,6 +8518,65 @@
|
|
|
8521
8518
|
promise && promise.reject && promise.reject(resp);
|
|
8522
8519
|
};
|
|
8523
8520
|
}
|
|
8521
|
+
/**
|
|
8522
|
+
* Run a `sync` and make sure `promise` settles from its outcome, however the
|
|
8523
|
+
* sync reports it.
|
|
8524
|
+
*
|
|
8525
|
+
* A `sync` may invoke `options.success`/`options.error` (the callback
|
|
8526
|
+
* contract), or settle the promise it returns without calling them: a
|
|
8527
|
+
* promise-only custom sync, or an async sync whose promise rejects because a
|
|
8528
|
+
* `'sync'`/`'error'` listener threw after the callbacks ran. `options.success`
|
|
8529
|
+
* and `options.error` must already be wired to resolve/reject `promise`. This
|
|
8530
|
+
* wraps them to record that they ran, calls `run()`, and bridges a thenable
|
|
8531
|
+
* result to them so the promise never hangs:
|
|
8532
|
+
*
|
|
8533
|
+
* - fulfilment with the callbacks unrun is delivered to `options.success`, so
|
|
8534
|
+
* the response is still merged into the model or collection;
|
|
8535
|
+
* - rejection with the callbacks unrun is delivered to `options.error`, so the
|
|
8536
|
+
* caller's `error` callback and the `'error'` event still fire;
|
|
8537
|
+
* - rejection after the callbacks ran rejects `promise` directly: a no-op if
|
|
8538
|
+
* they already settled it, and the only way to settle it if a listener threw
|
|
8539
|
+
* before they could;
|
|
8540
|
+
* - a throw from either delivery (a throwing `success`/`error` callback or
|
|
8541
|
+
* listener) rejects `promise` instead of surfacing as an unhandled rejection.
|
|
8542
|
+
*
|
|
8543
|
+
* A non-thenable result (a callback-only sync that returns nothing, or an
|
|
8544
|
+
* XHR) is left alone: such a sync settles `promise` through its callbacks, and
|
|
8545
|
+
* bridging it would fire `success` on the next microtask, before the sync is
|
|
8546
|
+
* done. Without a `promise`, `run()` is called as is.
|
|
8547
|
+
*/
|
|
8548
|
+
function bridgeSyncToPromise(options, promise, run) {
|
|
8549
|
+
if (!promise)
|
|
8550
|
+
return run();
|
|
8551
|
+
let settled = false;
|
|
8552
|
+
const success = options.success;
|
|
8553
|
+
const error = options.error;
|
|
8554
|
+
options.success = (...args) => {
|
|
8555
|
+
settled = true;
|
|
8556
|
+
return success && success(...args);
|
|
8557
|
+
};
|
|
8558
|
+
options.error = (...args) => {
|
|
8559
|
+
settled = true;
|
|
8560
|
+
return error && error(...args);
|
|
8561
|
+
};
|
|
8562
|
+
const result = run();
|
|
8563
|
+
if (isThenable(result)) {
|
|
8564
|
+
Promise.resolve(result)
|
|
8565
|
+
.then((value) => {
|
|
8566
|
+
if (!settled)
|
|
8567
|
+
options.success(value);
|
|
8568
|
+
}, (e) => {
|
|
8569
|
+
if (settled) {
|
|
8570
|
+
promise.reject(e);
|
|
8571
|
+
}
|
|
8572
|
+
else {
|
|
8573
|
+
options.error(e);
|
|
8574
|
+
}
|
|
8575
|
+
})
|
|
8576
|
+
.catch((e) => promise.reject(e));
|
|
8577
|
+
}
|
|
8578
|
+
return result;
|
|
8579
|
+
}
|
|
8524
8580
|
// Map from CRUD to HTTP for our default `sync` implementation.
|
|
8525
8581
|
const methodMap = {
|
|
8526
8582
|
create: 'POST',
|
|
@@ -8570,43 +8626,77 @@
|
|
|
8570
8626
|
return store ? store.sync() : sync;
|
|
8571
8627
|
}
|
|
8572
8628
|
/**
|
|
8573
|
-
*
|
|
8574
|
-
*
|
|
8575
|
-
*
|
|
8576
|
-
* to the model's `url()`.
|
|
8577
|
-
*
|
|
8578
|
-
* Some possible customizations could be:
|
|
8579
|
-
*
|
|
8580
|
-
* - Use `setTimeout` to batch rapid-fire updates into a single request.
|
|
8581
|
-
* - Persist models via WebSockets instead of Ajax.
|
|
8582
|
-
* - Persist models to browser storage
|
|
8629
|
+
* The error the default `sync` hands to `options.error`, and rejects with, for
|
|
8630
|
+
* a response outside the 2xx range.
|
|
8631
|
+
* @public
|
|
8583
8632
|
*/
|
|
8633
|
+
class HTTPError extends Error {
|
|
8634
|
+
constructor(response, method, url) {
|
|
8635
|
+
const statusText = response.statusText ? ` ${response.statusText}` : '';
|
|
8636
|
+
super(`${method} ${url} failed with HTTP ${response.status}${statusText}`);
|
|
8637
|
+
this.name = 'HTTPError';
|
|
8638
|
+
this.status = response.status;
|
|
8639
|
+
this.response = response;
|
|
8640
|
+
}
|
|
8641
|
+
}
|
|
8584
8642
|
/**
|
|
8643
|
+
* The default `sync`: a JSON request over `fetch()` to the model's `url()`.
|
|
8644
|
+
* Override it (or `Model.sync`/`Collection.sync`) to change how models are
|
|
8645
|
+
* persisted, for instance to batch rapid-fire updates with `setTimeout`, to
|
|
8646
|
+
* talk over WebSockets, or to write to browser storage.
|
|
8647
|
+
*
|
|
8648
|
+
* It honours both contracts a `sync` may use. On a 2xx response the JSON body
|
|
8649
|
+
* (`null` when the body is empty) is passed to `options.success` and the
|
|
8650
|
+
* returned promise resolves with it. On a non-2xx response, a network failure
|
|
8651
|
+
* or an unparsable body, `options.error` receives the error (an `HTTPError`
|
|
8652
|
+
* for the first) and the promise rejects with it.
|
|
8653
|
+
*
|
|
8654
|
+
* `options.data` is sent as query parameters on GET, which may not carry a
|
|
8655
|
+
* body, and as a JSON body otherwise. For create, update and patch the body
|
|
8656
|
+
* defaults to `options.attrs` or the model's `toJSON()`.
|
|
8585
8657
|
* @public
|
|
8586
8658
|
*/
|
|
8587
8659
|
function sync(method, model, options = {}) {
|
|
8660
|
+
const type = methodMap[method];
|
|
8588
8661
|
let data = options.data;
|
|
8589
8662
|
// Ensure that we have the appropriate request data.
|
|
8590
8663
|
if (data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
|
8591
8664
|
data = options.attrs || model.toJSON();
|
|
8592
8665
|
}
|
|
8593
|
-
|
|
8594
|
-
const params = {
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
const url = options.url || result(model, 'url') || urlError();
|
|
8604
|
-
const xhr = fetch(url, params);
|
|
8605
|
-
if (options) {
|
|
8606
|
-
options.xhr = xhr;
|
|
8666
|
+
let url = options.url || result(model, 'url') || urlError();
|
|
8667
|
+
const params = { method: type };
|
|
8668
|
+
if (data != null) {
|
|
8669
|
+
if (type === 'GET') {
|
|
8670
|
+
url += (url.includes('?') ? '&' : '?') + new URLSearchParams(data).toString();
|
|
8671
|
+
}
|
|
8672
|
+
else {
|
|
8673
|
+
params.body = JSON.stringify(data);
|
|
8674
|
+
params.headers = { 'Content-Type': 'application/json' };
|
|
8675
|
+
}
|
|
8607
8676
|
}
|
|
8677
|
+
const xhr = fetch(url, params);
|
|
8678
|
+
options.xhr = xhr;
|
|
8608
8679
|
model.trigger('request', model, xhr, { ...params, xhr });
|
|
8609
|
-
return xhr
|
|
8680
|
+
return Promise.resolve(xhr)
|
|
8681
|
+
.then(async (response) => {
|
|
8682
|
+
if (!response.ok)
|
|
8683
|
+
throw new HTTPError(response, type, url);
|
|
8684
|
+
const text = await response.text();
|
|
8685
|
+
return text ? JSON.parse(text) : null;
|
|
8686
|
+
})
|
|
8687
|
+
.then(
|
|
8688
|
+
// The callbacks run before the returned promise settles, so a caller that
|
|
8689
|
+
// bridges this promise (`Model.fetch`/`save`) sees the outcome as already
|
|
8690
|
+
// reported. A throw from `success` (a `'sync'` listener, say) is not
|
|
8691
|
+
// routed to `error`: it rejects the promise, which is what the bridge
|
|
8692
|
+
// expects once the callbacks have run.
|
|
8693
|
+
(resp) => {
|
|
8694
|
+
options.success?.(resp, options);
|
|
8695
|
+
return resp;
|
|
8696
|
+
}, (e) => {
|
|
8697
|
+
options.error?.(e);
|
|
8698
|
+
throw e;
|
|
8699
|
+
});
|
|
8610
8700
|
}
|
|
8611
8701
|
|
|
8612
8702
|
/**
|
|
@@ -9972,6 +10062,48 @@
|
|
|
9972
10062
|
baseAssignValue(result, key, value);
|
|
9973
10063
|
});
|
|
9974
10064
|
|
|
10065
|
+
/** Used to compose bitmasks for cloning. */
|
|
10066
|
+
var CLONE_DEEP_FLAG$1 = 1;
|
|
10067
|
+
|
|
10068
|
+
/**
|
|
10069
|
+
* Creates a function that performs a partial deep comparison between a given
|
|
10070
|
+
* object and `source`, returning `true` if the given object has equivalent
|
|
10071
|
+
* property values, else `false`.
|
|
10072
|
+
*
|
|
10073
|
+
* **Note:** The created function is equivalent to `_.isMatch` with `source`
|
|
10074
|
+
* partially applied.
|
|
10075
|
+
*
|
|
10076
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10077
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10078
|
+
* for a list of supported value comparisons.
|
|
10079
|
+
*
|
|
10080
|
+
* **Note:** Multiple values can be checked by combining several matchers
|
|
10081
|
+
* using `_.overSome`
|
|
10082
|
+
*
|
|
10083
|
+
* @static
|
|
10084
|
+
* @memberOf _
|
|
10085
|
+
* @since 3.0.0
|
|
10086
|
+
* @category Util
|
|
10087
|
+
* @param {Object} source The object of property values to match.
|
|
10088
|
+
* @returns {Function} Returns the new spec function.
|
|
10089
|
+
* @example
|
|
10090
|
+
*
|
|
10091
|
+
* var objects = [
|
|
10092
|
+
* { 'a': 1, 'b': 2, 'c': 3 },
|
|
10093
|
+
* { 'a': 4, 'b': 5, 'c': 6 }
|
|
10094
|
+
* ];
|
|
10095
|
+
*
|
|
10096
|
+
* _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
|
|
10097
|
+
* // => [{ 'a': 4, 'b': 5, 'c': 6 }]
|
|
10098
|
+
*
|
|
10099
|
+
* // Checking for several possible values
|
|
10100
|
+
* _.filter(objects, _.overSome([_.matches({ 'a': 1 }), _.matches({ 'a': 4 })]));
|
|
10101
|
+
* // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }]
|
|
10102
|
+
*/
|
|
10103
|
+
function matches(source) {
|
|
10104
|
+
return baseMatches(baseClone(source, CLONE_DEEP_FLAG$1));
|
|
10105
|
+
}
|
|
10106
|
+
|
|
9975
10107
|
/** Built-in value references. */
|
|
9976
10108
|
var spreadableSymbol = Symbol$1 ? Symbol$1.isConcatSpreadable : undefined;
|
|
9977
10109
|
|
|
@@ -10535,53 +10667,36 @@
|
|
|
10535
10667
|
return baseIsEqual(value, other);
|
|
10536
10668
|
}
|
|
10537
10669
|
|
|
10538
|
-
/** Used to compose bitmasks for cloning. */
|
|
10539
|
-
var CLONE_DEEP_FLAG$1 = 1;
|
|
10540
|
-
|
|
10541
10670
|
/**
|
|
10542
|
-
*
|
|
10543
|
-
*
|
|
10544
|
-
*
|
|
10545
|
-
*
|
|
10546
|
-
*
|
|
10671
|
+
* Performs a partial deep comparison between `object` and `source` to
|
|
10672
|
+
* determine if `object` contains equivalent property values.
|
|
10673
|
+
*
|
|
10674
|
+
* **Note:** This method is equivalent to `_.matches` when `source` is
|
|
10675
|
+
* partially applied.
|
|
10676
|
+
*
|
|
10677
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10678
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10679
|
+
* for a list of supported value comparisons.
|
|
10547
10680
|
*
|
|
10548
10681
|
* @static
|
|
10549
|
-
* @since 4.0.0
|
|
10550
10682
|
* @memberOf _
|
|
10551
|
-
* @
|
|
10552
|
-
* @
|
|
10553
|
-
* @
|
|
10683
|
+
* @since 3.0.0
|
|
10684
|
+
* @category Lang
|
|
10685
|
+
* @param {Object} object The object to inspect.
|
|
10686
|
+
* @param {Object} source The object of property values to match.
|
|
10687
|
+
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
|
|
10554
10688
|
* @example
|
|
10555
10689
|
*
|
|
10556
|
-
* var
|
|
10557
|
-
* { 'user': 'barney', 'age': 36, 'active': true },
|
|
10558
|
-
* { 'user': 'fred', 'age': 40, 'active': false }
|
|
10559
|
-
* ];
|
|
10560
|
-
*
|
|
10561
|
-
* // The `_.matches` iteratee shorthand.
|
|
10562
|
-
* _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
|
|
10563
|
-
* // => [{ 'user': 'barney', 'age': 36, 'active': true }]
|
|
10564
|
-
*
|
|
10565
|
-
* // The `_.matchesProperty` iteratee shorthand.
|
|
10566
|
-
* _.filter(users, _.iteratee(['user', 'fred']));
|
|
10567
|
-
* // => [{ 'user': 'fred', 'age': 40 }]
|
|
10690
|
+
* var object = { 'a': 1, 'b': 2 };
|
|
10568
10691
|
*
|
|
10569
|
-
*
|
|
10570
|
-
*
|
|
10571
|
-
* // => ['barney', 'fred']
|
|
10572
|
-
*
|
|
10573
|
-
* // Create custom iteratee shorthands.
|
|
10574
|
-
* _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
|
|
10575
|
-
* return !_.isRegExp(func) ? iteratee(func) : function(string) {
|
|
10576
|
-
* return func.test(string);
|
|
10577
|
-
* };
|
|
10578
|
-
* });
|
|
10692
|
+
* _.isMatch(object, { 'b': 2 });
|
|
10693
|
+
* // => true
|
|
10579
10694
|
*
|
|
10580
|
-
* _.
|
|
10581
|
-
* // =>
|
|
10695
|
+
* _.isMatch(object, { 'b': 1 });
|
|
10696
|
+
* // => false
|
|
10582
10697
|
*/
|
|
10583
|
-
function
|
|
10584
|
-
return
|
|
10698
|
+
function isMatch(object, source) {
|
|
10699
|
+
return object === source || baseIsMatch(object, source, getMatchData(source));
|
|
10585
10700
|
}
|
|
10586
10701
|
|
|
10587
10702
|
/**
|
|
@@ -10886,7 +11001,47 @@
|
|
|
10886
11001
|
return object == null ? {} : basePick(object, paths);
|
|
10887
11002
|
});
|
|
10888
11003
|
|
|
10889
|
-
|
|
11004
|
+
const INTERNALS = new WeakMap();
|
|
11005
|
+
function internals(model) {
|
|
11006
|
+
let priv = INTERNALS.get(model);
|
|
11007
|
+
if (!priv) {
|
|
11008
|
+
priv = { state: 'constructing' };
|
|
11009
|
+
INTERNALS.set(model, priv);
|
|
11010
|
+
}
|
|
11011
|
+
return priv;
|
|
11012
|
+
}
|
|
11013
|
+
/** Load the model from storage, marking it ready whether or not the read succeeds. */
|
|
11014
|
+
async function hydrate$1(model, priv) {
|
|
11015
|
+
ensureUnloadListener(PersistentStorage);
|
|
11016
|
+
try {
|
|
11017
|
+
await model.fetch({ fromStorage: true, promise: true });
|
|
11018
|
+
}
|
|
11019
|
+
finally {
|
|
11020
|
+
priv.state = 'ready';
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
/** Evaluate the model's declared computed properties once, seeding the cache. */
|
|
11024
|
+
function initComputed(model, priv) {
|
|
11025
|
+
const defs = result(model, 'computed');
|
|
11026
|
+
if (!defs || isEmpty(defs))
|
|
11027
|
+
return;
|
|
11028
|
+
priv.computedDefs = defs;
|
|
11029
|
+
priv.computedCache = {};
|
|
11030
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
11031
|
+
priv.computedCache[key] = def.fn(model);
|
|
11032
|
+
}
|
|
11033
|
+
}
|
|
11034
|
+
/** Recompute one computed property, firing `change:key` when its value changed. */
|
|
11035
|
+
function updateComputed(model, priv, key, def, silent, options) {
|
|
11036
|
+
const newVal = def.fn(model);
|
|
11037
|
+
if (!isEqual(priv.computedCache[key], newVal)) {
|
|
11038
|
+
priv.computedCache[key] = newVal;
|
|
11039
|
+
if (!silent) {
|
|
11040
|
+
model.changed[key] = newVal;
|
|
11041
|
+
model.trigger('change:' + key, model, newVal, options);
|
|
11042
|
+
}
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
10890
11045
|
/**
|
|
10891
11046
|
* @public
|
|
10892
11047
|
* **Models** are the basic data object in the framework --
|
|
@@ -10901,15 +11056,12 @@
|
|
|
10901
11056
|
*/
|
|
10902
11057
|
constructor(attributes, options) {
|
|
10903
11058
|
super();
|
|
10904
|
-
_Model_instances.add(this);
|
|
10905
|
-
_Model_computedCache.set(this, void 0);
|
|
10906
|
-
_Model_computedDefs.set(this, void 0);
|
|
10907
|
-
_Model_state.set(this, 'constructing');
|
|
10908
11059
|
this._changing = false;
|
|
10909
11060
|
this._pending = false;
|
|
10910
11061
|
this._url = '';
|
|
10911
11062
|
this.changed = {};
|
|
10912
11063
|
this.validationError = null;
|
|
11064
|
+
const priv = internals(this);
|
|
10913
11065
|
let attrs = attributes || {};
|
|
10914
11066
|
options = options || {};
|
|
10915
11067
|
this.preinitialize.apply(this, arguments);
|
|
@@ -10922,11 +11074,12 @@
|
|
|
10922
11074
|
const default_attrs = result(this, 'defaults');
|
|
10923
11075
|
attrs = defaults(Object.assign({}, default_attrs, attrs), default_attrs);
|
|
10924
11076
|
this.set(attrs, options);
|
|
10925
|
-
|
|
11077
|
+
initComputed(this, priv);
|
|
10926
11078
|
this.attrs = new Proxy(this.attributes, {
|
|
10927
11079
|
get: (_target, key) => {
|
|
10928
|
-
|
|
10929
|
-
|
|
11080
|
+
const cache = priv.computedCache;
|
|
11081
|
+
if (typeof key !== 'symbol' && cache && key in cache) {
|
|
11082
|
+
return cache[key];
|
|
10930
11083
|
}
|
|
10931
11084
|
return this.attributes[key];
|
|
10932
11085
|
},
|
|
@@ -10944,16 +11097,16 @@
|
|
|
10944
11097
|
// otherwise it's already ready and resolves immediately.
|
|
10945
11098
|
if (this.autoSync) {
|
|
10946
11099
|
if (getStorage(this) && !this.isNew()) {
|
|
10947
|
-
|
|
10948
|
-
this.hydrated =
|
|
11100
|
+
priv.state = 'hydrating';
|
|
11101
|
+
this.hydrated = hydrate$1(this, priv);
|
|
10949
11102
|
}
|
|
10950
11103
|
else {
|
|
10951
|
-
|
|
11104
|
+
priv.state = 'ready';
|
|
10952
11105
|
this.hydrated = Promise.resolve();
|
|
10953
11106
|
}
|
|
10954
11107
|
}
|
|
10955
11108
|
else {
|
|
10956
|
-
|
|
11109
|
+
priv.state = 'ready';
|
|
10957
11110
|
}
|
|
10958
11111
|
}
|
|
10959
11112
|
/**
|
|
@@ -11052,8 +11205,9 @@
|
|
|
11052
11205
|
* Get the value of an attribute, or a computed property.
|
|
11053
11206
|
*/
|
|
11054
11207
|
get(attr) {
|
|
11055
|
-
|
|
11056
|
-
|
|
11208
|
+
const cache = internals(this).computedCache;
|
|
11209
|
+
if (cache && attr in cache) {
|
|
11210
|
+
return cache[attr];
|
|
11057
11211
|
}
|
|
11058
11212
|
return this.attributes[attr];
|
|
11059
11213
|
}
|
|
@@ -11095,10 +11249,20 @@
|
|
|
11095
11249
|
return this.get(attr) != null;
|
|
11096
11250
|
}
|
|
11097
11251
|
/**
|
|
11098
|
-
*
|
|
11252
|
+
* Returns `true` if the model's attributes satisfy `attrs`.
|
|
11253
|
+
*
|
|
11254
|
+
* An attributes object is compared with lodash's `isMatch`: a partial, deep
|
|
11255
|
+
* comparison, so `{ form: { type: 'hidden' } }` matches a `form` attribute
|
|
11256
|
+
* that has `type: 'hidden'` alongside other keys. A function is called with
|
|
11257
|
+
* the attributes and its result coerced to a boolean.
|
|
11258
|
+
*
|
|
11259
|
+
* To scan a collection, prefer `Collection#find`, `filter` or `where`. They
|
|
11260
|
+
* build their matcher once per scan instead of once per model.
|
|
11099
11261
|
*/
|
|
11100
11262
|
matches(attrs) {
|
|
11101
|
-
|
|
11263
|
+
if (typeof attrs === 'function')
|
|
11264
|
+
return !!attrs(this.attributes);
|
|
11265
|
+
return isMatch(this.attributes, attrs);
|
|
11102
11266
|
}
|
|
11103
11267
|
/**
|
|
11104
11268
|
* Set a hash of model attributes on the object, firing `"change"`. This is
|
|
@@ -11119,9 +11283,10 @@
|
|
|
11119
11283
|
attrs[key] = val;
|
|
11120
11284
|
}
|
|
11121
11285
|
options = options || {};
|
|
11286
|
+
const priv = internals(this);
|
|
11122
11287
|
// Guard must come after arg normalization: when called as set(obj, opts),
|
|
11123
11288
|
// fromStorage lives in `val` (second arg), not `options` (third arg).
|
|
11124
|
-
if (this.autoSync &&
|
|
11289
|
+
if (this.autoSync && priv.state === 'hydrating' && !options.fromStorage) {
|
|
11125
11290
|
throw new Error(`Skeletor: set() called on an autoSync model before hydration resolved. Await model.hydrated first.`);
|
|
11126
11291
|
}
|
|
11127
11292
|
// Run validation.
|
|
@@ -11165,10 +11330,10 @@
|
|
|
11165
11330
|
}
|
|
11166
11331
|
}
|
|
11167
11332
|
// Recalculate computed properties whose deps changed.
|
|
11168
|
-
if (changes.length &&
|
|
11169
|
-
for (const [key, def] of Object.entries(
|
|
11333
|
+
if (changes.length && priv.computedDefs) {
|
|
11334
|
+
for (const [key, def] of Object.entries(priv.computedDefs)) {
|
|
11170
11335
|
if (def.deps.some((dep) => changes.includes(dep))) {
|
|
11171
|
-
|
|
11336
|
+
updateComputed(this, priv, key, def, silent, options);
|
|
11172
11337
|
}
|
|
11173
11338
|
}
|
|
11174
11339
|
}
|
|
@@ -11188,13 +11353,13 @@
|
|
|
11188
11353
|
// Auto-save: schedule a debounced write when autoSync is on and there
|
|
11189
11354
|
// were actual changes that originated from application code (not a
|
|
11190
11355
|
// storage read or an explicit noAutoSave call).
|
|
11191
|
-
if (
|
|
11356
|
+
if (priv.state === 'ready' &&
|
|
11192
11357
|
this.autoSync &&
|
|
11193
11358
|
getStorage(this) &&
|
|
11194
11359
|
changes.length &&
|
|
11195
11360
|
!options.fromStorage &&
|
|
11196
11361
|
!options.noAutoSave) {
|
|
11197
|
-
scheduleAutoSave(this, () =>
|
|
11362
|
+
scheduleAutoSave(this, () => this.save(null, { noAutoSave: true }), this.autoSyncDelay);
|
|
11198
11363
|
}
|
|
11199
11364
|
return this;
|
|
11200
11365
|
}
|
|
@@ -11271,47 +11436,25 @@
|
|
|
11271
11436
|
options = Object.assign({ parse: true }, options);
|
|
11272
11437
|
const success = options.success;
|
|
11273
11438
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
11274
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
11275
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
11276
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
11277
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
11278
|
-
// it's reliable by the time those handlers run.
|
|
11279
|
-
let settled = false;
|
|
11280
11439
|
options.success = (resp) => {
|
|
11281
|
-
settled = true;
|
|
11282
11440
|
const serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11283
11441
|
// fromStorage prevents the set() from triggering auto-save on reads
|
|
11284
|
-
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true })))
|
|
11442
|
+
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true }))) {
|
|
11443
|
+
// The fetched attributes failed validation (`'invalid'` has fired).
|
|
11444
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11445
|
+
promise && promise.reject(this.validationError);
|
|
11285
11446
|
return false;
|
|
11447
|
+
}
|
|
11286
11448
|
if (success)
|
|
11287
11449
|
success.call(options.context, this, resp, options);
|
|
11288
11450
|
this.trigger('sync', this, resp, options);
|
|
11289
11451
|
promise && promise.resolve(resp);
|
|
11290
11452
|
};
|
|
11291
11453
|
wrapError(this, options, promise);
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11296
|
-
onError(resp);
|
|
11297
|
-
};
|
|
11298
|
-
}
|
|
11299
|
-
const result = this.sync('read', this, options);
|
|
11300
|
-
if (promise) {
|
|
11301
|
-
Promise.resolve(result).then(
|
|
11302
|
-
// A sync that resolves its promise without invoking options.success:
|
|
11303
|
-
// treat the resolved value as the response, so the model is still
|
|
11304
|
-
// merged and the promise settles instead of hanging.
|
|
11305
|
-
(value) => {
|
|
11306
|
-
if (!settled)
|
|
11307
|
-
options.success(value);
|
|
11308
|
-
},
|
|
11309
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
11310
|
-
// twice is a harmless no-op.
|
|
11311
|
-
(e) => promise.reject(e));
|
|
11312
|
-
return promise;
|
|
11313
|
-
}
|
|
11314
|
-
return result;
|
|
11454
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11455
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11456
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
11457
|
+
return promise ?? result;
|
|
11315
11458
|
}
|
|
11316
11459
|
/**
|
|
11317
11460
|
* Set a hash of model attributes, and sync the model to the server.
|
|
@@ -11332,16 +11475,19 @@
|
|
|
11332
11475
|
options = Object.assign({ validate: true, parse: true }, options);
|
|
11333
11476
|
const wait = options.wait;
|
|
11334
11477
|
const return_promise = options.promise;
|
|
11335
|
-
const promise = return_promise
|
|
11478
|
+
const promise = return_promise ? getResolveablePromise() : undefined;
|
|
11336
11479
|
// If we're not waiting and attributes exist, save acts as
|
|
11337
11480
|
// `set(attr).save(null, opts)` with validation. Otherwise, check if
|
|
11338
11481
|
// the model will be valid when the attributes, if any, are set.
|
|
11339
|
-
|
|
11340
|
-
|
|
11482
|
+
const valid = attrs && !wait ? !!this.set(attrs, options) : this._validate(attrs || {}, options);
|
|
11483
|
+
if (!valid) {
|
|
11484
|
+
// Nothing is synced (`'invalid'` has fired). With `promise`, hand back a
|
|
11485
|
+
// rejected promise rather than `false`, so the promise contract holds
|
|
11486
|
+
// for every outcome.
|
|
11487
|
+
if (!promise)
|
|
11341
11488
|
return false;
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
return false;
|
|
11489
|
+
promise.reject(this.validationError);
|
|
11490
|
+
return promise;
|
|
11345
11491
|
}
|
|
11346
11492
|
// After a successful server-side save, the client is (optionally)
|
|
11347
11493
|
// updated with the server-side state.
|
|
@@ -11354,8 +11500,12 @@
|
|
|
11354
11500
|
let serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11355
11501
|
if (wait)
|
|
11356
11502
|
serverAttrs = Object.assign({}, attrs, serverAttrs);
|
|
11357
|
-
if (serverAttrs && !this.set(serverAttrs, options))
|
|
11503
|
+
if (serverAttrs && !this.set(serverAttrs, options)) {
|
|
11504
|
+
// The server attributes failed validation (`'invalid'` has fired).
|
|
11505
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11506
|
+
return_promise && promise.reject(this.validationError);
|
|
11358
11507
|
return false;
|
|
11508
|
+
}
|
|
11359
11509
|
if (success)
|
|
11360
11510
|
success.call(options.context, this, resp, options);
|
|
11361
11511
|
this.trigger('sync', this, resp, options);
|
|
@@ -11372,15 +11522,12 @@
|
|
|
11372
11522
|
const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
|
11373
11523
|
if (method === 'patch' && !options.attrs)
|
|
11374
11524
|
options.attrs = attrs;
|
|
11375
|
-
|
|
11525
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11526
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11527
|
+
const xhr = bridgeSyncToPromise(options, promise, () => this.sync(method, this, options));
|
|
11376
11528
|
// Restore attributes.
|
|
11377
11529
|
this.attributes = attributes;
|
|
11378
|
-
|
|
11379
|
-
return promise;
|
|
11380
|
-
}
|
|
11381
|
-
else {
|
|
11382
|
-
return xhr;
|
|
11383
|
-
}
|
|
11530
|
+
return promise ?? xhr;
|
|
11384
11531
|
}
|
|
11385
11532
|
/**
|
|
11386
11533
|
* Destroy this model on the server if it was already persisted.
|
|
@@ -11493,40 +11640,40 @@
|
|
|
11493
11640
|
return false;
|
|
11494
11641
|
}
|
|
11495
11642
|
}
|
|
11496
|
-
_Model_computedCache = new WeakMap(), _Model_computedDefs = new WeakMap(), _Model_state = new WeakMap(), _Model_instances = new WeakSet(), _Model_hydrate = async function _Model_hydrate() {
|
|
11497
|
-
ensureUnloadListener(PersistentStorage);
|
|
11498
|
-
try {
|
|
11499
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
11500
|
-
}
|
|
11501
|
-
finally {
|
|
11502
|
-
__classPrivateFieldSet(this, _Model_state, 'ready', "f");
|
|
11503
|
-
}
|
|
11504
|
-
}, _Model_doAutoSave = function _Model_doAutoSave() {
|
|
11505
|
-
return this.save(null, { noAutoSave: true });
|
|
11506
|
-
}, _Model_updateComputed = function _Model_updateComputed(key, def, silent, options) {
|
|
11507
|
-
const newVal = def.fn(this);
|
|
11508
|
-
if (!isEqual(__classPrivateFieldGet(this, _Model_computedCache, "f")[key], newVal)) {
|
|
11509
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = newVal;
|
|
11510
|
-
if (!silent) {
|
|
11511
|
-
this.changed[key] = newVal;
|
|
11512
|
-
this.trigger('change:' + key, this, newVal, options);
|
|
11513
|
-
}
|
|
11514
|
-
}
|
|
11515
|
-
}, _Model_initComputed = function _Model_initComputed() {
|
|
11516
|
-
const defs = result(this, 'computed');
|
|
11517
|
-
if (!defs || isEmpty(defs))
|
|
11518
|
-
return;
|
|
11519
|
-
__classPrivateFieldSet(this, _Model_computedDefs, defs, "f");
|
|
11520
|
-
__classPrivateFieldSet(this, _Model_computedCache, {}, "f");
|
|
11521
|
-
for (const [key, def] of Object.entries(defs)) {
|
|
11522
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = def.fn(this);
|
|
11523
|
-
}
|
|
11524
|
-
};
|
|
11525
11643
|
|
|
11526
|
-
var
|
|
11644
|
+
var _a;
|
|
11527
11645
|
// Default options for `Collection#set`.
|
|
11528
11646
|
const setOptions = { add: true, remove: true, merge: true };
|
|
11529
11647
|
const addOptions = { add: true, remove: false };
|
|
11648
|
+
/**
|
|
11649
|
+
* Normalizes the predicate forms accepted by the query methods into one
|
|
11650
|
+
* `(model) => value` function. A function is used as-is, a string reads that
|
|
11651
|
+
* attribute, and an attributes object becomes a lodash `matches` matcher.
|
|
11652
|
+
*
|
|
11653
|
+
* The matcher is built here, once per scan. Calling `Model#matches` per model
|
|
11654
|
+
* instead rebuilds the match data for every model visited, which made
|
|
11655
|
+
* `findWhere` an order of magnitude slower than a hand-written predicate.
|
|
11656
|
+
*/
|
|
11657
|
+
function toModelPredicate(pred) {
|
|
11658
|
+
if (isFunction(pred))
|
|
11659
|
+
return pred;
|
|
11660
|
+
if (isString(pred))
|
|
11661
|
+
return (m) => m.get(pred);
|
|
11662
|
+
const matcher = matches(pred);
|
|
11663
|
+
return (m) => matcher(m.attributes);
|
|
11664
|
+
}
|
|
11665
|
+
/**
|
|
11666
|
+
* Load the collection's members from storage. A module-level function rather
|
|
11667
|
+
* than a `#private` method so the published declarations carry no private
|
|
11668
|
+
* members, which TypeScript 6 rejects in anonymously extended classes (TS4094).
|
|
11669
|
+
*/
|
|
11670
|
+
async function hydrate(collection) {
|
|
11671
|
+
ensureUnloadListener(PersistentStorage);
|
|
11672
|
+
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
11673
|
+
// missing model record resolves to null, so hydration resolves rather
|
|
11674
|
+
// than rejecting.
|
|
11675
|
+
await collection.fetch({ fromStorage: true, promise: true });
|
|
11676
|
+
}
|
|
11530
11677
|
/**
|
|
11531
11678
|
* @public
|
|
11532
11679
|
* If models tend to represent a single row of data, a Collection is
|
|
@@ -11544,7 +11691,6 @@
|
|
|
11544
11691
|
*/
|
|
11545
11692
|
constructor(models, options) {
|
|
11546
11693
|
super();
|
|
11547
|
-
_Collection_instances.add(this);
|
|
11548
11694
|
this._url = '';
|
|
11549
11695
|
/**
|
|
11550
11696
|
* @public
|
|
@@ -11566,7 +11712,7 @@
|
|
|
11566
11712
|
// uniformly `await collection.hydrated`; it resolves immediately when
|
|
11567
11713
|
// there's no storage to hydrate from.
|
|
11568
11714
|
if (this.autoSync) {
|
|
11569
|
-
this.hydrated = getStorage(this) ?
|
|
11715
|
+
this.hydrated = getStorage(this) ? hydrate(this) : Promise.resolve();
|
|
11570
11716
|
}
|
|
11571
11717
|
}
|
|
11572
11718
|
get comparator() {
|
|
@@ -11855,17 +12001,13 @@
|
|
|
11855
12001
|
return this.models.slice(start, end);
|
|
11856
12002
|
}
|
|
11857
12003
|
filter(callback, thisArg) {
|
|
11858
|
-
return this.models.filter(
|
|
11859
|
-
? callback
|
|
11860
|
-
: (m) => m.matches(callback), thisArg);
|
|
12004
|
+
return this.models.filter(toModelPredicate(callback), thisArg);
|
|
11861
12005
|
}
|
|
11862
12006
|
every(pred) {
|
|
11863
12007
|
if (isFunction(pred)) {
|
|
11864
|
-
return this.models.
|
|
11865
|
-
}
|
|
11866
|
-
else {
|
|
11867
|
-
return this.models.every((m) => m.matches(pred));
|
|
12008
|
+
return this.models.every((m) => pred(m.attributes));
|
|
11868
12009
|
}
|
|
12010
|
+
return this.models.every(toModelPredicate(pred));
|
|
11869
12011
|
}
|
|
11870
12012
|
difference(values) {
|
|
11871
12013
|
return this.models.filter((m) => !values.includes(m));
|
|
@@ -11881,16 +12023,12 @@
|
|
|
11881
12023
|
}
|
|
11882
12024
|
some(pred) {
|
|
11883
12025
|
if (isFunction(pred)) {
|
|
11884
|
-
return this.models.
|
|
11885
|
-
}
|
|
11886
|
-
else {
|
|
11887
|
-
return this.models.some((m) => m.matches(pred));
|
|
12026
|
+
return this.models.some((m) => pred(m.attributes));
|
|
11888
12027
|
}
|
|
12028
|
+
return this.models.some(toModelPredicate(pred));
|
|
11889
12029
|
}
|
|
11890
12030
|
sortBy(iteratee) {
|
|
11891
|
-
return sortBy(this.models,
|
|
11892
|
-
? iteratee
|
|
11893
|
-
: (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)));
|
|
12031
|
+
return sortBy(this.models, toModelPredicate(iteratee));
|
|
11894
12032
|
}
|
|
11895
12033
|
isEmpty() {
|
|
11896
12034
|
return !this.models.length;
|
|
@@ -11911,26 +12049,22 @@
|
|
|
11911
12049
|
return this.models.length;
|
|
11912
12050
|
}
|
|
11913
12051
|
countBy(f) {
|
|
11914
|
-
return countBy(this.models,
|
|
12052
|
+
return countBy(this.models, toModelPredicate(f));
|
|
11915
12053
|
}
|
|
11916
12054
|
groupBy(pred) {
|
|
11917
|
-
return groupBy(this.models,
|
|
12055
|
+
return groupBy(this.models, toModelPredicate(pred));
|
|
11918
12056
|
}
|
|
11919
12057
|
indexOf(model, fromIndex) {
|
|
11920
12058
|
return this.models.indexOf(model, fromIndex);
|
|
11921
12059
|
}
|
|
11922
12060
|
findLastIndex(pred, fromIndex) {
|
|
11923
|
-
return this.models.findLastIndex(
|
|
11924
|
-
? pred
|
|
11925
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)), fromIndex);
|
|
12061
|
+
return this.models.findLastIndex(toModelPredicate(pred), fromIndex);
|
|
11926
12062
|
}
|
|
11927
12063
|
lastIndexOf(model, fromIndex) {
|
|
11928
12064
|
return this.models.lastIndexOf(model, fromIndex);
|
|
11929
12065
|
}
|
|
11930
12066
|
findIndex(pred) {
|
|
11931
|
-
return this.models.findIndex(
|
|
11932
|
-
? pred
|
|
11933
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
|
|
12067
|
+
return this.models.findIndex(toModelPredicate(pred));
|
|
11934
12068
|
}
|
|
11935
12069
|
last() {
|
|
11936
12070
|
const length = this.models == null ? 0 : this.models.length;
|
|
@@ -11943,9 +12077,7 @@
|
|
|
11943
12077
|
return this.head();
|
|
11944
12078
|
}
|
|
11945
12079
|
map(cb, thisArg) {
|
|
11946
|
-
return this.models.map(
|
|
11947
|
-
? cb
|
|
11948
|
-
: (m) => (isString(cb) ? m.get(cb) : m.matches(cb)), thisArg);
|
|
12080
|
+
return this.models.map(toModelPredicate(cb), thisArg);
|
|
11949
12081
|
}
|
|
11950
12082
|
reduce(callback, initialValue) {
|
|
11951
12083
|
return this.models.reduce(callback, initialValue || this.models[0]);
|
|
@@ -11996,10 +12128,7 @@
|
|
|
11996
12128
|
return this.where(attrs, true);
|
|
11997
12129
|
}
|
|
11998
12130
|
find(predicate, fromIndex) {
|
|
11999
|
-
|
|
12000
|
-
? predicate
|
|
12001
|
-
: (m) => m.matches(predicate);
|
|
12002
|
-
return this.models.find(pred, fromIndex);
|
|
12131
|
+
return this.models.find(toModelPredicate(predicate), fromIndex);
|
|
12003
12132
|
}
|
|
12004
12133
|
/**
|
|
12005
12134
|
* Force the collection to re-sort itself. You don't need to call this under
|
|
@@ -12042,46 +12171,22 @@
|
|
|
12042
12171
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
12043
12172
|
const collection = this;
|
|
12044
12173
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
12045
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
12046
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
12047
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
12048
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
12049
|
-
// it's reliable by the time those handlers run.
|
|
12050
|
-
let settled = false;
|
|
12051
12174
|
options.success = function (resp) {
|
|
12052
|
-
settled = true;
|
|
12053
12175
|
const method = options.reset ? 'reset' : 'set';
|
|
12054
12176
|
// fromStorage prevents individual model set() calls from triggering auto-save on reads
|
|
12055
12177
|
collection[method](resp, Object.assign({}, options, { fromStorage: true }));
|
|
12056
12178
|
if (success)
|
|
12057
12179
|
success.call(options.context, collection, resp, options);
|
|
12058
|
-
|
|
12180
|
+
// Fire 'sync' before resolving, as `Model.fetch`/`save` do, so a throwing
|
|
12181
|
+
// listener rejects the promise instead of being lost.
|
|
12059
12182
|
collection.trigger('sync', collection, resp, options);
|
|
12183
|
+
promise && promise.resolve();
|
|
12060
12184
|
};
|
|
12061
12185
|
wrapError(this, options, promise);
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
onError(resp);
|
|
12067
|
-
};
|
|
12068
|
-
}
|
|
12069
|
-
const result = this.sync('read', this, options);
|
|
12070
|
-
if (promise) {
|
|
12071
|
-
Promise.resolve(result).then(
|
|
12072
|
-
// A sync that resolves its promise without invoking options.success:
|
|
12073
|
-
// treat the resolved value as the response, so the collection is still
|
|
12074
|
-
// populated and the promise settles instead of hanging.
|
|
12075
|
-
(value) => {
|
|
12076
|
-
if (!settled)
|
|
12077
|
-
options.success(value);
|
|
12078
|
-
},
|
|
12079
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
12080
|
-
// twice is a harmless no-op.
|
|
12081
|
-
(e) => promise.reject(e));
|
|
12082
|
-
return promise;
|
|
12083
|
-
}
|
|
12084
|
-
return result;
|
|
12186
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
12187
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
12188
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
12189
|
+
return promise ?? result;
|
|
12085
12190
|
}
|
|
12086
12191
|
/**
|
|
12087
12192
|
* Create a new instance of a model in this collection. Add the model to the
|
|
@@ -12093,9 +12198,22 @@
|
|
|
12093
12198
|
const wait = options.wait;
|
|
12094
12199
|
const return_promise = options.promise;
|
|
12095
12200
|
const promise = return_promise && getResolveablePromise();
|
|
12201
|
+
// `_prepareModel` reports attributes that fail validation by firing
|
|
12202
|
+
// `'invalid'` and returning null. With `promise`, capture that error so a
|
|
12203
|
+
// rejected promise is handed back rather than `null`.
|
|
12204
|
+
let validationError;
|
|
12205
|
+
const captureInvalid = (_c, err) => (validationError = err);
|
|
12206
|
+
if (return_promise)
|
|
12207
|
+
this.on('invalid', captureInvalid);
|
|
12096
12208
|
const preparedModel = this._prepareModel(model, options);
|
|
12097
|
-
if (
|
|
12098
|
-
|
|
12209
|
+
if (return_promise)
|
|
12210
|
+
this.off('invalid', captureInvalid);
|
|
12211
|
+
if (!preparedModel) {
|
|
12212
|
+
if (!return_promise)
|
|
12213
|
+
return null;
|
|
12214
|
+
promise.reject(validationError);
|
|
12215
|
+
return promise;
|
|
12216
|
+
}
|
|
12099
12217
|
if (!wait)
|
|
12100
12218
|
this.add(preparedModel, options);
|
|
12101
12219
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
@@ -12117,8 +12235,15 @@
|
|
|
12117
12235
|
error && error.call(options.context, model, e, options);
|
|
12118
12236
|
return_promise && promise.reject(e);
|
|
12119
12237
|
};
|
|
12120
|
-
|
|
12238
|
+
// `options.promise` is passed through, so `save` installs its own bridge
|
|
12239
|
+
// from the sync's returned promise to its callbacks: a sync that resolves
|
|
12240
|
+
// without calling them still reaches the wired `options.success` above,
|
|
12241
|
+
// which resolves our promise with the model. Only a rejection that bypassed
|
|
12242
|
+
// the callbacks (a rejecting sync, or a throwing `'sync'`/`'error'`
|
|
12243
|
+
// listener) still needs bridging to ours.
|
|
12244
|
+
const result = preparedModel.save(null, options);
|
|
12121
12245
|
if (return_promise) {
|
|
12246
|
+
Promise.resolve(result).catch((e) => promise.reject(e));
|
|
12122
12247
|
return promise;
|
|
12123
12248
|
}
|
|
12124
12249
|
else {
|
|
@@ -12270,13 +12395,7 @@
|
|
|
12270
12395
|
this.trigger.apply(this, arguments);
|
|
12271
12396
|
}
|
|
12272
12397
|
}
|
|
12273
|
-
|
|
12274
|
-
ensureUnloadListener(PersistentStorage);
|
|
12275
|
-
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
12276
|
-
// missing model record resolves to null, so hydration resolves rather
|
|
12277
|
-
// than rejecting.
|
|
12278
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
12279
|
-
}, _a = Symbol.iterator;
|
|
12398
|
+
_a = Symbol.iterator;
|
|
12280
12399
|
// This "enum" defines the three possible kinds of values which can be emitted
|
|
12281
12400
|
// by a CollectionIterator that correspond to the values(), keys() and entries()
|
|
12282
12401
|
// methods on Collection, respectively.
|
|
@@ -12344,6 +12463,7 @@
|
|
|
12344
12463
|
EventEmitter,
|
|
12345
12464
|
Model,
|
|
12346
12465
|
sync,
|
|
12466
|
+
HTTPError,
|
|
12347
12467
|
};
|
|
12348
12468
|
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
|
|
12349
12469
|
// We use `self` instead of `window` for `WebWorker` support.
|
|
@@ -12374,6 +12494,7 @@
|
|
|
12374
12494
|
exports.BrowserStorage = BrowserStorage;
|
|
12375
12495
|
exports.Collection = Collection;
|
|
12376
12496
|
exports.EventEmitter = EventEmitter;
|
|
12497
|
+
exports.HTTPError = HTTPError;
|
|
12377
12498
|
exports.Model = Model;
|
|
12378
12499
|
exports.PersistentStorage = PersistentStorage;
|
|
12379
12500
|
exports.default = skeletor;
|