@wordpress/data 10.25.0 → 10.26.0
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/CHANGELOG.md +6 -0
- package/build/redux-store/index.js +175 -120
- package/build/redux-store/index.js.map +1 -1
- package/build-module/redux-store/index.js +175 -120
- package/build-module/redux-store/index.js.map +1 -1
- package/build-types/redux-store/index.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/redux-store/index.js +229 -124
- package/src/redux-store/test/index.js +4 -3
- package/src/test/privateAPIs.js +46 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 10.26.0 (2025-06-25)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Add support for private selectors to `resolveSelect` and `suspendSelect` ([#52036](https://github.com/WordPress/gutenberg/pull/52036)).
|
|
10
|
+
|
|
5
11
|
## 10.25.0 (2025-06-04)
|
|
6
12
|
|
|
7
13
|
## 10.24.0 (2025-05-22)
|
|
@@ -106,19 +106,28 @@ function createResolversCache() {
|
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
|
-
function createBindingCache(
|
|
109
|
+
function createBindingCache(getItem, bindItem) {
|
|
110
110
|
const cache = new WeakMap();
|
|
111
111
|
return {
|
|
112
|
-
get(
|
|
112
|
+
get(itemName) {
|
|
113
|
+
const item = getItem(itemName);
|
|
114
|
+
if (!item) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
113
117
|
let boundItem = cache.get(item);
|
|
114
118
|
if (!boundItem) {
|
|
115
|
-
boundItem =
|
|
119
|
+
boundItem = bindItem(item, itemName);
|
|
116
120
|
cache.set(item, boundItem);
|
|
117
121
|
}
|
|
118
122
|
return boundItem;
|
|
119
123
|
}
|
|
120
124
|
};
|
|
121
125
|
}
|
|
126
|
+
function createPrivateProxy(publicItems, privateItems) {
|
|
127
|
+
return new Proxy(publicItems, {
|
|
128
|
+
get: (target, itemName) => privateItems.get(itemName) || Reflect.get(target, itemName)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
122
131
|
|
|
123
132
|
/**
|
|
124
133
|
* Creates a data store descriptor for the provided Redux store configuration containing
|
|
@@ -176,42 +185,68 @@ function createReduxStore(key, options) {
|
|
|
176
185
|
*/
|
|
177
186
|
const listeners = new Set();
|
|
178
187
|
const reducer = options.reducer;
|
|
188
|
+
|
|
189
|
+
// Object that every thunk function receives as the first argument. It contains the
|
|
190
|
+
// `registry`, `dispatch`, `select` and `resolveSelect` fields. Some of them are
|
|
191
|
+
// constructed as getters to avoid circular dependencies.
|
|
179
192
|
const thunkArgs = {
|
|
180
193
|
registry,
|
|
181
194
|
get dispatch() {
|
|
182
|
-
return
|
|
195
|
+
return thunkDispatch;
|
|
183
196
|
},
|
|
184
197
|
get select() {
|
|
185
|
-
return
|
|
198
|
+
return thunkSelect;
|
|
186
199
|
},
|
|
187
200
|
get resolveSelect() {
|
|
188
|
-
return
|
|
201
|
+
return resolveSelectors;
|
|
189
202
|
}
|
|
190
203
|
};
|
|
191
204
|
const store = instantiateReduxStore(key, options, registry, thunkArgs);
|
|
205
|
+
|
|
192
206
|
// Expose the private registration functions on the store
|
|
193
207
|
// so they can be copied to a sub registry in registry.js.
|
|
194
208
|
(0, _lockUnlock.lock)(store, privateRegistrationFunctions);
|
|
195
209
|
const resolversCache = createResolversCache();
|
|
210
|
+
|
|
211
|
+
// Binds an action creator (`action`) to the `store`, making it a callable function.
|
|
212
|
+
// These are the functions that are returned by `useDispatch`, for example.
|
|
213
|
+
// It always returns a `Promise`, although actions are not always async. That's an
|
|
214
|
+
// unfortunate backward compatibility measure.
|
|
196
215
|
function bindAction(action) {
|
|
197
216
|
return (...args) => Promise.resolve(store.dispatch(action(...args)));
|
|
198
217
|
}
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
* Object with all public actions, both metadata and store actions.
|
|
221
|
+
*/
|
|
199
222
|
const actions = {
|
|
200
223
|
...mapValues(metadataActions, bindAction),
|
|
201
224
|
...mapValues(options.actions, bindAction)
|
|
202
225
|
};
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
226
|
+
|
|
227
|
+
// Object with both public and private actions. Private actions are accessed through a proxy,
|
|
228
|
+
// which looks them up in real time on the `privateActions` object. That's because private
|
|
229
|
+
// actions can be registered at any time with `registerPrivateActions`. Also once a private
|
|
230
|
+
// action creator is bound to the store, it is cached to give it a stable identity.
|
|
231
|
+
const allActions = createPrivateProxy(actions, createBindingCache(name => privateActions[name], bindAction));
|
|
232
|
+
|
|
233
|
+
// An object that implements the `dispatch` object that is passed to thunk functions.
|
|
234
|
+
// It is callable (`dispatch( action )`) and also has methods (`dispatch.foo()`) that
|
|
235
|
+
// correspond to bound registered actions, both public and private. Implemented with the proxy
|
|
236
|
+
// `get` method, delegating to `allActions`.
|
|
237
|
+
const thunkDispatch = new Proxy(action => store.dispatch(action), {
|
|
238
|
+
get: (target, name) => allActions[name]
|
|
212
239
|
});
|
|
240
|
+
|
|
241
|
+
// To the public `actions` object, add the "locked" `allActions` object. When used,
|
|
242
|
+
// `unlock( actions )` will return `allActions`, implementing a way how to get at the private actions.
|
|
213
243
|
(0, _lockUnlock.lock)(actions, allActions);
|
|
214
|
-
|
|
244
|
+
|
|
245
|
+
// If we have selector resolvers, convert them to a normalized form.
|
|
246
|
+
const resolvers = options.resolvers ? mapValues(options.resolvers, mapResolver) : {};
|
|
247
|
+
|
|
248
|
+
// Bind a selector to the store. Call the selector with the current state, correct registry,
|
|
249
|
+
// and if there is a resolver, attach the resolver logic to the selector.
|
|
215
250
|
function bindSelector(selector, selectorName) {
|
|
216
251
|
if (selector.isRegistrySelector) {
|
|
217
252
|
selector.registry = registry;
|
|
@@ -219,8 +254,7 @@ function createReduxStore(key, options) {
|
|
|
219
254
|
const boundSelector = (...args) => {
|
|
220
255
|
args = normalize(selector, args);
|
|
221
256
|
const state = store.__unstableOriginalGetState();
|
|
222
|
-
// Before calling the selector, switch to the correct
|
|
223
|
-
// registry.
|
|
257
|
+
// Before calling the selector, switch to the correct registry.
|
|
224
258
|
if (selector.isRegistrySelector) {
|
|
225
259
|
selector.registry = registry;
|
|
226
260
|
}
|
|
@@ -236,47 +270,84 @@ function createReduxStore(key, options) {
|
|
|
236
270
|
boundSelector.hasResolver = false;
|
|
237
271
|
return boundSelector;
|
|
238
272
|
}
|
|
239
|
-
return mapSelectorWithResolver(boundSelector, selectorName, resolver, store, resolversCache);
|
|
273
|
+
return mapSelectorWithResolver(boundSelector, selectorName, resolver, store, resolversCache, boundMetadataSelectors);
|
|
240
274
|
}
|
|
241
|
-
function bindMetadataSelector(metaDataSelector) {
|
|
242
|
-
const boundSelector = (...args) => {
|
|
243
|
-
const state = store.__unstableOriginalGetState();
|
|
244
|
-
const originalSelectorName = args && args[0];
|
|
245
|
-
const originalSelectorArgs = args && args[1];
|
|
246
|
-
const targetSelector = options?.selectors?.[originalSelectorName];
|
|
247
275
|
|
|
276
|
+
// Metadata selectors are bound differently: different state (`state.metadata`), no resolvers,
|
|
277
|
+
// normalization depending on the target selector.
|
|
278
|
+
function bindMetadataSelector(metaDataSelector) {
|
|
279
|
+
const boundSelector = (selectorName, selectorArgs, ...args) => {
|
|
248
280
|
// Normalize the arguments passed to the target selector.
|
|
249
|
-
if (
|
|
250
|
-
|
|
281
|
+
if (selectorName) {
|
|
282
|
+
const targetSelector = options.selectors?.[selectorName];
|
|
283
|
+
if (targetSelector) {
|
|
284
|
+
selectorArgs = normalize(targetSelector, selectorArgs);
|
|
285
|
+
}
|
|
251
286
|
}
|
|
252
|
-
|
|
287
|
+
const state = store.__unstableOriginalGetState();
|
|
288
|
+
return metaDataSelector(state.metadata, selectorName, selectorArgs, ...args);
|
|
253
289
|
};
|
|
254
290
|
boundSelector.hasResolver = false;
|
|
255
291
|
return boundSelector;
|
|
256
292
|
}
|
|
293
|
+
|
|
294
|
+
// Perform binding of both metadata and store selectors and combine them in one
|
|
295
|
+
// `selectors` object. These are all public selectors of the store.
|
|
296
|
+
const boundMetadataSelectors = mapValues(metadataSelectors, bindMetadataSelector);
|
|
297
|
+
const boundSelectors = mapValues(options.selectors, bindSelector);
|
|
257
298
|
const selectors = {
|
|
258
|
-
...
|
|
259
|
-
...
|
|
299
|
+
...boundMetadataSelectors,
|
|
300
|
+
...boundSelectors
|
|
260
301
|
};
|
|
261
|
-
|
|
302
|
+
|
|
303
|
+
// Cache of bould private selectors. They are bound only when first accessed, because
|
|
304
|
+
// new private selectors can be registered at any time (with `registerPrivateSelectors`).
|
|
305
|
+
// Once bound, they are cached to give them a stable identity.
|
|
306
|
+
const boundPrivateSelectors = createBindingCache(name => privateSelectors[name], bindSelector);
|
|
307
|
+
const allSelectors = createPrivateProxy(selectors, boundPrivateSelectors);
|
|
262
308
|
|
|
263
309
|
// Pre-bind the private selectors that have been registered by the time of
|
|
264
310
|
// instantiation, so that registry selectors are bound to the registry.
|
|
265
|
-
for (const
|
|
266
|
-
boundPrivateSelectors.get(
|
|
311
|
+
for (const selectorName of Object.keys(privateSelectors)) {
|
|
312
|
+
boundPrivateSelectors.get(selectorName);
|
|
267
313
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
apply: (target, thisArg, [selector]) => selector(store.__unstableOriginalGetState())
|
|
314
|
+
|
|
315
|
+
// An object that implements the `select` object that is passed to thunk functions.
|
|
316
|
+
// It is callable (`select( selector )`) and also has methods (`select.foo()`) that
|
|
317
|
+
// correspond to bound registered selectors, both public and private. Implemented with the proxy
|
|
318
|
+
// `get` method, delegating to `allSelectors`.
|
|
319
|
+
const thunkSelect = new Proxy(selector => selector(store.__unstableOriginalGetState()), {
|
|
320
|
+
get: (target, name) => allSelectors[name]
|
|
276
321
|
});
|
|
322
|
+
|
|
323
|
+
// To the public `selectors` object, add the "locked" `allSelectors` object. When used,
|
|
324
|
+
// `unlock( selectors )` will return `allSelectors`, implementing a way how to get at the private selectors.
|
|
277
325
|
(0, _lockUnlock.lock)(selectors, allSelectors);
|
|
278
|
-
|
|
279
|
-
|
|
326
|
+
|
|
327
|
+
// For each selector, create a function that calls the selector, waits for resolution and returns
|
|
328
|
+
// a promise that resolves when the resolution is finished.
|
|
329
|
+
const bindResolveSelector = mapResolveSelector(store, boundMetadataSelectors);
|
|
330
|
+
|
|
331
|
+
// Now apply this function to all bound selectors, public and private. We are excluding
|
|
332
|
+
// metadata selectors because they don't have resolvers.
|
|
333
|
+
const resolveSelectors = mapValues(boundSelectors, bindResolveSelector);
|
|
334
|
+
const allResolveSelectors = createPrivateProxy(resolveSelectors, createBindingCache(name => boundPrivateSelectors.get(name), bindResolveSelector));
|
|
335
|
+
|
|
336
|
+
// Lock the selectors so that `unlock( resolveSelectors )` returns `allResolveSelectors`.
|
|
337
|
+
(0, _lockUnlock.lock)(resolveSelectors, allResolveSelectors);
|
|
338
|
+
|
|
339
|
+
// Now, in a way very similar to `bindResolveSelector`, we create a function that maps
|
|
340
|
+
// selectors to functions that throw a suspense promise if not yet resolved.
|
|
341
|
+
const bindSuspendSelector = mapSuspendSelector(store, boundMetadataSelectors);
|
|
342
|
+
const suspendSelectors = {
|
|
343
|
+
...boundMetadataSelectors,
|
|
344
|
+
// no special suspense behavior
|
|
345
|
+
...mapValues(boundSelectors, bindSuspendSelector)
|
|
346
|
+
};
|
|
347
|
+
const allSuspendSelectors = createPrivateProxy(suspendSelectors, createBindingCache(name => boundPrivateSelectors.get(name), bindSuspendSelector));
|
|
348
|
+
|
|
349
|
+
// Lock the selectors so that `unlock( suspendSelectors )` returns 'allSuspendSelectors`.
|
|
350
|
+
(0, _lockUnlock.lock)(suspendSelectors, allSuspendSelectors);
|
|
280
351
|
const getSelectors = () => selectors;
|
|
281
352
|
const getActions = () => actions;
|
|
282
353
|
const getResolveSelectors = () => resolveSelectors;
|
|
@@ -325,7 +396,7 @@ function createReduxStore(key, options) {
|
|
|
325
396
|
|
|
326
397
|
// Expose the private registration functions on the store
|
|
327
398
|
// descriptor. That's a natural choice since that's where the
|
|
328
|
-
// public actions and selectors are stored
|
|
399
|
+
// public actions and selectors are stored.
|
|
329
400
|
(0, _lockUnlock.lock)(storeDescriptor, privateRegistrationFunctions);
|
|
330
401
|
return storeDescriptor;
|
|
331
402
|
}
|
|
@@ -372,113 +443,99 @@ function instantiateReduxStore(key, options, registry, thunkArgs) {
|
|
|
372
443
|
}
|
|
373
444
|
|
|
374
445
|
/**
|
|
375
|
-
* Maps selectors to functions that return a resolution promise for them
|
|
446
|
+
* Maps selectors to functions that return a resolution promise for them.
|
|
376
447
|
*
|
|
377
|
-
* @param {Object} selectors
|
|
378
|
-
* @param {Object}
|
|
448
|
+
* @param {Object} store The redux store the selectors are bound to.
|
|
449
|
+
* @param {Object} boundMetadataSelectors The bound metadata selectors.
|
|
379
450
|
*
|
|
380
|
-
* @return {
|
|
451
|
+
* @return {Function} Function that maps selectors to resolvers.
|
|
381
452
|
*/
|
|
382
|
-
function
|
|
383
|
-
|
|
384
|
-
getIsResolving,
|
|
385
|
-
hasStartedResolution,
|
|
386
|
-
hasFinishedResolution,
|
|
387
|
-
hasResolutionFailed,
|
|
388
|
-
isResolving,
|
|
389
|
-
getCachedResolvers,
|
|
390
|
-
getResolutionState,
|
|
391
|
-
getResolutionError,
|
|
392
|
-
hasResolvingSelectors,
|
|
393
|
-
countSelectorsByStatus,
|
|
394
|
-
...storeSelectors
|
|
395
|
-
} = selectors;
|
|
396
|
-
return mapValues(storeSelectors, (selector, selectorName) => {
|
|
453
|
+
function mapResolveSelector(store, boundMetadataSelectors) {
|
|
454
|
+
return (selector, selectorName) => {
|
|
397
455
|
// If the selector doesn't have a resolver, just convert the return value
|
|
398
456
|
// (including exceptions) to a Promise, no additional extra behavior is needed.
|
|
399
457
|
if (!selector.hasResolver) {
|
|
400
458
|
return async (...args) => selector.apply(null, args);
|
|
401
459
|
}
|
|
402
|
-
return (...args) => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
460
|
+
return (...args) => new Promise((resolve, reject) => {
|
|
461
|
+
const hasFinished = () => {
|
|
462
|
+
return boundMetadataSelectors.hasFinishedResolution(selectorName, args);
|
|
463
|
+
};
|
|
464
|
+
const finalize = result => {
|
|
465
|
+
const hasFailed = boundMetadataSelectors.hasResolutionFailed(selectorName, args);
|
|
466
|
+
if (hasFailed) {
|
|
467
|
+
const error = boundMetadataSelectors.getResolutionError(selectorName, args);
|
|
468
|
+
reject(error);
|
|
469
|
+
} else {
|
|
470
|
+
resolve(result);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
const getResult = () => selector.apply(null, args);
|
|
474
|
+
|
|
475
|
+
// Trigger the selector (to trigger the resolver)
|
|
476
|
+
const result = getResult();
|
|
477
|
+
if (hasFinished()) {
|
|
478
|
+
return finalize(result);
|
|
479
|
+
}
|
|
480
|
+
const unsubscribe = store.subscribe(() => {
|
|
417
481
|
if (hasFinished()) {
|
|
418
|
-
|
|
482
|
+
unsubscribe();
|
|
483
|
+
finalize(getResult());
|
|
419
484
|
}
|
|
420
|
-
const unsubscribe = store.subscribe(() => {
|
|
421
|
-
if (hasFinished()) {
|
|
422
|
-
unsubscribe();
|
|
423
|
-
finalize(getResult());
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
485
|
});
|
|
427
|
-
};
|
|
428
|
-
}
|
|
486
|
+
});
|
|
487
|
+
};
|
|
429
488
|
}
|
|
430
489
|
|
|
431
490
|
/**
|
|
432
491
|
* Maps selectors to functions that throw a suspense promise if not yet resolved.
|
|
433
492
|
*
|
|
434
|
-
* @param {Object} selectors
|
|
435
|
-
* @param {Object}
|
|
493
|
+
* @param {Object} store The redux store the selectors select from.
|
|
494
|
+
* @param {Object} boundMetadataSelectors The bound metadata selectors.
|
|
436
495
|
*
|
|
437
|
-
* @return {
|
|
496
|
+
* @return {Function} Function that maps selectors to their suspending versions.
|
|
438
497
|
*/
|
|
439
|
-
function
|
|
440
|
-
return
|
|
498
|
+
function mapSuspendSelector(store, boundMetadataSelectors) {
|
|
499
|
+
return (selector, selectorName) => {
|
|
441
500
|
// Selector without a resolver doesn't have any extra suspense behavior.
|
|
442
501
|
if (!selector.hasResolver) {
|
|
443
502
|
return selector;
|
|
444
503
|
}
|
|
445
504
|
return (...args) => {
|
|
446
505
|
const result = selector.apply(null, args);
|
|
447
|
-
if (
|
|
448
|
-
if (
|
|
449
|
-
throw
|
|
506
|
+
if (boundMetadataSelectors.hasFinishedResolution(selectorName, args)) {
|
|
507
|
+
if (boundMetadataSelectors.hasResolutionFailed(selectorName, args)) {
|
|
508
|
+
throw boundMetadataSelectors.getResolutionError(selectorName, args);
|
|
450
509
|
}
|
|
451
510
|
return result;
|
|
452
511
|
}
|
|
453
512
|
throw new Promise(resolve => {
|
|
454
513
|
const unsubscribe = store.subscribe(() => {
|
|
455
|
-
if (
|
|
514
|
+
if (boundMetadataSelectors.hasFinishedResolution(selectorName, args)) {
|
|
456
515
|
resolve();
|
|
457
516
|
unsubscribe();
|
|
458
517
|
}
|
|
459
518
|
});
|
|
460
519
|
});
|
|
461
520
|
};
|
|
462
|
-
}
|
|
521
|
+
};
|
|
463
522
|
}
|
|
464
523
|
|
|
465
524
|
/**
|
|
466
|
-
* Convert
|
|
525
|
+
* Convert a resolver to a normalized form, an object with `fulfill` method and
|
|
467
526
|
* optional methods like `isFulfilled`.
|
|
468
527
|
*
|
|
469
|
-
* @param {
|
|
528
|
+
* @param {Function} resolver Resolver to convert
|
|
470
529
|
*/
|
|
471
|
-
function
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
};
|
|
481
|
-
});
|
|
530
|
+
function mapResolver(resolver) {
|
|
531
|
+
if (resolver.fulfill) {
|
|
532
|
+
return resolver;
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
...resolver,
|
|
536
|
+
// Copy the enumerable properties of the resolver function.
|
|
537
|
+
fulfill: resolver // Add the fulfill method.
|
|
538
|
+
};
|
|
482
539
|
}
|
|
483
540
|
|
|
484
541
|
/**
|
|
@@ -486,22 +543,20 @@ function mapResolvers(resolvers) {
|
|
|
486
543
|
* Resolvers are side effects invoked once per argument set of a given selector call,
|
|
487
544
|
* used in ensuring that the data needs for the selector are satisfied.
|
|
488
545
|
*
|
|
489
|
-
* @param {Object} selector
|
|
490
|
-
* @param {string} selectorName
|
|
491
|
-
* @param {Object} resolver
|
|
492
|
-
* @param {Object} store
|
|
493
|
-
* @param {Object} resolversCache
|
|
546
|
+
* @param {Object} selector The selector function to be bound.
|
|
547
|
+
* @param {string} selectorName The selector name.
|
|
548
|
+
* @param {Object} resolver Resolver to call.
|
|
549
|
+
* @param {Object} store The redux store to which the resolvers should be mapped.
|
|
550
|
+
* @param {Object} resolversCache Resolvers Cache.
|
|
551
|
+
* @param {Object} boundMetadataSelectors The bound metadata selectors.
|
|
494
552
|
*/
|
|
495
|
-
function mapSelectorWithResolver(selector, selectorName, resolver, store, resolversCache) {
|
|
553
|
+
function mapSelectorWithResolver(selector, selectorName, resolver, store, resolversCache, boundMetadataSelectors) {
|
|
496
554
|
function fulfillSelector(args) {
|
|
497
555
|
const state = store.getState();
|
|
498
556
|
if (resolversCache.isRunning(selectorName, args) || typeof resolver.isFulfilled === 'function' && resolver.isFulfilled(state, ...args)) {
|
|
499
557
|
return;
|
|
500
558
|
}
|
|
501
|
-
|
|
502
|
-
metadata
|
|
503
|
-
} = store.__unstableOriginalGetState();
|
|
504
|
-
if (metadataSelectors.hasStartedResolution(metadata, selectorName, args)) {
|
|
559
|
+
if (boundMetadataSelectors.hasStartedResolution(selectorName, args)) {
|
|
505
560
|
return;
|
|
506
561
|
}
|
|
507
562
|
resolversCache.markAsRunning(selectorName, args);
|