@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.mjs
CHANGED
|
@@ -31,19 +31,6 @@ function __values(o) {
|
|
|
31
31
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
35
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
36
|
-
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");
|
|
37
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
41
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
42
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
43
|
-
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");
|
|
44
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
34
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
48
35
|
var e = new Error(message);
|
|
49
36
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
@@ -7792,6 +7779,16 @@ function guid() {
|
|
|
7792
7779
|
// Generate a pseudo-GUID by concatenating random hexadecimal.
|
|
7793
7780
|
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
|
|
7794
7781
|
}
|
|
7782
|
+
// Helpers
|
|
7783
|
+
// -------
|
|
7784
|
+
/**
|
|
7785
|
+
* Whether `value` is a thenable (a promise, or a promise-like object such as
|
|
7786
|
+
* a jqXHR). Decides whether a `sync` return value can be bridged to the
|
|
7787
|
+
* promise that `fetch`/`save` hand back.
|
|
7788
|
+
*/
|
|
7789
|
+
function isThenable(value) {
|
|
7790
|
+
return value != null && typeof value.then === 'function';
|
|
7791
|
+
}
|
|
7795
7792
|
function getResolveablePromise() {
|
|
7796
7793
|
const wrapper = {
|
|
7797
7794
|
isResolved: false,
|
|
@@ -7836,6 +7833,65 @@ function wrapError(model, options, promise) {
|
|
|
7836
7833
|
promise && promise.reject && promise.reject(resp);
|
|
7837
7834
|
};
|
|
7838
7835
|
}
|
|
7836
|
+
/**
|
|
7837
|
+
* Run a `sync` and make sure `promise` settles from its outcome, however the
|
|
7838
|
+
* sync reports it.
|
|
7839
|
+
*
|
|
7840
|
+
* A `sync` may invoke `options.success`/`options.error` (the callback
|
|
7841
|
+
* contract), or settle the promise it returns without calling them: a
|
|
7842
|
+
* promise-only custom sync, or an async sync whose promise rejects because a
|
|
7843
|
+
* `'sync'`/`'error'` listener threw after the callbacks ran. `options.success`
|
|
7844
|
+
* and `options.error` must already be wired to resolve/reject `promise`. This
|
|
7845
|
+
* wraps them to record that they ran, calls `run()`, and bridges a thenable
|
|
7846
|
+
* result to them so the promise never hangs:
|
|
7847
|
+
*
|
|
7848
|
+
* - fulfilment with the callbacks unrun is delivered to `options.success`, so
|
|
7849
|
+
* the response is still merged into the model or collection;
|
|
7850
|
+
* - rejection with the callbacks unrun is delivered to `options.error`, so the
|
|
7851
|
+
* caller's `error` callback and the `'error'` event still fire;
|
|
7852
|
+
* - rejection after the callbacks ran rejects `promise` directly: a no-op if
|
|
7853
|
+
* they already settled it, and the only way to settle it if a listener threw
|
|
7854
|
+
* before they could;
|
|
7855
|
+
* - a throw from either delivery (a throwing `success`/`error` callback or
|
|
7856
|
+
* listener) rejects `promise` instead of surfacing as an unhandled rejection.
|
|
7857
|
+
*
|
|
7858
|
+
* A non-thenable result (a callback-only sync that returns nothing, or an
|
|
7859
|
+
* XHR) is left alone: such a sync settles `promise` through its callbacks, and
|
|
7860
|
+
* bridging it would fire `success` on the next microtask, before the sync is
|
|
7861
|
+
* done. Without a `promise`, `run()` is called as is.
|
|
7862
|
+
*/
|
|
7863
|
+
function bridgeSyncToPromise(options, promise, run) {
|
|
7864
|
+
if (!promise)
|
|
7865
|
+
return run();
|
|
7866
|
+
let settled = false;
|
|
7867
|
+
const success = options.success;
|
|
7868
|
+
const error = options.error;
|
|
7869
|
+
options.success = (...args) => {
|
|
7870
|
+
settled = true;
|
|
7871
|
+
return success && success(...args);
|
|
7872
|
+
};
|
|
7873
|
+
options.error = (...args) => {
|
|
7874
|
+
settled = true;
|
|
7875
|
+
return error && error(...args);
|
|
7876
|
+
};
|
|
7877
|
+
const result = run();
|
|
7878
|
+
if (isThenable(result)) {
|
|
7879
|
+
Promise.resolve(result)
|
|
7880
|
+
.then((value) => {
|
|
7881
|
+
if (!settled)
|
|
7882
|
+
options.success(value);
|
|
7883
|
+
}, (e) => {
|
|
7884
|
+
if (settled) {
|
|
7885
|
+
promise.reject(e);
|
|
7886
|
+
}
|
|
7887
|
+
else {
|
|
7888
|
+
options.error(e);
|
|
7889
|
+
}
|
|
7890
|
+
})
|
|
7891
|
+
.catch((e) => promise.reject(e));
|
|
7892
|
+
}
|
|
7893
|
+
return result;
|
|
7894
|
+
}
|
|
7839
7895
|
// Map from CRUD to HTTP for our default `sync` implementation.
|
|
7840
7896
|
const methodMap = {
|
|
7841
7897
|
create: 'POST',
|
|
@@ -7885,43 +7941,77 @@ function getSyncMethod(model) {
|
|
|
7885
7941
|
return store ? store.sync() : sync;
|
|
7886
7942
|
}
|
|
7887
7943
|
/**
|
|
7888
|
-
*
|
|
7889
|
-
*
|
|
7890
|
-
*
|
|
7891
|
-
* to the model's `url()`.
|
|
7892
|
-
*
|
|
7893
|
-
* Some possible customizations could be:
|
|
7894
|
-
*
|
|
7895
|
-
* - Use `setTimeout` to batch rapid-fire updates into a single request.
|
|
7896
|
-
* - Persist models via WebSockets instead of Ajax.
|
|
7897
|
-
* - Persist models to browser storage
|
|
7944
|
+
* The error the default `sync` hands to `options.error`, and rejects with, for
|
|
7945
|
+
* a response outside the 2xx range.
|
|
7946
|
+
* @public
|
|
7898
7947
|
*/
|
|
7948
|
+
class HTTPError extends Error {
|
|
7949
|
+
constructor(response, method, url) {
|
|
7950
|
+
const statusText = response.statusText ? ` ${response.statusText}` : '';
|
|
7951
|
+
super(`${method} ${url} failed with HTTP ${response.status}${statusText}`);
|
|
7952
|
+
this.name = 'HTTPError';
|
|
7953
|
+
this.status = response.status;
|
|
7954
|
+
this.response = response;
|
|
7955
|
+
}
|
|
7956
|
+
}
|
|
7899
7957
|
/**
|
|
7958
|
+
* The default `sync`: a JSON request over `fetch()` to the model's `url()`.
|
|
7959
|
+
* Override it (or `Model.sync`/`Collection.sync`) to change how models are
|
|
7960
|
+
* persisted, for instance to batch rapid-fire updates with `setTimeout`, to
|
|
7961
|
+
* talk over WebSockets, or to write to browser storage.
|
|
7962
|
+
*
|
|
7963
|
+
* It honours both contracts a `sync` may use. On a 2xx response the JSON body
|
|
7964
|
+
* (`null` when the body is empty) is passed to `options.success` and the
|
|
7965
|
+
* returned promise resolves with it. On a non-2xx response, a network failure
|
|
7966
|
+
* or an unparsable body, `options.error` receives the error (an `HTTPError`
|
|
7967
|
+
* for the first) and the promise rejects with it.
|
|
7968
|
+
*
|
|
7969
|
+
* `options.data` is sent as query parameters on GET, which may not carry a
|
|
7970
|
+
* body, and as a JSON body otherwise. For create, update and patch the body
|
|
7971
|
+
* defaults to `options.attrs` or the model's `toJSON()`.
|
|
7900
7972
|
* @public
|
|
7901
7973
|
*/
|
|
7902
7974
|
function sync(method, model, options = {}) {
|
|
7975
|
+
const type = methodMap[method];
|
|
7903
7976
|
let data = options.data;
|
|
7904
7977
|
// Ensure that we have the appropriate request data.
|
|
7905
7978
|
if (data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
|
7906
7979
|
data = options.attrs || model.toJSON();
|
|
7907
7980
|
}
|
|
7908
|
-
|
|
7909
|
-
const params = {
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
const url = options.url || result(model, 'url') || urlError();
|
|
7919
|
-
const xhr = fetch(url, params);
|
|
7920
|
-
if (options) {
|
|
7921
|
-
options.xhr = xhr;
|
|
7981
|
+
let url = options.url || result(model, 'url') || urlError();
|
|
7982
|
+
const params = { method: type };
|
|
7983
|
+
if (data != null) {
|
|
7984
|
+
if (type === 'GET') {
|
|
7985
|
+
url += (url.includes('?') ? '&' : '?') + new URLSearchParams(data).toString();
|
|
7986
|
+
}
|
|
7987
|
+
else {
|
|
7988
|
+
params.body = JSON.stringify(data);
|
|
7989
|
+
params.headers = { 'Content-Type': 'application/json' };
|
|
7990
|
+
}
|
|
7922
7991
|
}
|
|
7992
|
+
const xhr = fetch(url, params);
|
|
7993
|
+
options.xhr = xhr;
|
|
7923
7994
|
model.trigger('request', model, xhr, { ...params, xhr });
|
|
7924
|
-
return xhr
|
|
7995
|
+
return Promise.resolve(xhr)
|
|
7996
|
+
.then(async (response) => {
|
|
7997
|
+
if (!response.ok)
|
|
7998
|
+
throw new HTTPError(response, type, url);
|
|
7999
|
+
const text = await response.text();
|
|
8000
|
+
return text ? JSON.parse(text) : null;
|
|
8001
|
+
})
|
|
8002
|
+
.then(
|
|
8003
|
+
// The callbacks run before the returned promise settles, so a caller that
|
|
8004
|
+
// bridges this promise (`Model.fetch`/`save`) sees the outcome as already
|
|
8005
|
+
// reported. A throw from `success` (a `'sync'` listener, say) is not
|
|
8006
|
+
// routed to `error`: it rejects the promise, which is what the bridge
|
|
8007
|
+
// expects once the callbacks have run.
|
|
8008
|
+
(resp) => {
|
|
8009
|
+
options.success?.(resp, options);
|
|
8010
|
+
return resp;
|
|
8011
|
+
}, (e) => {
|
|
8012
|
+
options.error?.(e);
|
|
8013
|
+
throw e;
|
|
8014
|
+
});
|
|
7925
8015
|
}
|
|
7926
8016
|
|
|
7927
8017
|
/**
|
|
@@ -10115,6 +10205,48 @@ var keyBy = createAggregator(function(result, value, key) {
|
|
|
10115
10205
|
baseAssignValue(result, key, value);
|
|
10116
10206
|
});
|
|
10117
10207
|
|
|
10208
|
+
/** Used to compose bitmasks for cloning. */
|
|
10209
|
+
var CLONE_DEEP_FLAG$1 = 1;
|
|
10210
|
+
|
|
10211
|
+
/**
|
|
10212
|
+
* Creates a function that performs a partial deep comparison between a given
|
|
10213
|
+
* object and `source`, returning `true` if the given object has equivalent
|
|
10214
|
+
* property values, else `false`.
|
|
10215
|
+
*
|
|
10216
|
+
* **Note:** The created function is equivalent to `_.isMatch` with `source`
|
|
10217
|
+
* partially applied.
|
|
10218
|
+
*
|
|
10219
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10220
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10221
|
+
* for a list of supported value comparisons.
|
|
10222
|
+
*
|
|
10223
|
+
* **Note:** Multiple values can be checked by combining several matchers
|
|
10224
|
+
* using `_.overSome`
|
|
10225
|
+
*
|
|
10226
|
+
* @static
|
|
10227
|
+
* @memberOf _
|
|
10228
|
+
* @since 3.0.0
|
|
10229
|
+
* @category Util
|
|
10230
|
+
* @param {Object} source The object of property values to match.
|
|
10231
|
+
* @returns {Function} Returns the new spec function.
|
|
10232
|
+
* @example
|
|
10233
|
+
*
|
|
10234
|
+
* var objects = [
|
|
10235
|
+
* { 'a': 1, 'b': 2, 'c': 3 },
|
|
10236
|
+
* { 'a': 4, 'b': 5, 'c': 6 }
|
|
10237
|
+
* ];
|
|
10238
|
+
*
|
|
10239
|
+
* _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
|
|
10240
|
+
* // => [{ 'a': 4, 'b': 5, 'c': 6 }]
|
|
10241
|
+
*
|
|
10242
|
+
* // Checking for several possible values
|
|
10243
|
+
* _.filter(objects, _.overSome([_.matches({ 'a': 1 }), _.matches({ 'a': 4 })]));
|
|
10244
|
+
* // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }]
|
|
10245
|
+
*/
|
|
10246
|
+
function matches(source) {
|
|
10247
|
+
return baseMatches(baseClone(source, CLONE_DEEP_FLAG$1));
|
|
10248
|
+
}
|
|
10249
|
+
|
|
10118
10250
|
/** Built-in value references. */
|
|
10119
10251
|
var spreadableSymbol = Symbol$1 ? Symbol$1.isConcatSpreadable : undefined;
|
|
10120
10252
|
|
|
@@ -10678,53 +10810,36 @@ function isEqual(value, other) {
|
|
|
10678
10810
|
return baseIsEqual(value, other);
|
|
10679
10811
|
}
|
|
10680
10812
|
|
|
10681
|
-
/** Used to compose bitmasks for cloning. */
|
|
10682
|
-
var CLONE_DEEP_FLAG$1 = 1;
|
|
10683
|
-
|
|
10684
10813
|
/**
|
|
10685
|
-
*
|
|
10686
|
-
*
|
|
10687
|
-
*
|
|
10688
|
-
*
|
|
10689
|
-
*
|
|
10814
|
+
* Performs a partial deep comparison between `object` and `source` to
|
|
10815
|
+
* determine if `object` contains equivalent property values.
|
|
10816
|
+
*
|
|
10817
|
+
* **Note:** This method is equivalent to `_.matches` when `source` is
|
|
10818
|
+
* partially applied.
|
|
10819
|
+
*
|
|
10820
|
+
* Partial comparisons will match empty array and empty object `source`
|
|
10821
|
+
* values against any array or object value, respectively. See `_.isEqual`
|
|
10822
|
+
* for a list of supported value comparisons.
|
|
10690
10823
|
*
|
|
10691
10824
|
* @static
|
|
10692
|
-
* @since 4.0.0
|
|
10693
10825
|
* @memberOf _
|
|
10694
|
-
* @
|
|
10695
|
-
* @
|
|
10696
|
-
* @
|
|
10826
|
+
* @since 3.0.0
|
|
10827
|
+
* @category Lang
|
|
10828
|
+
* @param {Object} object The object to inspect.
|
|
10829
|
+
* @param {Object} source The object of property values to match.
|
|
10830
|
+
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
|
|
10697
10831
|
* @example
|
|
10698
10832
|
*
|
|
10699
|
-
* var
|
|
10700
|
-
* { 'user': 'barney', 'age': 36, 'active': true },
|
|
10701
|
-
* { 'user': 'fred', 'age': 40, 'active': false }
|
|
10702
|
-
* ];
|
|
10703
|
-
*
|
|
10704
|
-
* // The `_.matches` iteratee shorthand.
|
|
10705
|
-
* _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
|
|
10706
|
-
* // => [{ 'user': 'barney', 'age': 36, 'active': true }]
|
|
10707
|
-
*
|
|
10708
|
-
* // The `_.matchesProperty` iteratee shorthand.
|
|
10709
|
-
* _.filter(users, _.iteratee(['user', 'fred']));
|
|
10710
|
-
* // => [{ 'user': 'fred', 'age': 40 }]
|
|
10833
|
+
* var object = { 'a': 1, 'b': 2 };
|
|
10711
10834
|
*
|
|
10712
|
-
*
|
|
10713
|
-
*
|
|
10714
|
-
* // => ['barney', 'fred']
|
|
10715
|
-
*
|
|
10716
|
-
* // Create custom iteratee shorthands.
|
|
10717
|
-
* _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
|
|
10718
|
-
* return !_.isRegExp(func) ? iteratee(func) : function(string) {
|
|
10719
|
-
* return func.test(string);
|
|
10720
|
-
* };
|
|
10721
|
-
* });
|
|
10835
|
+
* _.isMatch(object, { 'b': 2 });
|
|
10836
|
+
* // => true
|
|
10722
10837
|
*
|
|
10723
|
-
* _.
|
|
10724
|
-
* // =>
|
|
10838
|
+
* _.isMatch(object, { 'b': 1 });
|
|
10839
|
+
* // => false
|
|
10725
10840
|
*/
|
|
10726
|
-
function
|
|
10727
|
-
return
|
|
10841
|
+
function isMatch(object, source) {
|
|
10842
|
+
return object === source || baseIsMatch(object, source, getMatchData(source));
|
|
10728
10843
|
}
|
|
10729
10844
|
|
|
10730
10845
|
/**
|
|
@@ -11029,7 +11144,47 @@ var pick = flatRest(function(object, paths) {
|
|
|
11029
11144
|
return object == null ? {} : basePick(object, paths);
|
|
11030
11145
|
});
|
|
11031
11146
|
|
|
11032
|
-
|
|
11147
|
+
const INTERNALS = new WeakMap();
|
|
11148
|
+
function internals(model) {
|
|
11149
|
+
let priv = INTERNALS.get(model);
|
|
11150
|
+
if (!priv) {
|
|
11151
|
+
priv = { state: 'constructing' };
|
|
11152
|
+
INTERNALS.set(model, priv);
|
|
11153
|
+
}
|
|
11154
|
+
return priv;
|
|
11155
|
+
}
|
|
11156
|
+
/** Load the model from storage, marking it ready whether or not the read succeeds. */
|
|
11157
|
+
async function hydrate$1(model, priv) {
|
|
11158
|
+
ensureUnloadListener(PersistentStorage);
|
|
11159
|
+
try {
|
|
11160
|
+
await model.fetch({ fromStorage: true, promise: true });
|
|
11161
|
+
}
|
|
11162
|
+
finally {
|
|
11163
|
+
priv.state = 'ready';
|
|
11164
|
+
}
|
|
11165
|
+
}
|
|
11166
|
+
/** Evaluate the model's declared computed properties once, seeding the cache. */
|
|
11167
|
+
function initComputed(model, priv) {
|
|
11168
|
+
const defs = result(model, 'computed');
|
|
11169
|
+
if (!defs || isEmpty(defs))
|
|
11170
|
+
return;
|
|
11171
|
+
priv.computedDefs = defs;
|
|
11172
|
+
priv.computedCache = {};
|
|
11173
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
11174
|
+
priv.computedCache[key] = def.fn(model);
|
|
11175
|
+
}
|
|
11176
|
+
}
|
|
11177
|
+
/** Recompute one computed property, firing `change:key` when its value changed. */
|
|
11178
|
+
function updateComputed(model, priv, key, def, silent, options) {
|
|
11179
|
+
const newVal = def.fn(model);
|
|
11180
|
+
if (!isEqual(priv.computedCache[key], newVal)) {
|
|
11181
|
+
priv.computedCache[key] = newVal;
|
|
11182
|
+
if (!silent) {
|
|
11183
|
+
model.changed[key] = newVal;
|
|
11184
|
+
model.trigger('change:' + key, model, newVal, options);
|
|
11185
|
+
}
|
|
11186
|
+
}
|
|
11187
|
+
}
|
|
11033
11188
|
/**
|
|
11034
11189
|
* @public
|
|
11035
11190
|
* **Models** are the basic data object in the framework --
|
|
@@ -11044,15 +11199,12 @@ class Model extends EventEmitterObject {
|
|
|
11044
11199
|
*/
|
|
11045
11200
|
constructor(attributes, options) {
|
|
11046
11201
|
super();
|
|
11047
|
-
_Model_instances.add(this);
|
|
11048
|
-
_Model_computedCache.set(this, void 0);
|
|
11049
|
-
_Model_computedDefs.set(this, void 0);
|
|
11050
|
-
_Model_state.set(this, 'constructing');
|
|
11051
11202
|
this._changing = false;
|
|
11052
11203
|
this._pending = false;
|
|
11053
11204
|
this._url = '';
|
|
11054
11205
|
this.changed = {};
|
|
11055
11206
|
this.validationError = null;
|
|
11207
|
+
const priv = internals(this);
|
|
11056
11208
|
let attrs = attributes || {};
|
|
11057
11209
|
options = options || {};
|
|
11058
11210
|
this.preinitialize.apply(this, arguments);
|
|
@@ -11065,11 +11217,12 @@ class Model extends EventEmitterObject {
|
|
|
11065
11217
|
const default_attrs = result(this, 'defaults');
|
|
11066
11218
|
attrs = defaults(Object.assign({}, default_attrs, attrs), default_attrs);
|
|
11067
11219
|
this.set(attrs, options);
|
|
11068
|
-
|
|
11220
|
+
initComputed(this, priv);
|
|
11069
11221
|
this.attrs = new Proxy(this.attributes, {
|
|
11070
11222
|
get: (_target, key) => {
|
|
11071
|
-
|
|
11072
|
-
|
|
11223
|
+
const cache = priv.computedCache;
|
|
11224
|
+
if (typeof key !== 'symbol' && cache && key in cache) {
|
|
11225
|
+
return cache[key];
|
|
11073
11226
|
}
|
|
11074
11227
|
return this.attributes[key];
|
|
11075
11228
|
},
|
|
@@ -11087,16 +11240,16 @@ class Model extends EventEmitterObject {
|
|
|
11087
11240
|
// otherwise it's already ready and resolves immediately.
|
|
11088
11241
|
if (this.autoSync) {
|
|
11089
11242
|
if (getStorage(this) && !this.isNew()) {
|
|
11090
|
-
|
|
11091
|
-
this.hydrated =
|
|
11243
|
+
priv.state = 'hydrating';
|
|
11244
|
+
this.hydrated = hydrate$1(this, priv);
|
|
11092
11245
|
}
|
|
11093
11246
|
else {
|
|
11094
|
-
|
|
11247
|
+
priv.state = 'ready';
|
|
11095
11248
|
this.hydrated = Promise.resolve();
|
|
11096
11249
|
}
|
|
11097
11250
|
}
|
|
11098
11251
|
else {
|
|
11099
|
-
|
|
11252
|
+
priv.state = 'ready';
|
|
11100
11253
|
}
|
|
11101
11254
|
}
|
|
11102
11255
|
/**
|
|
@@ -11195,8 +11348,9 @@ class Model extends EventEmitterObject {
|
|
|
11195
11348
|
* Get the value of an attribute, or a computed property.
|
|
11196
11349
|
*/
|
|
11197
11350
|
get(attr) {
|
|
11198
|
-
|
|
11199
|
-
|
|
11351
|
+
const cache = internals(this).computedCache;
|
|
11352
|
+
if (cache && attr in cache) {
|
|
11353
|
+
return cache[attr];
|
|
11200
11354
|
}
|
|
11201
11355
|
return this.attributes[attr];
|
|
11202
11356
|
}
|
|
@@ -11238,10 +11392,20 @@ class Model extends EventEmitterObject {
|
|
|
11238
11392
|
return this.get(attr) != null;
|
|
11239
11393
|
}
|
|
11240
11394
|
/**
|
|
11241
|
-
*
|
|
11395
|
+
* Returns `true` if the model's attributes satisfy `attrs`.
|
|
11396
|
+
*
|
|
11397
|
+
* An attributes object is compared with lodash's `isMatch`: a partial, deep
|
|
11398
|
+
* comparison, so `{ form: { type: 'hidden' } }` matches a `form` attribute
|
|
11399
|
+
* that has `type: 'hidden'` alongside other keys. A function is called with
|
|
11400
|
+
* the attributes and its result coerced to a boolean.
|
|
11401
|
+
*
|
|
11402
|
+
* To scan a collection, prefer `Collection#find`, `filter` or `where`. They
|
|
11403
|
+
* build their matcher once per scan instead of once per model.
|
|
11242
11404
|
*/
|
|
11243
11405
|
matches(attrs) {
|
|
11244
|
-
|
|
11406
|
+
if (typeof attrs === 'function')
|
|
11407
|
+
return !!attrs(this.attributes);
|
|
11408
|
+
return isMatch(this.attributes, attrs);
|
|
11245
11409
|
}
|
|
11246
11410
|
/**
|
|
11247
11411
|
* Set a hash of model attributes on the object, firing `"change"`. This is
|
|
@@ -11262,9 +11426,10 @@ class Model extends EventEmitterObject {
|
|
|
11262
11426
|
attrs[key] = val;
|
|
11263
11427
|
}
|
|
11264
11428
|
options = options || {};
|
|
11429
|
+
const priv = internals(this);
|
|
11265
11430
|
// Guard must come after arg normalization: when called as set(obj, opts),
|
|
11266
11431
|
// fromStorage lives in `val` (second arg), not `options` (third arg).
|
|
11267
|
-
if (this.autoSync &&
|
|
11432
|
+
if (this.autoSync && priv.state === 'hydrating' && !options.fromStorage) {
|
|
11268
11433
|
throw new Error(`Skeletor: set() called on an autoSync model before hydration resolved. Await model.hydrated first.`);
|
|
11269
11434
|
}
|
|
11270
11435
|
// Run validation.
|
|
@@ -11308,10 +11473,10 @@ class Model extends EventEmitterObject {
|
|
|
11308
11473
|
}
|
|
11309
11474
|
}
|
|
11310
11475
|
// Recalculate computed properties whose deps changed.
|
|
11311
|
-
if (changes.length &&
|
|
11312
|
-
for (const [key, def] of Object.entries(
|
|
11476
|
+
if (changes.length && priv.computedDefs) {
|
|
11477
|
+
for (const [key, def] of Object.entries(priv.computedDefs)) {
|
|
11313
11478
|
if (def.deps.some((dep) => changes.includes(dep))) {
|
|
11314
|
-
|
|
11479
|
+
updateComputed(this, priv, key, def, silent, options);
|
|
11315
11480
|
}
|
|
11316
11481
|
}
|
|
11317
11482
|
}
|
|
@@ -11331,13 +11496,13 @@ class Model extends EventEmitterObject {
|
|
|
11331
11496
|
// Auto-save: schedule a debounced write when autoSync is on and there
|
|
11332
11497
|
// were actual changes that originated from application code (not a
|
|
11333
11498
|
// storage read or an explicit noAutoSave call).
|
|
11334
|
-
if (
|
|
11499
|
+
if (priv.state === 'ready' &&
|
|
11335
11500
|
this.autoSync &&
|
|
11336
11501
|
getStorage(this) &&
|
|
11337
11502
|
changes.length &&
|
|
11338
11503
|
!options.fromStorage &&
|
|
11339
11504
|
!options.noAutoSave) {
|
|
11340
|
-
scheduleAutoSave(this, () =>
|
|
11505
|
+
scheduleAutoSave(this, () => this.save(null, { noAutoSave: true }), this.autoSyncDelay);
|
|
11341
11506
|
}
|
|
11342
11507
|
return this;
|
|
11343
11508
|
}
|
|
@@ -11414,47 +11579,25 @@ class Model extends EventEmitterObject {
|
|
|
11414
11579
|
options = Object.assign({ parse: true }, options);
|
|
11415
11580
|
const success = options.success;
|
|
11416
11581
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
11417
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
11418
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
11419
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
11420
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
11421
|
-
// it's reliable by the time those handlers run.
|
|
11422
|
-
let settled = false;
|
|
11423
11582
|
options.success = (resp) => {
|
|
11424
|
-
settled = true;
|
|
11425
11583
|
const serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11426
11584
|
// fromStorage prevents the set() from triggering auto-save on reads
|
|
11427
|
-
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true })))
|
|
11585
|
+
if (!this.set(serverAttrs, Object.assign({}, options, { fromStorage: true }))) {
|
|
11586
|
+
// The fetched attributes failed validation (`'invalid'` has fired).
|
|
11587
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11588
|
+
promise && promise.reject(this.validationError);
|
|
11428
11589
|
return false;
|
|
11590
|
+
}
|
|
11429
11591
|
if (success)
|
|
11430
11592
|
success.call(options.context, this, resp, options);
|
|
11431
11593
|
this.trigger('sync', this, resp, options);
|
|
11432
11594
|
promise && promise.resolve(resp);
|
|
11433
11595
|
};
|
|
11434
11596
|
wrapError(this, options, promise);
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
onError(resp);
|
|
11440
|
-
};
|
|
11441
|
-
}
|
|
11442
|
-
const result = this.sync('read', this, options);
|
|
11443
|
-
if (promise) {
|
|
11444
|
-
Promise.resolve(result).then(
|
|
11445
|
-
// A sync that resolves its promise without invoking options.success:
|
|
11446
|
-
// treat the resolved value as the response, so the model is still
|
|
11447
|
-
// merged and the promise settles instead of hanging.
|
|
11448
|
-
(value) => {
|
|
11449
|
-
if (!settled)
|
|
11450
|
-
options.success(value);
|
|
11451
|
-
},
|
|
11452
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
11453
|
-
// twice is a harmless no-op.
|
|
11454
|
-
(e) => promise.reject(e));
|
|
11455
|
-
return promise;
|
|
11456
|
-
}
|
|
11457
|
-
return result;
|
|
11597
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11598
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11599
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
11600
|
+
return promise ?? result;
|
|
11458
11601
|
}
|
|
11459
11602
|
/**
|
|
11460
11603
|
* Set a hash of model attributes, and sync the model to the server.
|
|
@@ -11475,16 +11618,19 @@ class Model extends EventEmitterObject {
|
|
|
11475
11618
|
options = Object.assign({ validate: true, parse: true }, options);
|
|
11476
11619
|
const wait = options.wait;
|
|
11477
11620
|
const return_promise = options.promise;
|
|
11478
|
-
const promise = return_promise
|
|
11621
|
+
const promise = return_promise ? getResolveablePromise() : undefined;
|
|
11479
11622
|
// If we're not waiting and attributes exist, save acts as
|
|
11480
11623
|
// `set(attr).save(null, opts)` with validation. Otherwise, check if
|
|
11481
11624
|
// the model will be valid when the attributes, if any, are set.
|
|
11482
|
-
|
|
11483
|
-
|
|
11625
|
+
const valid = attrs && !wait ? !!this.set(attrs, options) : this._validate(attrs || {}, options);
|
|
11626
|
+
if (!valid) {
|
|
11627
|
+
// Nothing is synced (`'invalid'` has fired). With `promise`, hand back a
|
|
11628
|
+
// rejected promise rather than `false`, so the promise contract holds
|
|
11629
|
+
// for every outcome.
|
|
11630
|
+
if (!promise)
|
|
11484
11631
|
return false;
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
return false;
|
|
11632
|
+
promise.reject(this.validationError);
|
|
11633
|
+
return promise;
|
|
11488
11634
|
}
|
|
11489
11635
|
// After a successful server-side save, the client is (optionally)
|
|
11490
11636
|
// updated with the server-side state.
|
|
@@ -11497,8 +11643,12 @@ class Model extends EventEmitterObject {
|
|
|
11497
11643
|
let serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
11498
11644
|
if (wait)
|
|
11499
11645
|
serverAttrs = Object.assign({}, attrs, serverAttrs);
|
|
11500
|
-
if (serverAttrs && !this.set(serverAttrs, options))
|
|
11646
|
+
if (serverAttrs && !this.set(serverAttrs, options)) {
|
|
11647
|
+
// The server attributes failed validation (`'invalid'` has fired).
|
|
11648
|
+
// Settle the promise with the error rather than leave it pending.
|
|
11649
|
+
return_promise && promise.reject(this.validationError);
|
|
11501
11650
|
return false;
|
|
11651
|
+
}
|
|
11502
11652
|
if (success)
|
|
11503
11653
|
success.call(options.context, this, resp, options);
|
|
11504
11654
|
this.trigger('sync', this, resp, options);
|
|
@@ -11515,15 +11665,12 @@ class Model extends EventEmitterObject {
|
|
|
11515
11665
|
const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
|
11516
11666
|
if (method === 'patch' && !options.attrs)
|
|
11517
11667
|
options.attrs = attrs;
|
|
11518
|
-
|
|
11668
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
11669
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
11670
|
+
const xhr = bridgeSyncToPromise(options, promise, () => this.sync(method, this, options));
|
|
11519
11671
|
// Restore attributes.
|
|
11520
11672
|
this.attributes = attributes;
|
|
11521
|
-
|
|
11522
|
-
return promise;
|
|
11523
|
-
}
|
|
11524
|
-
else {
|
|
11525
|
-
return xhr;
|
|
11526
|
-
}
|
|
11673
|
+
return promise ?? xhr;
|
|
11527
11674
|
}
|
|
11528
11675
|
/**
|
|
11529
11676
|
* Destroy this model on the server if it was already persisted.
|
|
@@ -11636,40 +11783,40 @@ class Model extends EventEmitterObject {
|
|
|
11636
11783
|
return false;
|
|
11637
11784
|
}
|
|
11638
11785
|
}
|
|
11639
|
-
_Model_computedCache = new WeakMap(), _Model_computedDefs = new WeakMap(), _Model_state = new WeakMap(), _Model_instances = new WeakSet(), _Model_hydrate = async function _Model_hydrate() {
|
|
11640
|
-
ensureUnloadListener(PersistentStorage);
|
|
11641
|
-
try {
|
|
11642
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
11643
|
-
}
|
|
11644
|
-
finally {
|
|
11645
|
-
__classPrivateFieldSet(this, _Model_state, 'ready', "f");
|
|
11646
|
-
}
|
|
11647
|
-
}, _Model_doAutoSave = function _Model_doAutoSave() {
|
|
11648
|
-
return this.save(null, { noAutoSave: true });
|
|
11649
|
-
}, _Model_updateComputed = function _Model_updateComputed(key, def, silent, options) {
|
|
11650
|
-
const newVal = def.fn(this);
|
|
11651
|
-
if (!isEqual(__classPrivateFieldGet(this, _Model_computedCache, "f")[key], newVal)) {
|
|
11652
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = newVal;
|
|
11653
|
-
if (!silent) {
|
|
11654
|
-
this.changed[key] = newVal;
|
|
11655
|
-
this.trigger('change:' + key, this, newVal, options);
|
|
11656
|
-
}
|
|
11657
|
-
}
|
|
11658
|
-
}, _Model_initComputed = function _Model_initComputed() {
|
|
11659
|
-
const defs = result(this, 'computed');
|
|
11660
|
-
if (!defs || isEmpty(defs))
|
|
11661
|
-
return;
|
|
11662
|
-
__classPrivateFieldSet(this, _Model_computedDefs, defs, "f");
|
|
11663
|
-
__classPrivateFieldSet(this, _Model_computedCache, {}, "f");
|
|
11664
|
-
for (const [key, def] of Object.entries(defs)) {
|
|
11665
|
-
__classPrivateFieldGet(this, _Model_computedCache, "f")[key] = def.fn(this);
|
|
11666
|
-
}
|
|
11667
|
-
};
|
|
11668
11786
|
|
|
11669
|
-
var
|
|
11787
|
+
var _a;
|
|
11670
11788
|
// Default options for `Collection#set`.
|
|
11671
11789
|
const setOptions = { add: true, remove: true, merge: true };
|
|
11672
11790
|
const addOptions = { add: true, remove: false };
|
|
11791
|
+
/**
|
|
11792
|
+
* Normalizes the predicate forms accepted by the query methods into one
|
|
11793
|
+
* `(model) => value` function. A function is used as-is, a string reads that
|
|
11794
|
+
* attribute, and an attributes object becomes a lodash `matches` matcher.
|
|
11795
|
+
*
|
|
11796
|
+
* The matcher is built here, once per scan. Calling `Model#matches` per model
|
|
11797
|
+
* instead rebuilds the match data for every model visited, which made
|
|
11798
|
+
* `findWhere` an order of magnitude slower than a hand-written predicate.
|
|
11799
|
+
*/
|
|
11800
|
+
function toModelPredicate(pred) {
|
|
11801
|
+
if (isFunction(pred))
|
|
11802
|
+
return pred;
|
|
11803
|
+
if (isString(pred))
|
|
11804
|
+
return (m) => m.get(pred);
|
|
11805
|
+
const matcher = matches(pred);
|
|
11806
|
+
return (m) => matcher(m.attributes);
|
|
11807
|
+
}
|
|
11808
|
+
/**
|
|
11809
|
+
* Load the collection's members from storage. A module-level function rather
|
|
11810
|
+
* than a `#private` method so the published declarations carry no private
|
|
11811
|
+
* members, which TypeScript 6 rejects in anonymously extended classes (TS4094).
|
|
11812
|
+
*/
|
|
11813
|
+
async function hydrate(collection) {
|
|
11814
|
+
ensureUnloadListener(PersistentStorage);
|
|
11815
|
+
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
11816
|
+
// missing model record resolves to null, so hydration resolves rather
|
|
11817
|
+
// than rejecting.
|
|
11818
|
+
await collection.fetch({ fromStorage: true, promise: true });
|
|
11819
|
+
}
|
|
11673
11820
|
/**
|
|
11674
11821
|
* @public
|
|
11675
11822
|
* If models tend to represent a single row of data, a Collection is
|
|
@@ -11687,7 +11834,6 @@ class Collection extends EventEmitterObject {
|
|
|
11687
11834
|
*/
|
|
11688
11835
|
constructor(models, options) {
|
|
11689
11836
|
super();
|
|
11690
|
-
_Collection_instances.add(this);
|
|
11691
11837
|
this._url = '';
|
|
11692
11838
|
/**
|
|
11693
11839
|
* @public
|
|
@@ -11709,7 +11855,7 @@ class Collection extends EventEmitterObject {
|
|
|
11709
11855
|
// uniformly `await collection.hydrated`; it resolves immediately when
|
|
11710
11856
|
// there's no storage to hydrate from.
|
|
11711
11857
|
if (this.autoSync) {
|
|
11712
|
-
this.hydrated = getStorage(this) ?
|
|
11858
|
+
this.hydrated = getStorage(this) ? hydrate(this) : Promise.resolve();
|
|
11713
11859
|
}
|
|
11714
11860
|
}
|
|
11715
11861
|
get comparator() {
|
|
@@ -11998,17 +12144,13 @@ class Collection extends EventEmitterObject {
|
|
|
11998
12144
|
return this.models.slice(start, end);
|
|
11999
12145
|
}
|
|
12000
12146
|
filter(callback, thisArg) {
|
|
12001
|
-
return this.models.filter(
|
|
12002
|
-
? callback
|
|
12003
|
-
: (m) => m.matches(callback), thisArg);
|
|
12147
|
+
return this.models.filter(toModelPredicate(callback), thisArg);
|
|
12004
12148
|
}
|
|
12005
12149
|
every(pred) {
|
|
12006
12150
|
if (isFunction(pred)) {
|
|
12007
|
-
return this.models.
|
|
12008
|
-
}
|
|
12009
|
-
else {
|
|
12010
|
-
return this.models.every((m) => m.matches(pred));
|
|
12151
|
+
return this.models.every((m) => pred(m.attributes));
|
|
12011
12152
|
}
|
|
12153
|
+
return this.models.every(toModelPredicate(pred));
|
|
12012
12154
|
}
|
|
12013
12155
|
difference(values) {
|
|
12014
12156
|
return this.models.filter((m) => !values.includes(m));
|
|
@@ -12024,16 +12166,12 @@ class Collection extends EventEmitterObject {
|
|
|
12024
12166
|
}
|
|
12025
12167
|
some(pred) {
|
|
12026
12168
|
if (isFunction(pred)) {
|
|
12027
|
-
return this.models.
|
|
12028
|
-
}
|
|
12029
|
-
else {
|
|
12030
|
-
return this.models.some((m) => m.matches(pred));
|
|
12169
|
+
return this.models.some((m) => pred(m.attributes));
|
|
12031
12170
|
}
|
|
12171
|
+
return this.models.some(toModelPredicate(pred));
|
|
12032
12172
|
}
|
|
12033
12173
|
sortBy(iteratee) {
|
|
12034
|
-
return sortBy(this.models,
|
|
12035
|
-
? iteratee
|
|
12036
|
-
: (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)));
|
|
12174
|
+
return sortBy(this.models, toModelPredicate(iteratee));
|
|
12037
12175
|
}
|
|
12038
12176
|
isEmpty() {
|
|
12039
12177
|
return !this.models.length;
|
|
@@ -12054,26 +12192,22 @@ class Collection extends EventEmitterObject {
|
|
|
12054
12192
|
return this.models.length;
|
|
12055
12193
|
}
|
|
12056
12194
|
countBy(f) {
|
|
12057
|
-
return countBy(this.models,
|
|
12195
|
+
return countBy(this.models, toModelPredicate(f));
|
|
12058
12196
|
}
|
|
12059
12197
|
groupBy(pred) {
|
|
12060
|
-
return groupBy(this.models,
|
|
12198
|
+
return groupBy(this.models, toModelPredicate(pred));
|
|
12061
12199
|
}
|
|
12062
12200
|
indexOf(model, fromIndex) {
|
|
12063
12201
|
return this.models.indexOf(model, fromIndex);
|
|
12064
12202
|
}
|
|
12065
12203
|
findLastIndex(pred, fromIndex) {
|
|
12066
|
-
return this.models.findLastIndex(
|
|
12067
|
-
? pred
|
|
12068
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)), fromIndex);
|
|
12204
|
+
return this.models.findLastIndex(toModelPredicate(pred), fromIndex);
|
|
12069
12205
|
}
|
|
12070
12206
|
lastIndexOf(model, fromIndex) {
|
|
12071
12207
|
return this.models.lastIndexOf(model, fromIndex);
|
|
12072
12208
|
}
|
|
12073
12209
|
findIndex(pred) {
|
|
12074
|
-
return this.models.findIndex(
|
|
12075
|
-
? pred
|
|
12076
|
-
: (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
|
|
12210
|
+
return this.models.findIndex(toModelPredicate(pred));
|
|
12077
12211
|
}
|
|
12078
12212
|
last() {
|
|
12079
12213
|
const length = this.models == null ? 0 : this.models.length;
|
|
@@ -12086,9 +12220,7 @@ class Collection extends EventEmitterObject {
|
|
|
12086
12220
|
return this.head();
|
|
12087
12221
|
}
|
|
12088
12222
|
map(cb, thisArg) {
|
|
12089
|
-
return this.models.map(
|
|
12090
|
-
? cb
|
|
12091
|
-
: (m) => (isString(cb) ? m.get(cb) : m.matches(cb)), thisArg);
|
|
12223
|
+
return this.models.map(toModelPredicate(cb), thisArg);
|
|
12092
12224
|
}
|
|
12093
12225
|
reduce(callback, initialValue) {
|
|
12094
12226
|
return this.models.reduce(callback, initialValue || this.models[0]);
|
|
@@ -12139,10 +12271,7 @@ class Collection extends EventEmitterObject {
|
|
|
12139
12271
|
return this.where(attrs, true);
|
|
12140
12272
|
}
|
|
12141
12273
|
find(predicate, fromIndex) {
|
|
12142
|
-
|
|
12143
|
-
? predicate
|
|
12144
|
-
: (m) => m.matches(predicate);
|
|
12145
|
-
return this.models.find(pred, fromIndex);
|
|
12274
|
+
return this.models.find(toModelPredicate(predicate), fromIndex);
|
|
12146
12275
|
}
|
|
12147
12276
|
/**
|
|
12148
12277
|
* Force the collection to re-sort itself. You don't need to call this under
|
|
@@ -12185,46 +12314,22 @@ class Collection extends EventEmitterObject {
|
|
|
12185
12314
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
12186
12315
|
const collection = this;
|
|
12187
12316
|
const promise = options.promise ? getResolveablePromise() : undefined;
|
|
12188
|
-
// Tracks whether sync invoked one of the callbacks. A sync may instead
|
|
12189
|
-
// settle its returned promise; the `.then`/`.catch` below bridges that to
|
|
12190
|
-
// the callbacks so the fetch promise never hangs. The flag is set
|
|
12191
|
-
// synchronously inside each callback (before sync's promise resolves), so
|
|
12192
|
-
// it's reliable by the time those handlers run.
|
|
12193
|
-
let settled = false;
|
|
12194
12317
|
options.success = function (resp) {
|
|
12195
|
-
settled = true;
|
|
12196
12318
|
const method = options.reset ? 'reset' : 'set';
|
|
12197
12319
|
// fromStorage prevents individual model set() calls from triggering auto-save on reads
|
|
12198
12320
|
collection[method](resp, Object.assign({}, options, { fromStorage: true }));
|
|
12199
12321
|
if (success)
|
|
12200
12322
|
success.call(options.context, collection, resp, options);
|
|
12201
|
-
|
|
12323
|
+
// Fire 'sync' before resolving, as `Model.fetch`/`save` do, so a throwing
|
|
12324
|
+
// listener rejects the promise instead of being lost.
|
|
12202
12325
|
collection.trigger('sync', collection, resp, options);
|
|
12326
|
+
promise && promise.resolve();
|
|
12203
12327
|
};
|
|
12204
12328
|
wrapError(this, options, promise);
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
onError(resp);
|
|
12210
|
-
};
|
|
12211
|
-
}
|
|
12212
|
-
const result = this.sync('read', this, options);
|
|
12213
|
-
if (promise) {
|
|
12214
|
-
Promise.resolve(result).then(
|
|
12215
|
-
// A sync that resolves its promise without invoking options.success:
|
|
12216
|
-
// treat the resolved value as the response, so the collection is still
|
|
12217
|
-
// populated and the promise settles instead of hanging.
|
|
12218
|
-
(value) => {
|
|
12219
|
-
if (!settled)
|
|
12220
|
-
options.success(value);
|
|
12221
|
-
},
|
|
12222
|
-
// A sync that rejects instead of invoking options.error; settling
|
|
12223
|
-
// twice is a harmless no-op.
|
|
12224
|
-
(e) => promise.reject(e));
|
|
12225
|
-
return promise;
|
|
12226
|
-
}
|
|
12227
|
-
return result;
|
|
12329
|
+
// With `promise`, the sync's outcome is bridged to it however the sync
|
|
12330
|
+
// reports it: through the callbacks, or through the promise it returns.
|
|
12331
|
+
const result = bridgeSyncToPromise(options, promise, () => this.sync('read', this, options));
|
|
12332
|
+
return promise ?? result;
|
|
12228
12333
|
}
|
|
12229
12334
|
/**
|
|
12230
12335
|
* Create a new instance of a model in this collection. Add the model to the
|
|
@@ -12236,9 +12341,22 @@ class Collection extends EventEmitterObject {
|
|
|
12236
12341
|
const wait = options.wait;
|
|
12237
12342
|
const return_promise = options.promise;
|
|
12238
12343
|
const promise = return_promise && getResolveablePromise();
|
|
12344
|
+
// `_prepareModel` reports attributes that fail validation by firing
|
|
12345
|
+
// `'invalid'` and returning null. With `promise`, capture that error so a
|
|
12346
|
+
// rejected promise is handed back rather than `null`.
|
|
12347
|
+
let validationError;
|
|
12348
|
+
const captureInvalid = (_c, err) => (validationError = err);
|
|
12349
|
+
if (return_promise)
|
|
12350
|
+
this.on('invalid', captureInvalid);
|
|
12239
12351
|
const preparedModel = this._prepareModel(model, options);
|
|
12240
|
-
if (
|
|
12241
|
-
|
|
12352
|
+
if (return_promise)
|
|
12353
|
+
this.off('invalid', captureInvalid);
|
|
12354
|
+
if (!preparedModel) {
|
|
12355
|
+
if (!return_promise)
|
|
12356
|
+
return null;
|
|
12357
|
+
promise.reject(validationError);
|
|
12358
|
+
return promise;
|
|
12359
|
+
}
|
|
12242
12360
|
if (!wait)
|
|
12243
12361
|
this.add(preparedModel, options);
|
|
12244
12362
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
@@ -12260,8 +12378,15 @@ class Collection extends EventEmitterObject {
|
|
|
12260
12378
|
error && error.call(options.context, model, e, options);
|
|
12261
12379
|
return_promise && promise.reject(e);
|
|
12262
12380
|
};
|
|
12263
|
-
|
|
12381
|
+
// `options.promise` is passed through, so `save` installs its own bridge
|
|
12382
|
+
// from the sync's returned promise to its callbacks: a sync that resolves
|
|
12383
|
+
// without calling them still reaches the wired `options.success` above,
|
|
12384
|
+
// which resolves our promise with the model. Only a rejection that bypassed
|
|
12385
|
+
// the callbacks (a rejecting sync, or a throwing `'sync'`/`'error'`
|
|
12386
|
+
// listener) still needs bridging to ours.
|
|
12387
|
+
const result = preparedModel.save(null, options);
|
|
12264
12388
|
if (return_promise) {
|
|
12389
|
+
Promise.resolve(result).catch((e) => promise.reject(e));
|
|
12265
12390
|
return promise;
|
|
12266
12391
|
}
|
|
12267
12392
|
else {
|
|
@@ -12413,13 +12538,7 @@ class Collection extends EventEmitterObject {
|
|
|
12413
12538
|
this.trigger.apply(this, arguments);
|
|
12414
12539
|
}
|
|
12415
12540
|
}
|
|
12416
|
-
|
|
12417
|
-
ensureUnloadListener(PersistentStorage);
|
|
12418
|
-
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
12419
|
-
// missing model record resolves to null, so hydration resolves rather
|
|
12420
|
-
// than rejecting.
|
|
12421
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
12422
|
-
}, _a = Symbol.iterator;
|
|
12541
|
+
_a = Symbol.iterator;
|
|
12423
12542
|
// This "enum" defines the three possible kinds of values which can be emitted
|
|
12424
12543
|
// by a CollectionIterator that correspond to the values(), keys() and entries()
|
|
12425
12544
|
// methods on Collection, respectively.
|
|
@@ -12487,6 +12606,7 @@ const skeletor = {
|
|
|
12487
12606
|
EventEmitter,
|
|
12488
12607
|
Model,
|
|
12489
12608
|
sync,
|
|
12609
|
+
HTTPError,
|
|
12490
12610
|
};
|
|
12491
12611
|
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
|
|
12492
12612
|
// We use `self` instead of `window` for `WebWorker` support.
|
|
@@ -12524,5 +12644,5 @@ const BrowserStorage = PersistentStorage;
|
|
|
12524
12644
|
// their bundles entirely.
|
|
12525
12645
|
PersistentStorage.nodeStorage = NodeSQLiteStorage;
|
|
12526
12646
|
|
|
12527
|
-
export { BrowserStorage, Collection, EventEmitter, Model, NodeSQLiteStorage, PersistentStorage, skeletor as default, getStorage, noConflict, sync };
|
|
12647
|
+
export { BrowserStorage, Collection, EventEmitter, HTTPError, Model, NodeSQLiteStorage, PersistentStorage, skeletor as default, getStorage, noConflict, sync };
|
|
12528
12648
|
//# sourceMappingURL=skeletor.node.mjs.map
|