@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.node.js
CHANGED
|
@@ -35,19 +35,6 @@ function __values(o) {
|
|
|
35
35
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
39
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
40
|
-
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");
|
|
41
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
45
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
46
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
47
|
-
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");
|
|
48
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
38
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
52
39
|
var e = new Error(message);
|
|
53
40
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
@@ -7796,6 +7783,16 @@ function guid() {
|
|
|
7796
7783
|
// Generate a pseudo-GUID by concatenating random hexadecimal.
|
|
7797
7784
|
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
|
|
7798
7785
|
}
|
|
7786
|
+
// Helpers
|
|
7787
|
+
// -------
|
|
7788
|
+
/**
|
|
7789
|
+
* Whether `value` is a thenable (a promise, or a promise-like object such as
|
|
7790
|
+
* a jqXHR). Decides whether a `sync` return value can be bridged to the
|
|
7791
|
+
* promise that `fetch`/`save` hand back.
|
|
7792
|
+
*/
|
|
7793
|
+
function isThenable(value) {
|
|
7794
|
+
return value != null && typeof value.then === 'function';
|
|
7795
|
+
}
|
|
7799
7796
|
function getResolveablePromise() {
|
|
7800
7797
|
const wrapper = {
|
|
7801
7798
|
isResolved: false,
|
|
@@ -7840,6 +7837,65 @@ function wrapError(model, options, promise) {
|
|
|
7840
7837
|
promise && promise.reject && promise.reject(resp);
|
|
7841
7838
|
};
|
|
7842
7839
|
}
|
|
7840
|
+
/**
|
|
7841
|
+
* Run a `sync` and make sure `promise` settles from its outcome, however the
|
|
7842
|
+
* sync reports it.
|
|
7843
|
+
*
|
|
7844
|
+
* A `sync` may invoke `options.success`/`options.error` (the callback
|
|
7845
|
+
* contract), or settle the promise it returns without calling them: a
|
|
7846
|
+
* promise-only custom sync, or an async sync whose promise rejects because a
|
|
7847
|
+
* `'sync'`/`'error'` listener threw after the callbacks ran. `options.success`
|
|
7848
|
+
* and `options.error` must already be wired to resolve/reject `promise`. This
|
|
7849
|
+
* wraps them to record that they ran, calls `run()`, and bridges a thenable
|
|
7850
|
+
* result to them so the promise never hangs:
|
|
7851
|
+
*
|
|
7852
|
+
* - fulfilment with the callbacks unrun is delivered to `options.success`, so
|
|
7853
|
+
* the response is still merged into the model or collection;
|
|
7854
|
+
* - rejection with the callbacks unrun is delivered to `options.error`, so the
|
|
7855
|
+
* caller's `error` callback and the `'error'` event still fire;
|
|
7856
|
+
* - rejection after the callbacks ran rejects `promise` directly: a no-op if
|
|
7857
|
+
* they already settled it, and the only way to settle it if a listener threw
|
|
7858
|
+
* before they could;
|
|
7859
|
+
* - a throw from either delivery (a throwing `success`/`error` callback or
|
|
7860
|
+
* listener) rejects `promise` instead of surfacing as an unhandled rejection.
|
|
7861
|
+
*
|
|
7862
|
+
* A non-thenable result (a callback-only sync that returns nothing, or an
|
|
7863
|
+
* XHR) is left alone: such a sync settles `promise` through its callbacks, and
|
|
7864
|
+
* bridging it would fire `success` on the next microtask, before the sync is
|
|
7865
|
+
* done. Without a `promise`, `run()` is called as is.
|
|
7866
|
+
*/
|
|
7867
|
+
function bridgeSyncToPromise(options, promise, run) {
|
|
7868
|
+
if (!promise)
|
|
7869
|
+
return run();
|
|
7870
|
+
let settled = false;
|
|
7871
|
+
const success = options.success;
|
|
7872
|
+
const error = options.error;
|
|
7873
|
+
options.success = (...args) => {
|
|
7874
|
+
settled = true;
|
|
7875
|
+
return success && success(...args);
|
|
7876
|
+
};
|
|
7877
|
+
options.error = (...args) => {
|
|
7878
|
+
settled = true;
|
|
7879
|
+
return error && error(...args);
|
|
7880
|
+
};
|
|
7881
|
+
const result = run();
|
|
7882
|
+
if (isThenable(result)) {
|
|
7883
|
+
Promise.resolve(result)
|
|
7884
|
+
.then((value) => {
|
|
7885
|
+
if (!settled)
|
|
7886
|
+
options.success(value);
|
|
7887
|
+
}, (e) => {
|
|
7888
|
+
if (settled) {
|
|
7889
|
+
promise.reject(e);
|
|
7890
|
+
}
|
|
7891
|
+
else {
|
|
7892
|
+
options.error(e);
|
|
7893
|
+
}
|
|
7894
|
+
})
|
|
7895
|
+
.catch((e) => promise.reject(e));
|
|
7896
|
+
}
|
|
7897
|
+
return result;
|
|
7898
|
+
}
|
|
7843
7899
|
// Map from CRUD to HTTP for our default `sync` implementation.
|
|
7844
7900
|
const methodMap = {
|
|
7845
7901
|
create: 'POST',
|
|
@@ -7889,43 +7945,77 @@ function getSyncMethod(model) {
|
|
|
7889
7945
|
return store ? store.sync() : sync;
|
|
7890
7946
|
}
|
|
7891
7947
|
/**
|
|
7892
|
-
*
|
|
7893
|
-
*
|
|
7894
|
-
*
|
|
7895
|
-
* to the model's `url()`.
|
|
7896
|
-
*
|
|
7897
|
-
* Some possible customizations could be:
|
|
7898
|
-
*
|
|
7899
|
-
* - Use `setTimeout` to batch rapid-fire updates into a single request.
|
|
7900
|
-
* - Persist models via WebSockets instead of Ajax.
|
|
7901
|
-
* - Persist models to browser storage
|
|
7948
|
+
* The error the default `sync` hands to `options.error`, and rejects with, for
|
|
7949
|
+
* a response outside the 2xx range.
|
|
7950
|
+
* @public
|
|
7902
7951
|
*/
|
|
7952
|
+
class HTTPError extends Error {
|
|
7953
|
+
constructor(response, method, url) {
|
|
7954
|
+
const statusText = response.statusText ? ` ${response.statusText}` : '';
|
|
7955
|
+
super(`${method} ${url} failed with HTTP ${response.status}${statusText}`);
|
|
7956
|
+
this.name = 'HTTPError';
|
|
7957
|
+
this.status = response.status;
|
|
7958
|
+
this.response = response;
|
|
7959
|
+
}
|
|
7960
|
+
}
|
|
7903
7961
|
/**
|
|
7962
|
+
* The default `sync`: a JSON request over `fetch()` to the model's `url()`.
|
|
7963
|
+
* Override it (or `Model.sync`/`Collection.sync`) to change how models are
|
|
7964
|
+
* persisted, for instance to batch rapid-fire updates with `setTimeout`, to
|
|
7965
|
+
* talk over WebSockets, or to write to browser storage.
|
|
7966
|
+
*
|
|
7967
|
+
* It honours both contracts a `sync` may use. On a 2xx response the JSON body
|
|
7968
|
+
* (`null` when the body is empty) is passed to `options.success` and the
|
|
7969
|
+
* returned promise resolves with it. On a non-2xx response, a network failure
|
|
7970
|
+
* or an unparsable body, `options.error` receives the error (an `HTTPError`
|
|
7971
|
+
* for the first) and the promise rejects with it.
|
|
7972
|
+
*
|
|
7973
|
+
* `options.data` is sent as query parameters on GET, which may not carry a
|
|
7974
|
+
* body, and as a JSON body otherwise. For create, update and patch the body
|
|
7975
|
+
* defaults to `options.attrs` or the model's `toJSON()`.
|
|
7904
7976
|
* @public
|
|
7905
7977
|
*/
|
|
7906
7978
|
function sync(method, model, options = {}) {
|
|
7979
|
+
const type = methodMap[method];
|
|
7907
7980
|
let data = options.data;
|
|
7908
7981
|
// Ensure that we have the appropriate request data.
|
|
7909
7982
|
if (data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
|
7910
7983
|
data = options.attrs || model.toJSON();
|
|
7911
7984
|
}
|
|
7912
|
-
|
|
7913
|
-
const params = {
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
const url = options.url || result(model, 'url') || urlError();
|
|
7923
|
-
const xhr = fetch(url, params);
|
|
7924
|
-
if (options) {
|
|
7925
|
-
options.xhr = xhr;
|
|
7985
|
+
let url = options.url || result(model, 'url') || urlError();
|
|
7986
|
+
const params = { method: type };
|
|
7987
|
+
if (data != null) {
|
|
7988
|
+
if (type === 'GET') {
|
|
7989
|
+
url += (url.includes('?') ? '&' : '?') + new URLSearchParams(data).toString();
|
|
7990
|
+
}
|
|
7991
|
+
else {
|
|
7992
|
+
params.body = JSON.stringify(data);
|
|
7993
|
+
params.headers = { 'Content-Type': 'application/json' };
|
|
7994
|
+
}
|
|
7926
7995
|
}
|
|
7996
|
+
const xhr = fetch(url, params);
|
|
7997
|
+
options.xhr = xhr;
|
|
7927
7998
|
model.trigger('request', model, xhr, { ...params, xhr });
|
|
7928
|
-
return xhr
|
|
7999
|
+
return Promise.resolve(xhr)
|
|
8000
|
+
.then(async (response) => {
|
|
8001
|
+
if (!response.ok)
|
|
8002
|
+
throw new HTTPError(response, type, url);
|
|
8003
|
+
const text = await response.text();
|
|
8004
|
+
return text ? JSON.parse(text) : null;
|
|
8005
|
+
})
|
|
8006
|
+
.then(
|
|
8007
|
+
// The callbacks run before the returned promise settles, so a caller that
|
|
8008
|
+
// bridges this promise (`Model.fetch`/`save`) sees the outcome as already
|
|
8009
|
+
// reported. A throw from `success` (a `'sync'` listener, say) is not
|
|
8010
|
+
// routed to `error`: it rejects the promise, which is what the bridge
|
|
8011
|
+
// expects once the callbacks have run.
|
|
8012
|
+
(resp) => {
|
|
8013
|
+
options.success?.(resp, options);
|
|
8014
|
+
return resp;
|
|
8015
|
+
}, (e) => {
|
|
8016
|
+
options.error?.(e);
|
|
8017
|
+
throw e;
|
|
8018
|
+
});
|
|
7929
8019
|
}
|
|
7930
8020
|
|
|
7931
8021
|
/**
|
|
@@ -10119,6 +10209,48 @@ var keyBy = createAggregator(function(result, value, key) {
|
|
|
10119
10209
|
baseAssignValue(result, key, value);
|
|
10120
10210
|
});
|
|
10121
10211
|
|
|
10212
|
+
/** Used to compose bitmasks for cloning. */
|
|
10213
|
+
var CLONE_DEEP_FLAG$1 = 1;
|
|
10214
|
+
|
|
10215
|
+
/**
|
|
10216
|
+
* Creates a function that performs a partial deep comparison between a given
|
|
10217
|
+
* object and `source`, returning `true` if the given object has equivalent
|
|
10218
|
+
* property values, else `false`.
|
|
10219
|
+
*
|
|
10220
|
+
* **Note:** The created function is equivalent to `_.isMatch` with `source`
|
|
10221
|
+
* partially applied.
|
|
10222
|
+
*
|
|
10223
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10224
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10225
|
+
* for a list of supported value comparisons.
|
|
10226
|
+
*
|
|
10227
|
+
* **Note:** Multiple values can be checked by combining several matchers
|
|
10228
|
+
* using `_.overSome`
|
|
10229
|
+
*
|
|
10230
|
+
* @static
|
|
10231
|
+
* @memberOf _
|
|
10232
|
+
* @since 3.0.0
|
|
10233
|
+
* @category Util
|
|
10234
|
+
* @param {Object} source The object of property values to match.
|
|
10235
|
+
* @returns {Function} Returns the new spec function.
|
|
10236
|
+
* @example
|
|
10237
|
+
*
|
|
10238
|
+
* var objects = [
|
|
10239
|
+
* { 'a': 1, 'b': 2, 'c': 3 },
|
|
10240
|
+
* { 'a': 4, 'b': 5, 'c': 6 }
|
|
10241
|
+
* ];
|
|
10242
|
+
*
|
|
10243
|
+
* _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
|
|
10244
|
+
* // => [{ 'a': 4, 'b': 5, 'c': 6 }]
|
|
10245
|
+
*
|
|
10246
|
+
* // Checking for several possible values
|
|
10247
|
+
* _.filter(objects, _.overSome([_.matches({ 'a': 1 }), _.matches({ 'a': 4 })]));
|
|
10248
|
+
* // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }]
|
|
10249
|
+
*/
|
|
10250
|
+
function matches(source) {
|
|
10251
|
+
return baseMatches(baseClone(source, CLONE_DEEP_FLAG$1));
|
|
10252
|
+
}
|
|
10253
|
+
|
|
10122
10254
|
/** Built-in value references. */
|
|
10123
10255
|
var spreadableSymbol = Symbol$1 ? Symbol$1.isConcatSpreadable : undefined;
|
|
10124
10256
|
|
|
@@ -10682,53 +10814,36 @@ function isEqual(value, other) {
|
|
|
10682
10814
|
return baseIsEqual(value, other);
|
|
10683
10815
|
}
|
|
10684
10816
|
|
|
10685
|
-
/** Used to compose bitmasks for cloning. */
|
|
10686
|
-
var CLONE_DEEP_FLAG$1 = 1;
|
|
10687
|
-
|
|
10688
10817
|
/**
|
|
10689
|
-
*
|
|
10690
|
-
*
|
|
10691
|
-
*
|
|
10692
|
-
*
|
|
10693
|
-
*
|
|
10818
|
+
* Performs a partial deep comparison between `object` and `source` to
|
|
10819
|
+
* determine if `object` contains equivalent property values.
|
|
10820
|
+
*
|
|
10821
|
+
* **Note:** This method is equivalent to `_.matches` when `source` is
|
|
10822
|
+
* partially applied.
|
|
10823
|
+
*
|
|
10824
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10825
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10826
|
+
* for a list of supported value comparisons.
|
|
10694
10827
|
*
|
|
10695
10828
|
* @static
|
|
10696
|
-
* @since 4.0.0
|
|
10697
10829
|
* @memberOf _
|
|
10698
|
-
* @
|
|
10699
|
-
* @
|
|
10700
|
-
* @
|
|
10830
|
+
* @since 3.0.0
|
|
10831
|
+
* @category Lang
|
|
10832
|
+
* @param {Object} object The object to inspect.
|
|
10833
|
+
* @param {Object} source The object of property values to match.
|
|
10834
|
+
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
|
|
10701
10835
|
* @example
|
|
10702
10836
|
*
|
|
10703
|
-
* var
|
|
10704
|
-
* { 'user': 'barney', 'age': 36, 'active': true },
|
|
10705
|
-
* { 'user': 'fred', 'age': 40, 'active': false }
|
|
10706
|
-
* ];
|
|
10707
|
-
*
|
|
10708
|
-
* // The `_.matches` iteratee shorthand.
|
|
10709
|
-
* _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
|
|
10710
|
-
* // => [{ 'user': 'barney', 'age': 36, 'active': true }]
|
|
10711
|
-
*
|
|
10712
|
-
* // The `_.matchesProperty` iteratee shorthand.
|
|
10713
|
-
* _.filter(users, _.iteratee(['user', 'fred']));
|
|
10714
|
-
* // => [{ 'user': 'fred', 'age': 40 }]
|
|
10837
|
+
* var object = { 'a': 1, 'b': 2 };
|
|
10715
10838
|
*
|
|
10716
|
-
*
|
|
10717
|
-
*
|
|
10718
|
-
* // => ['barney', 'fred']
|
|
10719
|
-
*
|
|
10720
|
-
* // Create custom iteratee shorthands.
|
|
10721
|
-
* _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
|
|
10722
|
-
* return !_.isRegExp(func) ? iteratee(func) : function(string) {
|
|
10723
|
-
* return func.test(string);
|
|
10724
|
-
* };
|
|
10725
|
-
* });
|
|
10839
|
+
* _.isMatch(object, { 'b': 2 });
|
|
10840
|
+
* // => true
|
|
10726
10841
|
*
|
|
10727
|
-
* _.
|
|
10728
|
-
* // =>
|
|
10842
|
+
* _.isMatch(object, { 'b': 1 });
|
|
10843
|
+
* // => false
|
|
10729
10844
|
*/
|
|
10730
|
-
function
|
|
10731
|
-
return
|
|
10845
|
+
function isMatch(object, source) {
|
|
10846
|
+
return object === source || baseIsMatch(object, source, getMatchData(source));
|
|
10732
10847
|
}
|
|
10733
10848
|
|
|
10734
10849
|
/**
|
|
@@ -11033,7 +11148,47 @@ var pick = flatRest(function(object, paths) {
|
|
|
11033
11148
|
return object == null ? {} : basePick(object, paths);
|
|
11034
11149
|
});
|
|
11035
11150
|
|
|
11036
|
-
|
|
11151
|
+
const INTERNALS = new WeakMap();
|
|
11152
|
+
function internals(model) {
|
|
11153
|
+
let priv = INTERNALS.get(model);
|
|
11154
|
+
if (!priv) {
|
|
11155
|
+
priv = { state: 'constructing' };
|
|
11156
|
+
INTERNALS.set(model, priv);
|
|
11157
|
+
}
|
|
11158
|
+
return priv;
|
|
11159
|
+
}
|
|
11160
|
+
/** Load the model from storage, marking it ready whether or not the read succeeds. */
|
|
11161
|
+
async function hydrate$1(model, priv) {
|
|
11162
|
+
ensureUnloadListener(PersistentStorage);
|
|
11163
|
+
try {
|
|
11164
|
+
await model.fetch({ fromStorage: true, promise: true });
|
|
11165
|
+
}
|
|
11166
|
+
finally {
|
|
11167
|
+
priv.state = 'ready';
|
|
11168
|
+
}
|
|
11169
|
+
}
|
|
11170
|
+
/** Evaluate the model's declared computed properties once, seeding the cache. */
|
|
11171
|
+
function initComputed(model, priv) {
|
|
11172
|
+
const defs = result(model, 'computed');
|
|
11173
|
+
if (!defs || isEmpty(defs))
|
|
11174
|
+
return;
|
|
11175
|
+
priv.computedDefs = defs;
|
|
11176
|
+
priv.computedCache = {};
|
|
11177
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
11178
|
+
priv.computedCache[key] = def.fn(model);
|
|
11179
|
+
}
|
|
11180
|
+
}
|
|
11181
|
+
/** Recompute one computed property, firing `change:key` when its value changed. */
|
|
11182
|
+
function updateComputed(model, priv, key, def, silent, options) {
|
|
11183
|
+
const newVal = def.fn(model);
|
|
11184
|
+
if (!isEqual(priv.computedCache[key], newVal)) {
|
|
11185
|
+
priv.computedCache[key] = newVal;
|
|
11186
|
+
if (!silent) {
|
|
11187
|
+
model.changed[key] = newVal;
|
|
11188
|
+
model.trigger('change:' + key, model, newVal, options);
|
|
11189
|
+
}
|
|
11190
|
+
}
|
|
11191
|
+
}
|
|
11037
11192
|
/**
|
|
11038
11193
|
* @public
|
|
11039
11194
|
* **Models** are the basic data object in the framework --
|
|
@@ -11048,15 +11203,12 @@ class Model extends EventEmitterObject {
|
|
|
11048
11203
|
*/
|
|
11049
11204
|
constructor(attributes, options) {
|
|
11050
11205
|
super();
|
|
11051
|
-
_Model_instances.add(this);
|
|
11052
|
-
_Model_computedCache.set(this, void 0);
|
|
11053
|
-
_Model_computedDefs.set(this, void 0);
|
|
11054
|
-
_Model_state.set(this, 'constructing');
|
|
11055
11206
|
this._changing = false;
|
|
11056
11207
|
this._pending = false;
|
|
11057
11208
|
this._url = '';
|
|
11058
11209
|
this.changed = {};
|
|
11059
11210
|
this.validationError = null;
|
|
11211
|
+
const priv = internals(this);
|
|
11060
11212
|
let attrs = attributes || {};
|
|
11061
11213
|
options = options || {};
|
|
11062
11214
|
this.preinitialize.apply(this, arguments);
|
|
@@ -11069,11 +11221,12 @@ class Model extends EventEmitterObject {
|
|
|
11069
11221
|
const default_attrs = result(this, 'defaults');
|
|
11070
11222
|
attrs = defaults(Object.assign({}, default_attrs, attrs), default_attrs);
|
|
11071
11223
|
this.set(attrs, options);
|
|
11072
|
-
|
|
11224
|
+
initComputed(this, priv);
|
|
11073
11225
|
this.attrs = new Proxy(this.attributes, {
|
|
11074
11226
|
get: (_target, key) => {
|
|
11075
|
-
|
|
11076
|
-
|
|
11227
|
+
const cache = priv.computedCache;
|
|
11228
|
+
if (typeof key !== 'symbol' && cache && key in cache) {
|
|
11229
|
+
return cache[key];
|
|
11077
11230
|
}
|
|
11078
11231
|
return this.attributes[key];
|
|
11079
11232
|
},
|
|
@@ -11091,16 +11244,16 @@ class Model extends EventEmitterObject {
|
|
|
11091
11244
|
// otherwise it's already ready and resolves immediately.
|
|
11092
11245
|
if (this.autoSync) {
|
|
11093
11246
|
if (getStorage(this) && !this.isNew()) {
|
|
11094
|
-
|
|
11095
|
-
this.hydrated =
|
|
11247
|
+
priv.state = 'hydrating';
|
|
11248
|
+
this.hydrated = hydrate$1(this, priv);
|
|
11096
11249
|
}
|
|
11097
11250
|
else {
|
|
11098
|
-
|
|
11251
|
+
priv.state = 'ready';
|
|
11099
11252
|
this.hydrated = Promise.resolve();
|
|
11100
11253
|
}
|
|
11101
11254
|
}
|
|
11102
11255
|
else {
|
|
11103
|
-
|
|
11256
|
+
priv.state = 'ready';
|
|
11104
11257
|
}
|
|
11105
11258
|
}
|
|
11106
11259
|
/**
|
|
@@ -11199,8 +11352,9 @@ class Model extends EventEmitterObject {
|
|
|
11199
11352
|
* Get the value of an attribute, or a computed property.
|
|
11200
11353
|
*/
|
|
11201
11354
|
get(attr) {
|
|
11202
|
-
|
|
11203
|
-
|
|
11355
|
+
const cache = internals(this).computedCache;
|
|
11356
|
+
if (cache && attr in cache) {
|
|
11357
|
+
return cache[attr];
|
|
11204
11358
|
}
|
|
11205
11359
|
return this.attributes[attr];
|
|
11206
11360
|
}
|
|
@@ -11242,10 +11396,20 @@ class Model extends EventEmitterObject {
|
|
|
11242
11396
|
return this.get(attr) != null;
|
|
11243
11397
|
}
|
|
11244
11398
|
/**
|
|
11245
|
-
*
|
|
11399
|
+
* Returns `true` if the model's attributes satisfy `attrs`.
|
|
11400
|
+
*
|
|
11401
|
+
* An attributes object is compared with lodash's `isMatch`: a partial, deep
|
|
11402
|
+
* comparison, so `{ form: { type: 'hidden' } }` matches a `form` attribute
|
|
11403
|
+
* that has `type: 'hidden'` alongside other keys. A function is called with
|
|
11404
|
+
* the attributes and its result coerced to a boolean.
|
|
11405
|
+
*
|
|
11406
|
+
* To scan a collection, prefer `Collection#find`, `filter` or `where`. They
|
|
11407
|
+
* build their matcher once per scan instead of once per model.
|
|
11246
11408
|
*/
|
|
11247
11409
|
matches(attrs) {
|
|
11248
|
-
|
|
11410
|
+
if (typeof attrs === 'function')
|
|
11411
|
+
return !!attrs(this.attributes);
|
|
11412
|
+
return isMatch(this.attributes, attrs);
|
|
11249
11413
|
}
|
|
11250
11414
|
/**
|
|
11251
11415
|
* Set a hash of model attributes on the object, firing `"change"`. This is
|
|
@@ -11266,9 +11430,10 @@ class Model extends EventEmitterObject {
|
|
|
11266
11430
|
attrs[key] = val;
|
|
11267
11431
|
}
|
|
11268
11432
|
options = options || {};
|
|
11433
|
+
const priv = internals(this);
|
|
11269
11434
|
// Guard must come after arg normalization: when called as set(obj, opts),
|
|
11270
11435
|
// fromStorage lives in `val` (second arg), not `options` (third arg).
|
|
11271
|
-
if (this.autoSync &&
|
|
11436
|
+
if (this.autoSync && priv.state === 'hydrating' && !options.fromStorage) {
|
|
11272
11437
|
throw new Error(`Skeletor: set() called on an autoSync model before hydration resolved. Await model.hydrated first.`);
|
|
11273
11438
|
}
|
|
11274
11439
|
// Run validation.
|
|
@@ -11312,10 +11477,10 @@ class Model extends EventEmitterObject {
|
|
|
11312
11477
|
}
|
|
11313
11478
|
}
|
|
11314
11479
|
// Recalculate computed properties whose deps changed.
|
|
11315
|
-
if (changes.length &&
|
|
11316
|
-
for (const [key, def] of Object.entries(
|
|
11480
|
+
if (changes.length && priv.computedDefs) {
|
|
11481
|
+
for (const [key, def] of Object.entries(priv.computedDefs)) {
|
|
11317
11482
|
if (def.deps.some((dep) => changes.includes(dep))) {
|
|
11318
|
-
|
|
11483
|
+
updateComputed(this, priv, key, def, silent, options);
|
|
11319
11484
|
}
|
|
11320
11485
|
}
|
|
11321
11486
|
}
|
|
@@ -11335,13 +11500,13 @@ class Model extends EventEmitterObject {
|
|
|
11335
11500
|
// Auto-save: schedule a debounced write when autoSync is on and there
|
|
11336
11501
|
// were actual changes that originated from application code (not a
|
|
11337
11502
|
// storage read or an explicit noAutoSave call).
|
|
11338
|
-
if (
|
|
11503
|
+
if (priv.state === 'ready' &&
|
|
11339
11504
|
this.autoSync &&
|
|
11340
11505
|
getStorage(this) &&
|
|
11341
11506
|
changes.length &&
|
|
11342
11507
|
!options.fromStorage &&
|
|
11343
11508
|
!options.noAutoSave) {
|
|
11344
|
-
scheduleAutoSave(this, () =>
|
|
11509
|
+
scheduleAutoSave(this, () => this.save(null, { noAutoSave: true }), this.autoSyncDelay);
|
|
11345
11510
|
}
|
|
11346
11511
|
return this;
|
|
11347
11512
|
}
|
|
@@ -11418,47 +11583,25 @@ class Model extends EventEmitterObject {
|
|
|
11418
11583
|
options = Object.assign({ parse: true }, options);
|
|
11419
11584
|
const success = options.success;
|
|
11420
11585
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
11421
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
11422
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
11423
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
11424
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
11425
|
-
// it's reliable by the time those handlers run.
|
|
11426
|
-
let settled = false;
|
|
11427
11586
|
options.success = (resp) => {
|
|
11428
|
-
settled = true;
|
|
11429
11587
|
const serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11430
11588
|
// fromStorage prevents the set() from triggering auto-save on reads
|
|
11431
|
-
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true })))
|
|
11589
|
+
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true }))) {
|
|
11590
|
+
// The fetched attributes failed validation (`'invalid'` has fired).
|
|
11591
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11592
|
+
promise && promise.reject(this.validationError);
|
|
11432
11593
|
return false;
|
|
11594
|
+
}
|
|
11433
11595
|
if (success)
|
|
11434
11596
|
success.call(options.context, this, resp, options);
|
|
11435
11597
|
this.trigger('sync', this, resp, options);
|
|
11436
11598
|
promise && promise.resolve(resp);
|
|
11437
11599
|
};
|
|
11438
11600
|
wrapError(this, options, promise);
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
onError(resp);
|
|
11444
|
-
};
|
|
11445
|
-
}
|
|
11446
|
-
const result = this.sync('read', this, options);
|
|
11447
|
-
if (promise) {
|
|
11448
|
-
Promise.resolve(result).then(
|
|
11449
|
-
// A sync that resolves its promise without invoking options.success:
|
|
11450
|
-
// treat the resolved value as the response, so the model is still
|
|
11451
|
-
// merged and the promise settles instead of hanging.
|
|
11452
|
-
(value) => {
|
|
11453
|
-
if (!settled)
|
|
11454
|
-
options.success(value);
|
|
11455
|
-
},
|
|
11456
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
11457
|
-
// twice is a harmless no-op.
|
|
11458
|
-
(e) => promise.reject(e));
|
|
11459
|
-
return promise;
|
|
11460
|
-
}
|
|
11461
|
-
return result;
|
|
11601
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11602
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11603
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
11604
|
+
return promise ?? result;
|
|
11462
11605
|
}
|
|
11463
11606
|
/**
|
|
11464
11607
|
* Set a hash of model attributes, and sync the model to the server.
|
|
@@ -11479,16 +11622,19 @@ class Model extends EventEmitterObject {
|
|
|
11479
11622
|
options = Object.assign({ validate: true, parse: true }, options);
|
|
11480
11623
|
const wait = options.wait;
|
|
11481
11624
|
const return_promise = options.promise;
|
|
11482
|
-
const promise = return_promise
|
|
11625
|
+
const promise = return_promise ? getResolveablePromise() : undefined;
|
|
11483
11626
|
// If we're not waiting and attributes exist, save acts as
|
|
11484
11627
|
// `set(attr).save(null, opts)` with validation. Otherwise, check if
|
|
11485
11628
|
// the model will be valid when the attributes, if any, are set.
|
|
11486
|
-
|
|
11487
|
-
|
|
11629
|
+
const valid = attrs && !wait ? !!this.set(attrs, options) : this._validate(attrs || {}, options);
|
|
11630
|
+
if (!valid) {
|
|
11631
|
+
// Nothing is synced (`'invalid'` has fired). With `promise`, hand back a
|
|
11632
|
+
// rejected promise rather than `false`, so the promise contract holds
|
|
11633
|
+
// for every outcome.
|
|
11634
|
+
if (!promise)
|
|
11488
11635
|
return false;
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
return false;
|
|
11636
|
+
promise.reject(this.validationError);
|
|
11637
|
+
return promise;
|
|
11492
11638
|
}
|
|
11493
11639
|
// After a successful server-side save, the client is (optionally)
|
|
11494
11640
|
// updated with the server-side state.
|
|
@@ -11501,8 +11647,12 @@ class Model extends EventEmitterObject {
|
|
|
11501
11647
|
let serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11502
11648
|
if (wait)
|
|
11503
11649
|
serverAttrs = Object.assign({}, attrs, serverAttrs);
|
|
11504
|
-
if (serverAttrs && !this.set(serverAttrs, options))
|
|
11650
|
+
if (serverAttrs && !this.set(serverAttrs, options)) {
|
|
11651
|
+
// The server attributes failed validation (`'invalid'` has fired).
|
|
11652
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11653
|
+
return_promise && promise.reject(this.validationError);
|
|
11505
11654
|
return false;
|
|
11655
|
+
}
|
|
11506
11656
|
if (success)
|
|
11507
11657
|
success.call(options.context, this, resp, options);
|
|
11508
11658
|
this.trigger('sync', this, resp, options);
|
|
@@ -11519,15 +11669,12 @@ class Model extends EventEmitterObject {
|
|
|
11519
11669
|
const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
|
11520
11670
|
if (method === 'patch' && !options.attrs)
|
|
11521
11671
|
options.attrs = attrs;
|
|
11522
|
-
|
|
11672
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11673
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11674
|
+
const xhr = bridgeSyncToPromise(options, promise, () => this.sync(method, this, options));
|
|
11523
11675
|
// Restore attributes.
|
|
11524
11676
|
this.attributes = attributes;
|
|
11525
|
-
|
|
11526
|
-
return promise;
|
|
11527
|
-
}
|
|
11528
|
-
else {
|
|
11529
|
-
return xhr;
|
|
11530
|
-
}
|
|
11677
|
+
return promise ?? xhr;
|
|
11531
11678
|
}
|
|
11532
11679
|
/**
|
|
11533
11680
|
* Destroy this model on the server if it was already persisted.
|
|
@@ -11640,40 +11787,40 @@ class Model extends EventEmitterObject {
|
|
|
11640
11787
|
return false;
|
|
11641
11788
|
}
|
|
11642
11789
|
}
|
|
11643
|
-
_Model_computedCache = new WeakMap(), _Model_computedDefs = new WeakMap(), _Model_state = new WeakMap(), _Model_instances = new WeakSet(), _Model_hydrate = async function _Model_hydrate() {
|
|
11644
|
-
ensureUnloadListener(PersistentStorage);
|
|
11645
|
-
try {
|
|
11646
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
11647
|
-
}
|
|
11648
|
-
finally {
|
|
11649
|
-
__classPrivateFieldSet(this, _Model_state, 'ready', "f");
|
|
11650
|
-
}
|
|
11651
|
-
}, _Model_doAutoSave = function _Model_doAutoSave() {
|
|
11652
|
-
return this.save(null, { noAutoSave: true });
|
|
11653
|
-
}, _Model_updateComputed = function _Model_updateComputed(key, def, silent, options) {
|
|
11654
|
-
const newVal = def.fn(this);
|
|
11655
|
-
if (!isEqual(__classPrivateFieldGet(this, _Model_computedCache, "f")[key], newVal)) {
|
|
11656
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = newVal;
|
|
11657
|
-
if (!silent) {
|
|
11658
|
-
this.changed[key] = newVal;
|
|
11659
|
-
this.trigger('change:' + key, this, newVal, options);
|
|
11660
|
-
}
|
|
11661
|
-
}
|
|
11662
|
-
}, _Model_initComputed = function _Model_initComputed() {
|
|
11663
|
-
const defs = result(this, 'computed');
|
|
11664
|
-
if (!defs || isEmpty(defs))
|
|
11665
|
-
return;
|
|
11666
|
-
__classPrivateFieldSet(this, _Model_computedDefs, defs, "f");
|
|
11667
|
-
__classPrivateFieldSet(this, _Model_computedCache, {}, "f");
|
|
11668
|
-
for (const [key, def] of Object.entries(defs)) {
|
|
11669
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = def.fn(this);
|
|
11670
|
-
}
|
|
11671
|
-
};
|
|
11672
11790
|
|
|
11673
|
-
var
|
|
11791
|
+
var _a;
|
|
11674
11792
|
// Default options for `Collection#set`.
|
|
11675
11793
|
const setOptions = { add: true, remove: true, merge: true };
|
|
11676
11794
|
const addOptions = { add: true, remove: false };
|
|
11795
|
+
/**
|
|
11796
|
+
* Normalizes the predicate forms accepted by the query methods into one
|
|
11797
|
+
* `(model) => value` function. A function is used as-is, a string reads that
|
|
11798
|
+
* attribute, and an attributes object becomes a lodash `matches` matcher.
|
|
11799
|
+
*
|
|
11800
|
+
* The matcher is built here, once per scan. Calling `Model#matches` per model
|
|
11801
|
+
* instead rebuilds the match data for every model visited, which made
|
|
11802
|
+
* `findWhere` an order of magnitude slower than a hand-written predicate.
|
|
11803
|
+
*/
|
|
11804
|
+
function toModelPredicate(pred) {
|
|
11805
|
+
if (isFunction(pred))
|
|
11806
|
+
return pred;
|
|
11807
|
+
if (isString(pred))
|
|
11808
|
+
return (m) => m.get(pred);
|
|
11809
|
+
const matcher = matches(pred);
|
|
11810
|
+
return (m) => matcher(m.attributes);
|
|
11811
|
+
}
|
|
11812
|
+
/**
|
|
11813
|
+
* Load the collection's members from storage. A module-level function rather
|
|
11814
|
+
* than a `#private` method so the published declarations carry no private
|
|
11815
|
+
* members, which TypeScript 6 rejects in anonymously extended classes (TS4094).
|
|
11816
|
+
*/
|
|
11817
|
+
async function hydrate(collection) {
|
|
11818
|
+
ensureUnloadListener(PersistentStorage);
|
|
11819
|
+
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
11820
|
+
// missing model record resolves to null, so hydration resolves rather
|
|
11821
|
+
// than rejecting.
|
|
11822
|
+
await collection.fetch({ fromStorage: true, promise: true });
|
|
11823
|
+
}
|
|
11677
11824
|
/**
|
|
11678
11825
|
* @public
|
|
11679
11826
|
* If models tend to represent a single row of data, a Collection is
|
|
@@ -11691,7 +11838,6 @@ class Collection extends EventEmitterObject {
|
|
|
11691
11838
|
*/
|
|
11692
11839
|
constructor(models, options) {
|
|
11693
11840
|
super();
|
|
11694
|
-
_Collection_instances.add(this);
|
|
11695
11841
|
this._url = '';
|
|
11696
11842
|
/**
|
|
11697
11843
|
* @public
|
|
@@ -11713,7 +11859,7 @@ class Collection extends EventEmitterObject {
|
|
|
11713
11859
|
// uniformly `await collection.hydrated`; it resolves immediately when
|
|
11714
11860
|
// there's no storage to hydrate from.
|
|
11715
11861
|
if (this.autoSync) {
|
|
11716
|
-
this.hydrated = getStorage(this) ?
|
|
11862
|
+
this.hydrated = getStorage(this) ? hydrate(this) : Promise.resolve();
|
|
11717
11863
|
}
|
|
11718
11864
|
}
|
|
11719
11865
|
get comparator() {
|
|
@@ -12002,17 +12148,13 @@ class Collection extends EventEmitterObject {
|
|
|
12002
12148
|
return this.models.slice(start, end);
|
|
12003
12149
|
}
|
|
12004
12150
|
filter(callback, thisArg) {
|
|
12005
|
-
return this.models.filter(
|
|
12006
|
-
? callback
|
|
12007
|
-
: (m) => m.matches(callback), thisArg);
|
|
12151
|
+
return this.models.filter(toModelPredicate(callback), thisArg);
|
|
12008
12152
|
}
|
|
12009
12153
|
every(pred) {
|
|
12010
12154
|
if (isFunction(pred)) {
|
|
12011
|
-
return this.models.
|
|
12012
|
-
}
|
|
12013
|
-
else {
|
|
12014
|
-
return this.models.every((m) => m.matches(pred));
|
|
12155
|
+
return this.models.every((m) => pred(m.attributes));
|
|
12015
12156
|
}
|
|
12157
|
+
return this.models.every(toModelPredicate(pred));
|
|
12016
12158
|
}
|
|
12017
12159
|
difference(values) {
|
|
12018
12160
|
return this.models.filter((m) => !values.includes(m));
|
|
@@ -12028,16 +12170,12 @@ class Collection extends EventEmitterObject {
|
|
|
12028
12170
|
}
|
|
12029
12171
|
some(pred) {
|
|
12030
12172
|
if (isFunction(pred)) {
|
|
12031
|
-
return this.models.
|
|
12032
|
-
}
|
|
12033
|
-
else {
|
|
12034
|
-
return this.models.some((m) => m.matches(pred));
|
|
12173
|
+
return this.models.some((m) => pred(m.attributes));
|
|
12035
12174
|
}
|
|
12175
|
+
return this.models.some(toModelPredicate(pred));
|
|
12036
12176
|
}
|
|
12037
12177
|
sortBy(iteratee) {
|
|
12038
|
-
return sortBy(this.models,
|
|
12039
|
-
? iteratee
|
|
12040
|
-
: (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)));
|
|
12178
|
+
return sortBy(this.models, toModelPredicate(iteratee));
|
|
12041
12179
|
}
|
|
12042
12180
|
isEmpty() {
|
|
12043
12181
|
return !this.models.length;
|
|
@@ -12058,26 +12196,22 @@ class Collection extends EventEmitterObject {
|
|
|
12058
12196
|
return this.models.length;
|
|
12059
12197
|
}
|
|
12060
12198
|
countBy(f) {
|
|
12061
|
-
return countBy(this.models,
|
|
12199
|
+
return countBy(this.models, toModelPredicate(f));
|
|
12062
12200
|
}
|
|
12063
12201
|
groupBy(pred) {
|
|
12064
|
-
return groupBy(this.models,
|
|
12202
|
+
return groupBy(this.models, toModelPredicate(pred));
|
|
12065
12203
|
}
|
|
12066
12204
|
indexOf(model, fromIndex) {
|
|
12067
12205
|
return this.models.indexOf(model, fromIndex);
|
|
12068
12206
|
}
|
|
12069
12207
|
findLastIndex(pred, fromIndex) {
|
|
12070
|
-
return this.models.findLastIndex(
|
|
12071
|
-
? pred
|
|
12072
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)), fromIndex);
|
|
12208
|
+
return this.models.findLastIndex(toModelPredicate(pred), fromIndex);
|
|
12073
12209
|
}
|
|
12074
12210
|
lastIndexOf(model, fromIndex) {
|
|
12075
12211
|
return this.models.lastIndexOf(model, fromIndex);
|
|
12076
12212
|
}
|
|
12077
12213
|
findIndex(pred) {
|
|
12078
|
-
return this.models.findIndex(
|
|
12079
|
-
? pred
|
|
12080
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
|
|
12214
|
+
return this.models.findIndex(toModelPredicate(pred));
|
|
12081
12215
|
}
|
|
12082
12216
|
last() {
|
|
12083
12217
|
const length = this.models == null ? 0 : this.models.length;
|
|
@@ -12090,9 +12224,7 @@ class Collection extends EventEmitterObject {
|
|
|
12090
12224
|
return this.head();
|
|
12091
12225
|
}
|
|
12092
12226
|
map(cb, thisArg) {
|
|
12093
|
-
return this.models.map(
|
|
12094
|
-
? cb
|
|
12095
|
-
: (m) => (isString(cb) ? m.get(cb) : m.matches(cb)), thisArg);
|
|
12227
|
+
return this.models.map(toModelPredicate(cb), thisArg);
|
|
12096
12228
|
}
|
|
12097
12229
|
reduce(callback, initialValue) {
|
|
12098
12230
|
return this.models.reduce(callback, initialValue || this.models[0]);
|
|
@@ -12143,10 +12275,7 @@ class Collection extends EventEmitterObject {
|
|
|
12143
12275
|
return this.where(attrs, true);
|
|
12144
12276
|
}
|
|
12145
12277
|
find(predicate, fromIndex) {
|
|
12146
|
-
|
|
12147
|
-
? predicate
|
|
12148
|
-
: (m) => m.matches(predicate);
|
|
12149
|
-
return this.models.find(pred, fromIndex);
|
|
12278
|
+
return this.models.find(toModelPredicate(predicate), fromIndex);
|
|
12150
12279
|
}
|
|
12151
12280
|
/**
|
|
12152
12281
|
* Force the collection to re-sort itself. You don't need to call this under
|
|
@@ -12189,46 +12318,22 @@ class Collection extends EventEmitterObject {
|
|
|
12189
12318
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
12190
12319
|
const collection = this;
|
|
12191
12320
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
12192
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
12193
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
12194
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
12195
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
12196
|
-
// it's reliable by the time those handlers run.
|
|
12197
|
-
let settled = false;
|
|
12198
12321
|
options.success = function (resp) {
|
|
12199
|
-
settled = true;
|
|
12200
12322
|
const method = options.reset ? 'reset' : 'set';
|
|
12201
12323
|
// fromStorage prevents individual model set() calls from triggering auto-save on reads
|
|
12202
12324
|
collection[method](resp, Object.assign({}, options, { fromStorage: true }));
|
|
12203
12325
|
if (success)
|
|
12204
12326
|
success.call(options.context, collection, resp, options);
|
|
12205
|
-
|
|
12327
|
+
// Fire 'sync' before resolving, as `Model.fetch`/`save` do, so a throwing
|
|
12328
|
+
// listener rejects the promise instead of being lost.
|
|
12206
12329
|
collection.trigger('sync', collection, resp, options);
|
|
12330
|
+
promise && promise.resolve();
|
|
12207
12331
|
};
|
|
12208
12332
|
wrapError(this, options, promise);
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
onError(resp);
|
|
12214
|
-
};
|
|
12215
|
-
}
|
|
12216
|
-
const result = this.sync('read', this, options);
|
|
12217
|
-
if (promise) {
|
|
12218
|
-
Promise.resolve(result).then(
|
|
12219
|
-
// A sync that resolves its promise without invoking options.success:
|
|
12220
|
-
// treat the resolved value as the response, so the collection is still
|
|
12221
|
-
// populated and the promise settles instead of hanging.
|
|
12222
|
-
(value) => {
|
|
12223
|
-
if (!settled)
|
|
12224
|
-
options.success(value);
|
|
12225
|
-
},
|
|
12226
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
12227
|
-
// twice is a harmless no-op.
|
|
12228
|
-
(e) => promise.reject(e));
|
|
12229
|
-
return promise;
|
|
12230
|
-
}
|
|
12231
|
-
return result;
|
|
12333
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
12334
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
12335
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
12336
|
+
return promise ?? result;
|
|
12232
12337
|
}
|
|
12233
12338
|
/**
|
|
12234
12339
|
* Create a new instance of a model in this collection. Add the model to the
|
|
@@ -12240,9 +12345,22 @@ class Collection extends EventEmitterObject {
|
|
|
12240
12345
|
const wait = options.wait;
|
|
12241
12346
|
const return_promise = options.promise;
|
|
12242
12347
|
const promise = return_promise && getResolveablePromise();
|
|
12348
|
+
// `_prepareModel` reports attributes that fail validation by firing
|
|
12349
|
+
// `'invalid'` and returning null. With `promise`, capture that error so a
|
|
12350
|
+
// rejected promise is handed back rather than `null`.
|
|
12351
|
+
let validationError;
|
|
12352
|
+
const captureInvalid = (_c, err) => (validationError = err);
|
|
12353
|
+
if (return_promise)
|
|
12354
|
+
this.on('invalid', captureInvalid);
|
|
12243
12355
|
const preparedModel = this._prepareModel(model, options);
|
|
12244
|
-
if (
|
|
12245
|
-
|
|
12356
|
+
if (return_promise)
|
|
12357
|
+
this.off('invalid', captureInvalid);
|
|
12358
|
+
if (!preparedModel) {
|
|
12359
|
+
if (!return_promise)
|
|
12360
|
+
return null;
|
|
12361
|
+
promise.reject(validationError);
|
|
12362
|
+
return promise;
|
|
12363
|
+
}
|
|
12246
12364
|
if (!wait)
|
|
12247
12365
|
this.add(preparedModel, options);
|
|
12248
12366
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
@@ -12264,8 +12382,15 @@ class Collection extends EventEmitterObject {
|
|
|
12264
12382
|
error && error.call(options.context, model, e, options);
|
|
12265
12383
|
return_promise && promise.reject(e);
|
|
12266
12384
|
};
|
|
12267
|
-
|
|
12385
|
+
// `options.promise` is passed through, so `save` installs its own bridge
|
|
12386
|
+
// from the sync's returned promise to its callbacks: a sync that resolves
|
|
12387
|
+
// without calling them still reaches the wired `options.success` above,
|
|
12388
|
+
// which resolves our promise with the model. Only a rejection that bypassed
|
|
12389
|
+
// the callbacks (a rejecting sync, or a throwing `'sync'`/`'error'`
|
|
12390
|
+
// listener) still needs bridging to ours.
|
|
12391
|
+
const result = preparedModel.save(null, options);
|
|
12268
12392
|
if (return_promise) {
|
|
12393
|
+
Promise.resolve(result).catch((e) => promise.reject(e));
|
|
12269
12394
|
return promise;
|
|
12270
12395
|
}
|
|
12271
12396
|
else {
|
|
@@ -12417,13 +12542,7 @@ class Collection extends EventEmitterObject {
|
|
|
12417
12542
|
this.trigger.apply(this, arguments);
|
|
12418
12543
|
}
|
|
12419
12544
|
}
|
|
12420
|
-
|
|
12421
|
-
ensureUnloadListener(PersistentStorage);
|
|
12422
|
-
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
12423
|
-
// missing model record resolves to null, so hydration resolves rather
|
|
12424
|
-
// than rejecting.
|
|
12425
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
12426
|
-
}, _a = Symbol.iterator;
|
|
12545
|
+
_a = Symbol.iterator;
|
|
12427
12546
|
// This "enum" defines the three possible kinds of values which can be emitted
|
|
12428
12547
|
// by a CollectionIterator that correspond to the values(), keys() and entries()
|
|
12429
12548
|
// methods on Collection, respectively.
|
|
@@ -12491,6 +12610,7 @@ const skeletor = {
|
|
|
12491
12610
|
EventEmitter,
|
|
12492
12611
|
Model,
|
|
12493
12612
|
sync,
|
|
12613
|
+
HTTPError,
|
|
12494
12614
|
};
|
|
12495
12615
|
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
|
|
12496
12616
|
// We use `self` instead of `window` for `WebWorker` support.
|
|
@@ -12531,6 +12651,7 @@ PersistentStorage.nodeStorage = NodeSQLiteStorage;
|
|
|
12531
12651
|
exports.BrowserStorage = BrowserStorage;
|
|
12532
12652
|
exports.Collection = Collection;
|
|
12533
12653
|
exports.EventEmitter = EventEmitter;
|
|
12654
|
+
exports.HTTPError = HTTPError;
|
|
12534
12655
|
exports.Model = Model;
|
|
12535
12656
|
exports.NodeSQLiteStorage = NodeSQLiteStorage;
|
|
12536
12657
|
exports.PersistentStorage = PersistentStorage;
|