@ethlete/query 6.0.0-next.40 → 6.0.0-next.41

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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, Injector, effect, DestroyRef, InjectionToken, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, untracked, signal, linkedSignal, computed, runInInjectionContext, isDevMode, PLATFORM_ID, resource, DOCUMENT, VERSION, makeEnvironmentProviders, assertInInjectionContext, provideEnvironmentInitializer, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, NgZone } from '@angular/core';
3
3
  import { UrlTree, Router, RedirectCommand, ActivatedRoute } from '@angular/router';
4
- import { Observable, defer, from, filter, Subject, Subscription, throwError, of, timer, switchMap, tap, retry, catchError, EMPTY, take, takeUntil, finalize, mergeMap, toArray, map, merge, takeWhile, firstValueFrom, race, concatMap, fromEvent, throttleTime, interval, startWith, pairwise, BehaviorSubject, combineLatest, ReplaySubject, shareReplay, skip, withLatestFrom, debounceTime } from 'rxjs';
4
+ import { Observable, defer, from, filter, Subject, Subscription, throwError, of, timer, switchMap, tap, retry, catchError, EMPTY as EMPTY$1, take, takeUntil, finalize, mergeMap, toArray, map, merge, takeWhile, firstValueFrom, race, concatMap, fromEvent, throttleTime, interval, startWith, pairwise, BehaviorSubject, combineLatest, ReplaySubject, shareReplay, skip, withLatestFrom, debounceTime } from 'rxjs';
5
5
  import { defineStaticProvider, RuntimeError, clamp, randomId, defineRootProvider, isObject, injectUnsavedChangesCoordinator, injectRoute, getCookie, deleteCookie, setCookie, CORE_VERSION, createDestroy, computedTillTruthy, defineProvider, toProvideFn, toInjectFn, nextFrame, injectQueryParams, injectQueryParamChanges, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
6
6
  import { HttpClient, HttpEventType, HttpErrorResponse, HttpResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
7
7
  import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
@@ -330,6 +330,36 @@ const consumeSuppressQueryStackDevtools = () => {
330
330
  suppressStackRegistration = false;
331
331
  return value;
332
332
  };
333
+ let authProviderRegistrar = null;
334
+ /**
335
+ * Installs the session vault's provider registrar. Called by `provideQueryDevtools()`; nothing else may
336
+ * call it.
337
+ * @internal
338
+ */
339
+ const setQueryDevtoolsAuthProviderRegistrar = (fn) => {
340
+ authProviderRegistrar = fn;
341
+ };
342
+ /**
343
+ * Hands one auth provider to the devtools' session vault, which then keeps the live session's tokens in
344
+ * step with the stored one. A no-op without `provideQueryDevtools()`.
345
+ * @internal
346
+ */
347
+ const registerQueryDevtoolsAuthProvider = (registration) => authProviderRegistrar?.(registration) ?? (() => undefined);
348
+ let authSeedReader = null;
349
+ /**
350
+ * Installs the tab-local session reader. Called by `provideQueryDevtools()`; nothing else may call it.
351
+ * @internal
352
+ */
353
+ const setQueryDevtoolsAuthSeedReader = (fn) => {
354
+ authSeedReader = fn;
355
+ };
356
+ /**
357
+ * The session the panel put in this tab alone, or `null` for a tab on the shared one. Read once while a
358
+ * provider is built, before its features run: the tokens have to be in place before `withPersistentAuth`
359
+ * decides whether to spend its cookie.
360
+ * @internal
361
+ */
362
+ const readQueryDevtoolsAuthSeed = (providerName) => authSeedReader?.(providerName) ?? null;
333
363
 
334
364
  /**
335
365
  * Whether a create-query options object came from the gql layer - only gql's `creatorInternals` carries
@@ -3102,12 +3132,12 @@ const createQueryBatch = (options) => {
3102
3132
  const runEntry = (entry) => defer(() => {
3103
3133
  if (cancelRequested) {
3104
3134
  record({ status: 'cancelled', index: entry.index, item: entry.item });
3105
- return EMPTY;
3135
+ return EMPTY$1;
3106
3136
  }
3107
3137
  const args = mapArgs(entry.item, entry.index);
3108
3138
  if (args === null) {
3109
3139
  record({ status: 'skipped', index: entry.index, item: entry.item });
3110
- return EMPTY;
3140
+ return EMPTY$1;
3111
3141
  }
3112
3142
  // The args reach the query through `execute()`, never `withArgs()`, so a function route has to
3113
3143
  // be told the missing feature is intentional or creating the query throws ET100.
@@ -4511,7 +4541,7 @@ const createSecureExecuteFactory = (options) => {
4511
4541
  injector: options.deps.injector,
4512
4542
  }).pipe(switchMap((latestQuery) => {
4513
4543
  if (!latestQuery)
4514
- return EMPTY;
4544
+ return EMPTY$1;
4515
4545
  const query = latestQuery.snapshot;
4516
4546
  return toObservable(query.isAlive, { injector: options.deps.injector }).pipe(filter((isAlive) => !isAlive), filter(() => !!query.error() || !!query.response()), map(() => query));
4517
4547
  }), tap((query) => {
@@ -5276,6 +5306,16 @@ const createBearerAuthProviderImpl = (config) => {
5276
5306
  // leaves the tab locked against closing. See `injectUnsavedChangesCoordinator`.
5277
5307
  unsavedChanges.abandonAll('logout');
5278
5308
  };
5309
+ const isTabLocalSession = signal(false, /* @ts-ignore */
5310
+ ...(ngDevMode ? [{ debugName: "isTabLocalSession" }] : /* istanbul ignore next */ []));
5311
+ // Before the features: `withPersistentAuth` starts its cookie auto-login during setup, and a tab that
5312
+ // was handed a session of its own must already hold it by then - both so the auto-login stands down,
5313
+ // and so the sync and the cookie see the flag before they decide whether to run at all.
5314
+ const devtoolsSeed = isQueryDevtoolsEnabled() ? readQueryDevtoolsAuthSeed(config.name) : null;
5315
+ if (devtoolsSeed) {
5316
+ isTabLocalSession.set(true);
5317
+ setTokens(devtoolsSeed.accessToken, devtoolsSeed.refreshToken);
5318
+ }
5279
5319
  const isLeader = runEarlyFeatureSetup(config.features, {
5280
5320
  accessToken,
5281
5321
  refreshToken,
@@ -5285,6 +5325,7 @@ const createBearerAuthProviderImpl = (config) => {
5285
5325
  setTokens,
5286
5326
  logout,
5287
5327
  sessionEndCause: sessionEndCause.asReadonly(),
5328
+ isTabLocalSession: isTabLocalSession.asReadonly(),
5288
5329
  });
5289
5330
  const querySetupContext = {
5290
5331
  name: config.name,
@@ -5314,6 +5355,7 @@ const createBearerAuthProviderImpl = (config) => {
5314
5355
  destroyRef,
5315
5356
  setTokens,
5316
5357
  isLeader: isLeader.isLeaderFn,
5358
+ isTabLocalSession: isTabLocalSession.asReadonly(),
5317
5359
  leaderElection: isLeader.leaderElectionContext,
5318
5360
  sessionAdoption: isLeader.sessionAdoption,
5319
5361
  activityCoordination: isLeader.activityCoordination,
@@ -5373,6 +5415,13 @@ const createBearerAuthProviderImpl = (config) => {
5373
5415
  },
5374
5416
  });
5375
5417
  destroyRef.onDestroy(unregister);
5418
+ destroyRef.onDestroy(registerQueryDevtoolsAuthProvider({
5419
+ name: config.name,
5420
+ handle: provider,
5421
+ client: queryClient,
5422
+ isTabLocalSession,
5423
+ injector,
5424
+ }));
5376
5425
  }
5377
5426
  return provider;
5378
5427
  };
@@ -5706,7 +5755,7 @@ const withRefreshQuery = (key, config) => {
5706
5755
  const attempt = executeRefresh('scheduled');
5707
5756
  const retryInMs = retryScheduledIn(attempt);
5708
5757
  if (retryInMs === null)
5709
- return EMPTY;
5758
+ return EMPTY$1;
5710
5759
  // Waiting on another tab does not spend the budget. Both outcomes come back at a deadline
5711
5760
  // rather than on a fixed interval, and both end the moment a token pair arrives, because a
5712
5761
  // new token restarts the schedule. Spending the budget on them leaves a follower whose
@@ -5714,7 +5763,7 @@ const withRefreshQuery = (key, config) => {
5714
5763
  // from: the tab then holds a dead token and cannot even ask for a new one.
5715
5764
  if (attempt === 'notLeader' || attempt === 'delegated')
5716
5765
  return scheduledRefresh(retryInMs, attemptsLeft);
5717
- return attemptsLeft <= 0 ? EMPTY : scheduledRefresh(retryInMs, attemptsLeft - 1);
5766
+ return attemptsLeft <= 0 ? EMPTY$1 : scheduledRefresh(retryInMs, attemptsLeft - 1);
5718
5767
  }));
5719
5768
  // A backgrounded tab's `timer()` does not fire on time - the browser throttles it, and a frozen
5720
5769
  // page stops running it altogether while keeping the Web Lock that makes it the leader. So the
@@ -5742,7 +5791,7 @@ const withRefreshQuery = (key, config) => {
5742
5791
  }, /* @ts-ignore */
5743
5792
  ...(ngDevMode ? [{ debugName: "nextScheduledRefresh" }] : /* istanbul ignore next */ []));
5744
5793
  toObservable(nextScheduledRefresh)
5745
- .pipe(switchMap(({ dueInMs }) => (dueInMs === null ? EMPTY : scheduledRefresh(dueInMs, maxRescheduleAttempts))), takeUntilDestroyed())
5794
+ .pipe(switchMap(({ dueInMs }) => (dueInMs === null ? EMPTY$1 : scheduledRefresh(dueInMs, maxRescheduleAttempts))), takeUntilDestroyed())
5746
5795
  .subscribe();
5747
5796
  context.refreshCoordination?.requests$.pipe(takeUntilDestroyed()).subscribe(() => executeRefresh('unauthorized'));
5748
5797
  context.refreshCoordination?.starts$.pipe(takeUntilDestroyed()).subscribe(() => {
@@ -6543,7 +6592,10 @@ const withBearerAuthMultiTabSync = (config = {}) => {
6543
6592
  type: BearerAuthFeatureType.MULTI_TAB_SYNC,
6544
6593
  instance: state.instance,
6545
6594
  devtools: () => [
6546
- { label: 'channel', value: state.channelName },
6595
+ {
6596
+ label: 'channel',
6597
+ value: context.isTabLocalSession() ? "off (session is this tab's own)" : state.channelName,
6598
+ },
6547
6599
  { label: 'tokens', value: config.syncTokens === false ? 'tab local' : 'synced' },
6548
6600
  { label: 'logout', value: config.syncLogout === false ? 'tab local' : 'synced' },
6549
6601
  {
@@ -6557,6 +6609,13 @@ const withBearerAuthMultiTabSync = (config = {}) => {
6557
6609
  // regular feature setup runs - so this half has to happen earlier than `setup`.
6558
6610
  const earlySetup = (context) => {
6559
6611
  const channelName = config.channelName ?? defaultSyncChannelName(context.name);
6612
+ // A tab the devtools handed a session of its own is not part of this app's shared session: it must
6613
+ // not push its tokens at its siblings, adopt theirs, or log them out - and it refreshes for itself,
6614
+ // because a leader that speaks for a session it is not on would refresh the wrong one.
6615
+ if (context.isTabLocalSession()) {
6616
+ pendingSetups.push({ instance: SINGLE_TAB, channelName });
6617
+ return {};
6618
+ }
6560
6619
  // Filled in below once the election exists. Until then this tab answers as its own leader, which
6561
6620
  // is what it is: the sync is set up first because the auth queries read `isLeader` while being
6562
6621
  // wired, and a `state-request` cannot arrive before this function returns anyway.
@@ -6643,6 +6702,11 @@ const createPersistentAuthFeature = (config, context) => {
6643
6702
  effect(() => {
6644
6703
  const token = refreshToken();
6645
6704
  const rememberMe = rememberMeSignal();
6705
+ // The cookie is the whole browser's, so a tab holding a session of its own would hand its user to
6706
+ // every other tab on the next load. Such a tab is restored from `sessionStorage` by the devtools
6707
+ // instead - see `setQueryDevtoolsAuthTabLocal`.
6708
+ if (context.isTabLocalSession())
6709
+ return;
6646
6710
  // A missing token is not a reason to drop the cookie. It is missing on every startup - `tryLogin`
6647
6711
  // reads the cookie synchronously and the auto-login only resolves a tick later - so deleting here
6648
6712
  // would throw away a 30-day refresh token before it was ever used. Deletion is driven by the events
@@ -6654,7 +6718,7 @@ const createPersistentAuthFeature = (config, context) => {
6654
6718
  });
6655
6719
  effect(() => {
6656
6720
  const endCause = context.sessionEndCause();
6657
- if (endCause)
6721
+ if (endCause && !context.isTabLocalSession())
6658
6722
  removeCookie();
6659
6723
  });
6660
6724
  effect(() => {
@@ -6666,7 +6730,7 @@ const createPersistentAuthFeature = (config, context) => {
6666
6730
  const wasRejected = state.state === 'error' &&
6667
6731
  (state.type === 'tokenRefresh' || state.type === 'autoLogin') &&
6668
6732
  (state.error.code === 401 || state.error.code === 403);
6669
- if (wasRejected)
6733
+ if (wasRejected && !context.isTabLocalSession())
6670
6734
  removeCookie();
6671
6735
  });
6672
6736
  const exchangeCookie = () => {
@@ -7034,6 +7098,437 @@ const queryDevtoolsAbout = () => ({
7034
7098
  app,
7035
7099
  });
7036
7100
 
7101
+ const HOST_ID = 'et-query-devtools-pill';
7102
+ /** What the picker writes for "let the application decide", which is the key removed. */
7103
+ const DEFAULT_VALUE = '';
7104
+ /** Prefixes a production env's name, so the open dropdown says which option is the real backend. */
7105
+ const PRODUCTION_MARK = '⚠ ';
7106
+ const STYLE = `
7107
+ :host {
7108
+ position: fixed;
7109
+ display: flex;
7110
+ flex-direction: column;
7111
+ align-items: flex-end;
7112
+ gap: 8px;
7113
+ inset-block-end: 64px;
7114
+ inset-inline-end: 16px;
7115
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
7116
+ font-size: 13px;
7117
+ /* One below the panel and the toggle button (2147483010), so an open panel covers the pill - the
7118
+ panel offers the same picker on its Settings tab. Keep the three in step. */
7119
+ z-index: 2147483009;
7120
+ }
7121
+
7122
+ label {
7123
+ --_accent: var(--et-theme-color-primary-solid, #60a5fa);
7124
+
7125
+ display: inline-flex;
7126
+ align-items: center;
7127
+ gap: 8px;
7128
+ padding: 8px 10px 8px 12px;
7129
+ border-radius: 999px;
7130
+ background: linear-gradient(135deg, #1f1f23, #2a2a30);
7131
+ color: #fafafa;
7132
+ box-shadow:
7133
+ 0 6px 20px rgb(0 0 0 / 0.35),
7134
+ inset 0 0 0 1px rgb(255 255 255 / 0.08);
7135
+ }
7136
+
7137
+ .name {
7138
+ color: #a1a1aa;
7139
+ font-size: 11px;
7140
+ font-weight: 600;
7141
+ letter-spacing: 0.04em;
7142
+ line-height: 1;
7143
+ text-transform: uppercase;
7144
+ }
7145
+
7146
+ select {
7147
+ max-inline-size: 220px;
7148
+ padding: 3px 6px;
7149
+ border: none;
7150
+ border-radius: 5px;
7151
+ background: rgb(255 255 255 / 0.07);
7152
+ box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.12);
7153
+ color: #fafafa;
7154
+ font: inherit;
7155
+ font-size: 12px;
7156
+ font-weight: 600;
7157
+ line-height: 1;
7158
+ cursor: pointer;
7159
+ }
7160
+
7161
+ select:hover {
7162
+ box-shadow: inset 0 0 0 1px var(--_accent);
7163
+ }
7164
+
7165
+ /* The popup is painted by the browser, which does not inherit the pill's dark background. */
7166
+ option {
7167
+ background: #1f1f23;
7168
+ color: #fafafa;
7169
+ }
7170
+
7171
+ label[data-production] {
7172
+ background: linear-gradient(135deg, #7f1d1d, #b91c1c);
7173
+ color: #fff;
7174
+ box-shadow:
7175
+ 0 6px 22px rgb(239 68 68 / 0.5),
7176
+ inset 0 0 0 1px rgb(254 202 202 / 0.7);
7177
+ }
7178
+
7179
+ label[data-production] .name {
7180
+ color: #fee2e2;
7181
+ }
7182
+
7183
+ label[data-production] .mark {
7184
+ font-size: 15px;
7185
+ line-height: 1;
7186
+ }
7187
+
7188
+ label[data-production] select {
7189
+ background: rgb(0 0 0 / 0.35);
7190
+ box-shadow: inset 0 0 0 1px rgb(254 202 202 / 0.6);
7191
+ text-transform: uppercase;
7192
+ }
7193
+
7194
+ label[data-production] option {
7195
+ background: #450a0a;
7196
+ }
7197
+
7198
+ label[data-auth] {
7199
+ --_accent: var(--et-theme-color-success-solid, #34d399);
7200
+ }
7201
+
7202
+ button {
7203
+ padding: 3px 7px;
7204
+ border: none;
7205
+ border-radius: 5px;
7206
+ background: rgb(255 255 255 / 0.07);
7207
+ box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.12);
7208
+ color: #fafafa;
7209
+ font: inherit;
7210
+ font-size: 11px;
7211
+ font-weight: 600;
7212
+ line-height: 1.4;
7213
+ cursor: pointer;
7214
+ }
7215
+
7216
+ button:hover {
7217
+ box-shadow: inset 0 0 0 1px var(--_accent);
7218
+ }
7219
+
7220
+ button[data-on] {
7221
+ background: rgb(52 211 153 / 0.18);
7222
+ box-shadow: inset 0 0 0 1px rgb(52 211 153 / 0.7);
7223
+ }
7224
+
7225
+ /* The whole viewport, so the warning is on screen wherever the reader is looking. Outside the pill's
7226
+ flex flow: a fixed child of a fixed host still lays out against the viewport. */
7227
+ .frame {
7228
+ position: fixed;
7229
+ inset: 0;
7230
+ box-shadow:
7231
+ inset 0 0 0 3px rgb(239 68 68 / 0.85),
7232
+ inset 0 0 22px rgb(239 68 68 / 0.25);
7233
+ opacity: 0.85;
7234
+ pointer-events: none;
7235
+ }
7236
+
7237
+ @media (prefers-reduced-motion: no-preference) {
7238
+ label[data-production] {
7239
+ animation: et-qd-api-env-pulse 1.6s ease-in-out infinite;
7240
+ }
7241
+
7242
+ .frame {
7243
+ animation: et-qd-api-env-frame 1.6s ease-in-out infinite;
7244
+ }
7245
+ }
7246
+
7247
+ @keyframes et-qd-api-env-pulse {
7248
+ 0%,
7249
+ 100% {
7250
+ box-shadow:
7251
+ 0 6px 20px rgb(239 68 68 / 0.35),
7252
+ inset 0 0 0 1px rgb(254 202 202 / 0.45);
7253
+ }
7254
+
7255
+ 50% {
7256
+ box-shadow:
7257
+ 0 6px 30px rgb(239 68 68 / 0.85),
7258
+ inset 0 0 0 2px rgb(254 226 226 / 0.95);
7259
+ }
7260
+ }
7261
+
7262
+ @keyframes et-qd-api-env-frame {
7263
+ 0%,
7264
+ 100% {
7265
+ opacity: 0.35;
7266
+ }
7267
+
7268
+ 50% {
7269
+ opacity: 1;
7270
+ }
7271
+ }
7272
+ `;
7273
+ /** The env a stored value lands on, which is the switch's own fallback while nothing is stored. */
7274
+ const resolvedEnvOf = (apiSwitch, stored) => apiSwitch.envs.find((env) => env.id === (stored || apiSwitch.fallback));
7275
+ const nameOf = (env) => `${env.production ? PRODUCTION_MARK : ''}${env.label ?? env.id}`;
7276
+ const optionsOf = (apiSwitch, stored) => {
7277
+ const fallback = apiSwitch.envs.find((env) => env.id === apiSwitch.fallback);
7278
+ const options = [
7279
+ {
7280
+ value: DEFAULT_VALUE,
7281
+ label: fallback ? `default (${nameOf(fallback)})` : 'default',
7282
+ title: `Removes ${apiSwitch.storageKey}, so the app picks for itself`,
7283
+ },
7284
+ ];
7285
+ for (const env of apiSwitch.envs) {
7286
+ options.push({ value: env.id, label: nameOf(env), title: env.url ?? env.id });
7287
+ }
7288
+ // A URL typed into the Settings tab is a value no env carries, and a select cannot show one.
7289
+ if (stored !== DEFAULT_VALUE && !apiSwitch.envs.some((env) => env.id === stored)) {
7290
+ options.push({ value: stored, label: stored, title: `Typed in: ${stored}` });
7291
+ }
7292
+ return options;
7293
+ };
7294
+ const buildSwitch = (doc, apiSwitch, stored, pick) => {
7295
+ const production = resolvedEnvOf(apiSwitch, stored)?.production === true;
7296
+ const label = doc.createElement('label');
7297
+ label.title = production
7298
+ ? `${apiSwitch.name} points at production. The data is real. Picking reloads the page.`
7299
+ : `${apiSwitch.name} — picking reloads the page`;
7300
+ if (production) {
7301
+ label.setAttribute('data-production', '');
7302
+ const mark = doc.createElement('span');
7303
+ mark.className = 'mark';
7304
+ mark.textContent = '⚠';
7305
+ mark.setAttribute('aria-hidden', 'true');
7306
+ label.append(mark);
7307
+ }
7308
+ const name = doc.createElement('span');
7309
+ name.className = 'name';
7310
+ name.textContent = production ? `${apiSwitch.name} · live` : apiSwitch.name;
7311
+ const select = doc.createElement('select');
7312
+ select.setAttribute('aria-label', apiSwitch.name);
7313
+ for (const option of optionsOf(apiSwitch, stored)) {
7314
+ const element = doc.createElement('option');
7315
+ element.value = option.value;
7316
+ element.textContent = option.label;
7317
+ element.title = option.title;
7318
+ element.selected = option.value === stored;
7319
+ select.append(element);
7320
+ }
7321
+ select.addEventListener('change', () => pick(apiSwitch.storageKey, select.value === DEFAULT_VALUE ? null : select.value));
7322
+ label.append(name, select);
7323
+ return label;
7324
+ };
7325
+ let envState = null;
7326
+ let authState = null;
7327
+ const buildAuthRow = (doc, row) => {
7328
+ const label = doc.createElement('label');
7329
+ label.setAttribute('data-auth', '');
7330
+ label.title = row.tabLocal
7331
+ ? `${row.name} holds this tab's own session. The other tabs are on theirs.`
7332
+ : `${row.name} - the session every tab of this app shares`;
7333
+ const name = doc.createElement('span');
7334
+ name.className = 'name';
7335
+ name.textContent = row.tabLocal ? `${row.name} · this tab` : row.name;
7336
+ const select = doc.createElement('select');
7337
+ select.setAttribute('aria-label', `${row.name} session`);
7338
+ const current = doc.createElement('option');
7339
+ current.value = '';
7340
+ current.textContent = row.current;
7341
+ current.selected = !row.options.some((option) => option.selected);
7342
+ select.append(current);
7343
+ for (const option of row.options) {
7344
+ const element = doc.createElement('option');
7345
+ element.value = option.value;
7346
+ element.textContent = option.label;
7347
+ element.title = option.title;
7348
+ element.selected = option.selected;
7349
+ element.disabled = option.disabled;
7350
+ select.append(element);
7351
+ }
7352
+ select.addEventListener('change', () => {
7353
+ if (select.value)
7354
+ row.pick(select.value);
7355
+ });
7356
+ const isolate = doc.createElement('button');
7357
+ isolate.type = 'button';
7358
+ isolate.textContent = row.tabLocal ? 'rejoin' : 'own tab';
7359
+ isolate.title = row.tabLocal
7360
+ ? 'Hands this tab back to the session every other tab shares. Reloads the page.'
7361
+ : 'Keeps this session in this tab alone, so another tab can be somebody else. Reloads the page.';
7362
+ if (row.tabLocal)
7363
+ isolate.setAttribute('data-on', '');
7364
+ isolate.addEventListener('click', row.toggleTabLocal);
7365
+ label.append(name, select, isolate);
7366
+ return label;
7367
+ };
7368
+ const render = (doc) => {
7369
+ doc.getElementById(HOST_ID)?.remove();
7370
+ const switches = envState?.switches ?? [];
7371
+ const rows = authState?.rows ?? [];
7372
+ if (!switches.length && !rows.length)
7373
+ return;
7374
+ const host = doc.createElement('div');
7375
+ host.id = HOST_ID;
7376
+ // Shadow DOM is intentional, for the same reason the toggle button uses it: this paints over the
7377
+ // host application's page and must be as isolated from its global CSS (resets, Tailwind) as possible.
7378
+ const shadow = host.attachShadow({ mode: 'open' });
7379
+ const style = doc.createElement('style');
7380
+ style.textContent = STYLE;
7381
+ shadow.append(style);
7382
+ let production = false;
7383
+ for (const row of rows)
7384
+ shadow.append(buildAuthRow(doc, row));
7385
+ for (const apiSwitch of switches) {
7386
+ const stored = envState?.read(apiSwitch.storageKey) ?? DEFAULT_VALUE;
7387
+ production ||= resolvedEnvOf(apiSwitch, stored)?.production === true;
7388
+ shadow.append(buildSwitch(doc, apiSwitch, stored, (storageKey, value) => envState?.pick(storageKey, value)));
7389
+ }
7390
+ if (production) {
7391
+ const frame = doc.createElement('div');
7392
+ frame.className = 'frame';
7393
+ frame.setAttribute('aria-hidden', 'true');
7394
+ shadow.append(frame);
7395
+ }
7396
+ doc.body.append(host);
7397
+ };
7398
+ /**
7399
+ * Paints the devtools' pills straight onto `document.body`, outside any Angular component tree.
7400
+ *
7401
+ * They cannot be components: an application whose boot is stuck on the backend it was just pointed at -
7402
+ * a blocking initial navigation waiting out an auth call that never answers - renders no component at
7403
+ * all, and that is exactly when somebody needs to pick a different backend or a different user. Mounted
7404
+ * from `setQueryDevtoolsApiEnvs()`, which runs while `provideQueryDevtools()` is being called, the env
7405
+ * part of this is on the page before `bootstrapApplication` starts.
7406
+ */
7407
+ const renderPills = () => {
7408
+ const doc = globalThis.document;
7409
+ if (!doc)
7410
+ return;
7411
+ if (doc.body)
7412
+ render(doc);
7413
+ else
7414
+ doc.addEventListener('DOMContentLoaded', () => render(doc), { once: true });
7415
+ };
7416
+ /**
7417
+ * Declares the API env part of the pill. Called by `setQueryDevtoolsApiEnvs()`.
7418
+ * @internal
7419
+ */
7420
+ const setQueryDevtoolsEnvPills = (state) => {
7421
+ envState = state.switches.length ? state : null;
7422
+ renderPills();
7423
+ };
7424
+ /**
7425
+ * Declares the session part of the pill, which is repainted on every switch, login and logout.
7426
+ * @internal
7427
+ */
7428
+ const setQueryDevtoolsAuthPill = (state) => {
7429
+ authState = state.rows.length ? state : null;
7430
+ renderPills();
7431
+ };
7432
+
7433
+ const switches = /* @__PURE__ */ signal([], /* @ts-ignore */
7434
+ ...(ngDevMode ? [{ debugName: "switches" }] : /* istanbul ignore next */ []));
7435
+ const values = /* @__PURE__ */ signal({}, /* @ts-ignore */
7436
+ ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
7437
+ /**
7438
+ * The API env switches the application declared, or an empty list. Both the floating toggle and the
7439
+ * panel's Settings tab render nothing at all until an application declares one.
7440
+ *
7441
+ * Part of the devtools contract. **Not part of the general public contract.**
7442
+ */
7443
+ const queryDevtoolsApiEnvs = /* @__PURE__ */ switches.asReadonly();
7444
+ /**
7445
+ * What each switch's key holds now, keyed by that key. `null` for a key nothing has written, which is
7446
+ * the switch's own fallback.
7447
+ *
7448
+ * Part of the devtools contract. **Not part of the general public contract.**
7449
+ */
7450
+ const queryDevtoolsApiEnvValues = /* @__PURE__ */ values.asReadonly();
7451
+ const read$1 = (key) => {
7452
+ try {
7453
+ return window.localStorage.getItem(key);
7454
+ }
7455
+ catch {
7456
+ return null;
7457
+ }
7458
+ };
7459
+ /**
7460
+ * Declares the switches and reads what each key holds. Called by `provideQueryDevtools()` and by
7461
+ * nothing else.
7462
+ * @internal
7463
+ */
7464
+ const setQueryDevtoolsApiEnvs = (list) => {
7465
+ switches.set(list ?? []);
7466
+ values.set(Object.fromEntries((list ?? []).map((entry) => [entry.storageKey, read$1(entry.storageKey)])));
7467
+ setQueryDevtoolsEnvPills({ switches: list ?? [], read: read$1, pick: pickAndReload });
7468
+ };
7469
+ /**
7470
+ * Writes one switch's key, or removes it when `value` is `null` so the application falls back. The
7471
+ * application reads the key before Angular boots, so this takes effect on the next page load only -
7472
+ * callers reload.
7473
+ *
7474
+ * The value is written as the plain string the application reads. Do not route it through
7475
+ * `writeQueryDevtoolsStore`: that JSON-encodes, and the quotes would reach the application.
7476
+ *
7477
+ * Part of the devtools contract. **Not part of the general public contract.**
7478
+ */
7479
+ const setQueryDevtoolsApiEnv = (storageKey, value) => {
7480
+ try {
7481
+ if (value === null)
7482
+ window.localStorage.removeItem(storageKey);
7483
+ else
7484
+ window.localStorage.setItem(storageKey, value);
7485
+ }
7486
+ catch {
7487
+ // ignore (private mode / disabled storage / quota)
7488
+ }
7489
+ values.update((current) => ({ ...current, [storageKey]: read$1(storageKey) }));
7490
+ };
7491
+ /** The env in force for one switch, which is its own fallback while nothing is stored. */
7492
+ const resolvedIdOf = (entry) => values()[entry.storageKey] ?? entry.fallback ?? '';
7493
+ /**
7494
+ * Which backends the application is pointed at right now, as one string. Every declared switch is in it,
7495
+ * so a session captured against staging is never offered on production.
7496
+ *
7497
+ * Part of the devtools contract. **Not part of the general public contract.**
7498
+ */
7499
+ const queryDevtoolsApiEnvScope = () => {
7500
+ const list = switches();
7501
+ if (!list.length)
7502
+ return 'default';
7503
+ return list
7504
+ .map((entry) => `${entry.storageKey}=${resolvedIdOf(entry)}`)
7505
+ .sort()
7506
+ .join('&');
7507
+ };
7508
+ /**
7509
+ * The env id in force per switch, which is what an account's declared `envs` is matched against.
7510
+ *
7511
+ * Part of the devtools contract. **Not part of the general public contract.**
7512
+ */
7513
+ const queryDevtoolsApiEnvIds = () => switches()
7514
+ .map(resolvedIdOf)
7515
+ .filter((id) => id !== '');
7516
+ /**
7517
+ * Whether any switch's pick is an env the application declared as `production`. Everything that would
7518
+ * keep a real user's tokens on this machine refuses while this reads `true`.
7519
+ *
7520
+ * Part of the devtools contract. **Not part of the general public contract.**
7521
+ */
7522
+ const queryDevtoolsApiEnvIsProduction = () => switches().some((entry) => entry.envs.find((env) => env.id === resolvedIdOf(entry))?.production === true);
7523
+ /**
7524
+ * What the floating pill does on a pick. The application reads the key before Angular boots, so a
7525
+ * reload is the only way to apply one.
7526
+ */
7527
+ const pickAndReload = (storageKey, value) => {
7528
+ setQueryDevtoolsApiEnv(storageKey, value);
7529
+ window.location.reload();
7530
+ };
7531
+
7037
7532
  const DEFAULTS = {
7038
7533
  viewState: 'session',
7039
7534
  pins: 'local',
@@ -7041,9 +7536,11 @@ const DEFAULTS = {
7041
7536
  mocks: 'local',
7042
7537
  armedMocks: 'none',
7043
7538
  armedFaults: 'none',
7539
+ authSessions: 'local',
7044
7540
  maxEvents: 100,
7045
7541
  maxDroppedCacheEntries: 20,
7046
7542
  responseHistory: null,
7543
+ reloadOnAuthSwitch: true,
7047
7544
  };
7048
7545
  /**
7049
7546
  * `localStorage`, whatever the scopes below say - a scope of `none` still has to be remembered, or the
@@ -7138,16 +7635,18 @@ const asOptionalCount = (value, limits) => {
7138
7635
  return Number.isFinite(count) ? Math.min(Math.max(count, limits.min), limits.max) : null;
7139
7636
  };
7140
7637
  /** Both a hand-edited store and a number typed into the panel come through here. */
7141
- const sanitize = (value) => ({
7638
+ const sanitize$1 = (value) => ({
7142
7639
  viewState: asScope(value?.viewState, DEFAULTS.viewState),
7143
7640
  pins: asScope(value?.pins, DEFAULTS.pins),
7144
7641
  overrides: asScope(value?.overrides, DEFAULTS.overrides),
7145
7642
  mocks: asScope(value?.mocks, DEFAULTS.mocks),
7146
7643
  armedMocks: asScope(value?.armedMocks, DEFAULTS.armedMocks),
7147
7644
  armedFaults: asScope(value?.armedFaults, DEFAULTS.armedFaults),
7645
+ authSessions: asScope(value?.authSessions, DEFAULTS.authSessions),
7148
7646
  maxEvents: asCount(value?.maxEvents, LIMITS.maxEvents, DEFAULTS.maxEvents),
7149
7647
  maxDroppedCacheEntries: asCount(value?.maxDroppedCacheEntries, LIMITS.maxDroppedCacheEntries, DEFAULTS.maxDroppedCacheEntries),
7150
7648
  responseHistory: asOptionalCount(value?.responseHistory, LIMITS.responseHistory),
7649
+ reloadOnAuthSwitch: typeof value?.reloadOnAuthSwitch === 'boolean' ? value.reloadOnAuthSwitch : DEFAULTS.reloadOnAuthSwitch,
7151
7650
  });
7152
7651
  /**
7153
7652
  * Reads the stored settings. Called by `provideQueryDevtools()` **before** the override store is read
@@ -7156,7 +7655,7 @@ const sanitize = (value) => ({
7156
7655
  * @internal
7157
7656
  */
7158
7657
  const initQueryDevtoolsSettings = () => {
7159
- settings.set(sanitize(readQueryDevtoolsStore('local', SETTINGS_KEY)));
7658
+ settings.set(sanitize$1(readQueryDevtoolsStore('local', SETTINGS_KEY)));
7160
7659
  };
7161
7660
  /**
7162
7661
  * Changes part of the devtools configuration and stores it. Values outside the range a field allows are
@@ -7168,11 +7667,503 @@ const initQueryDevtoolsSettings = () => {
7168
7667
  * Part of the devtools contract. **Not part of the general public contract.**
7169
7668
  */
7170
7669
  const setQueryDevtoolsSettings = (patch) => {
7171
- const next = sanitize({ ...settings(), ...patch });
7670
+ const next = sanitize$1({ ...settings(), ...patch });
7172
7671
  settings.set(next);
7173
7672
  writeQueryDevtoolsStore('local', SETTINGS_KEY, next);
7174
7673
  };
7175
7674
 
7675
+ const STORE_KEY = 'ethlete:query:devtools:auth:v1';
7676
+ /**
7677
+ * `sessionStorage`: which stored session a provider's live tokens belong to is the one part of this that
7678
+ * is the tab's own. Two tabs holding two users would otherwise overwrite each other's answer.
7679
+ */
7680
+ const ACTIVE_KEY = 'ethlete:query:devtools:auth-active:v1';
7681
+ /** `sessionStorage`, always: a session that belongs to one tab cannot be kept where every tab reads it. */
7682
+ const SEED_KEY = 'ethlete:query:devtools:auth-tab-session:v1';
7683
+ const DEFAULT_FIELDS = [
7684
+ { name: 'email', type: 'email' },
7685
+ { name: 'password', type: 'password' },
7686
+ ];
7687
+ const EMPTY = { sessions: [], credentials: {}, accounts: [] };
7688
+ const store = /* @__PURE__ */ signal(EMPTY, /* @ts-ignore */
7689
+ ...(ngDevMode ? [{ debugName: "store" }] : /* istanbul ignore next */ []));
7690
+ const active = /* @__PURE__ */ signal({}, /* @ts-ignore */
7691
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
7692
+ const declared = /* @__PURE__ */ signal([], /* @ts-ignore */
7693
+ ...(ngDevMode ? [{ debugName: "declared" }] : /* istanbul ignore next */ []));
7694
+ const providers = /* @__PURE__ */ signal([], /* @ts-ignore */
7695
+ ...(ngDevMode ? [{ debugName: "providers" }] : /* istanbul ignore next */ []));
7696
+ const tabLocal = /* @__PURE__ */ signal({}, /* @ts-ignore */
7697
+ ...(ngDevMode ? [{ debugName: "tabLocal" }] : /* istanbul ignore next */ []));
7698
+ const live = /* @__PURE__ */ new Map();
7699
+ let idCounter = 0;
7700
+ const nextId = (prefix) => `${prefix}-${Date.now().toString(36)}-${++idCounter}`;
7701
+ /**
7702
+ * Every session the vault holds, whatever env or provider it belongs to. Read
7703
+ * {@link queryDevtoolsAuthSessionsFor} instead where a provider is in hand.
7704
+ *
7705
+ * Part of the devtools contract. **Not part of the general public contract.**
7706
+ */
7707
+ const queryDevtoolsAuthSessions = /* @__PURE__ */ computed(() => store().sessions, /* @ts-ignore */
7708
+ ...(ngDevMode ? [{ debugName: "queryDevtoolsAuthSessions" }] : /* istanbul ignore next */ []));
7709
+ /**
7710
+ * Which stored session each provider's live tokens belong to, keyed by provider name. This tab's own
7711
+ * answer: another tab on another user has its own.
7712
+ *
7713
+ * Part of the devtools contract. **Not part of the general public contract.**
7714
+ */
7715
+ const queryDevtoolsAuthActive = /* @__PURE__ */ active.asReadonly();
7716
+ /**
7717
+ * Which providers hold a session of their own rather than the one every tab shares, keyed by provider
7718
+ * name.
7719
+ *
7720
+ * Part of the devtools contract. **Not part of the general public contract.**
7721
+ */
7722
+ const queryDevtoolsAuthTabLocal = /* @__PURE__ */ tabLocal.asReadonly();
7723
+ /** The auth providers the vault has been handed, in registration order. @internal */
7724
+ const queryDevtoolsAuthProviders = /* @__PURE__ */ providers.asReadonly();
7725
+ const scopeOf = () => queryDevtoolsApiEnvScope();
7726
+ const persist = () => {
7727
+ const scope = queryDevtoolsSettings().authSessions;
7728
+ writeQueryDevtoolsStore(scope, STORE_KEY, store());
7729
+ writeQueryDevtoolsStore(scope === 'none' ? 'none' : 'session', ACTIVE_KEY, active());
7730
+ };
7731
+ const setActive = (provider, sessionId) => {
7732
+ active.update((current) => ({ ...current, [provider]: sessionId }));
7733
+ };
7734
+ const readSeeds = () => readQueryDevtoolsStore('session', SEED_KEY) ?? {};
7735
+ const writeSeeds = (seeds) => {
7736
+ if (Object.keys(seeds).length)
7737
+ writeQueryDevtoolsStore('session', SEED_KEY, seeds);
7738
+ else
7739
+ queryDevtoolsStorage('session')?.removeItem(SEED_KEY);
7740
+ };
7741
+ /** The claims a session is named and recognised by, or nothing for a token that is not a JWT. */
7742
+ const identityOf = (accessToken) => {
7743
+ try {
7744
+ const payload = decryptBearer(accessToken);
7745
+ if (!payload)
7746
+ return { label: null, subject: null, expiresAt: null };
7747
+ const named = ['name', 'preferred_username', 'username', 'email', 'sub'].find((claim) => typeof payload[claim] === 'string' && payload[claim] !== '');
7748
+ return {
7749
+ label: named ? String(payload[named]) : null,
7750
+ subject: typeof payload['sub'] === 'string' ? payload['sub'] : null,
7751
+ expiresAt: typeof payload['exp'] === 'number' ? payload['exp'] : null,
7752
+ };
7753
+ }
7754
+ catch {
7755
+ return { label: null, subject: null, expiresAt: null };
7756
+ }
7757
+ };
7758
+ const sanitizeSession = (value) => {
7759
+ if (typeof value !== 'object' || value === null)
7760
+ return null;
7761
+ const session = value;
7762
+ if (typeof session.id !== 'string' ||
7763
+ typeof session.provider !== 'string' ||
7764
+ typeof session.accessToken !== 'string' ||
7765
+ typeof session.refreshToken !== 'string') {
7766
+ return null;
7767
+ }
7768
+ return {
7769
+ id: session.id,
7770
+ provider: session.provider,
7771
+ label: typeof session.label === 'string' ? session.label : session.id,
7772
+ scope: typeof session.scope === 'string' ? session.scope : 'default',
7773
+ accessToken: session.accessToken,
7774
+ refreshToken: session.refreshToken,
7775
+ subject: typeof session.subject === 'string' ? session.subject : null,
7776
+ expiresAt: typeof session.expiresAt === 'number' ? session.expiresAt : null,
7777
+ savedAt: typeof session.savedAt === 'number' ? session.savedAt : 0,
7778
+ };
7779
+ };
7780
+ const sanitize = (value) => ({
7781
+ sessions: Array.isArray(value?.sessions)
7782
+ ? value.sessions.map(sanitizeSession).filter((session) => session !== null)
7783
+ : [],
7784
+ credentials: typeof value?.credentials === 'object' && value.credentials !== null ? value.credentials : {},
7785
+ accounts: Array.isArray(value?.accounts) ? value.accounts : [],
7786
+ });
7787
+ /**
7788
+ * The vault is one key every tab writes, so a tab holding a stale copy of it would drop whatever another
7789
+ * tab stored since. The event fires in the other tabs only, which is exactly the ones that need it.
7790
+ */
7791
+ const listenForOtherTabs = () => {
7792
+ if (typeof window === 'undefined' || listening)
7793
+ return;
7794
+ listening = true;
7795
+ window.addEventListener('storage', (event) => {
7796
+ if (event.key !== STORE_KEY)
7797
+ return;
7798
+ store.set(sanitize(event.newValue ? JSON.parse(event.newValue) : null));
7799
+ syncPill();
7800
+ });
7801
+ };
7802
+ let listening = false;
7803
+ /**
7804
+ * Declares the accounts the panel may log in as, and reads back what a previous page stored. Called by
7805
+ * `provideQueryDevtools()` and by nothing else.
7806
+ * @internal
7807
+ */
7808
+ const initQueryDevtoolsAuthSessions = (accounts) => {
7809
+ const scope = queryDevtoolsSettings().authSessions;
7810
+ declared.set(accounts ?? []);
7811
+ store.set(sanitize(readQueryDevtoolsStore(scope, STORE_KEY)));
7812
+ active.set(readQueryDevtoolsStore(scope === 'none' ? 'none' : 'session', ACTIVE_KEY) ?? {});
7813
+ listenForOtherTabs();
7814
+ syncPill();
7815
+ };
7816
+ /**
7817
+ * The session the panel put in this tab alone, or `null`. A seed from another backend is dropped rather
7818
+ * than applied: its tokens were issued by an API this page is not pointed at any more.
7819
+ * @internal
7820
+ */
7821
+ const readQueryDevtoolsAuthSeedFor = (providerName) => {
7822
+ const seeds = readSeeds();
7823
+ const seed = seeds[providerName];
7824
+ if (!seed)
7825
+ return null;
7826
+ if (seed.scope !== scopeOf()) {
7827
+ const { [providerName]: _dropped, ...rest } = seeds;
7828
+ writeSeeds(rest);
7829
+ return null;
7830
+ }
7831
+ tabLocal.update((current) => ({ ...current, [providerName]: true }));
7832
+ setActive(providerName, seed.sessionId);
7833
+ return { accessToken: seed.accessToken, refreshToken: seed.refreshToken };
7834
+ };
7835
+ /**
7836
+ * Keeps the stored session in step with the live one: every rotation of a refresh token is written back,
7837
+ * so switching away and back later does not present a token the server has already spent.
7838
+ * @internal
7839
+ */
7840
+ const trackQueryDevtoolsAuthProvider = (registration) => {
7841
+ const { name, handle, injector, isTabLocalSession } = registration;
7842
+ live.set(name, registration);
7843
+ providers.update((current) => (current.includes(name) ? current : [...current, name]));
7844
+ isTabLocalSession.set(tabLocal()[name] === true);
7845
+ const stop = effect(() => {
7846
+ const accessToken = handle.accessToken();
7847
+ const refreshToken = handle.refreshToken();
7848
+ untracked(() => {
7849
+ if (accessToken && refreshToken)
7850
+ remember({ provider: name, accessToken, refreshToken });
7851
+ else
7852
+ forgetActive(name);
7853
+ });
7854
+ }, { ...(ngDevMode ? { debugName: "stop" } : /* istanbul ignore next */ {}), injector });
7855
+ syncPill();
7856
+ return () => {
7857
+ stop.destroy();
7858
+ live.delete(name);
7859
+ providers.update((current) => current.filter((entry) => entry !== name));
7860
+ syncPill();
7861
+ };
7862
+ };
7863
+ /**
7864
+ * Writes a live token pair into the vault. A pair whose subject is already stored updates that session
7865
+ * rather than adding another, which is what lets a plain login in the application fill the picker.
7866
+ */
7867
+ const remember = (options) => {
7868
+ const { provider, accessToken, refreshToken } = options;
7869
+ // Nobody's real credentials or tokens are kept on this machine. See the Auth tab's own warning.
7870
+ if (queryDevtoolsApiEnvIsProduction())
7871
+ return;
7872
+ const scope = scopeOf();
7873
+ const identity = identityOf(accessToken);
7874
+ const activeId = active()[provider] ?? null;
7875
+ const activeSession = store().sessions.find((session) => session.id === activeId);
7876
+ const bySubject = identity.subject
7877
+ ? store().sessions.find((session) => session.provider === provider && session.scope === scope && session.subject === identity.subject)
7878
+ : undefined;
7879
+ // The session on record is only a rotation of the live one while both name the same user. Logging in as
7880
+ // somebody else without logging out first would otherwise overwrite the tokens of the user this tab is
7881
+ // switching away from - which is the one pair the vault exists to keep.
7882
+ const isRotation = !activeSession || !identity.subject || !activeSession.subject || activeSession.subject === identity.subject;
7883
+ const target = isRotation ? (activeSession ?? bySubject) : bySubject;
7884
+ const session = {
7885
+ id: target?.id ?? nextId('session'),
7886
+ provider,
7887
+ label: target?.label ?? identity.label ?? 'session',
7888
+ scope,
7889
+ accessToken,
7890
+ refreshToken,
7891
+ subject: identity.subject,
7892
+ expiresAt: identity.expiresAt,
7893
+ savedAt: Date.now(),
7894
+ };
7895
+ store.update((current) => ({
7896
+ ...current,
7897
+ sessions: target
7898
+ ? current.sessions.map((entry) => (entry.id === target.id ? session : entry))
7899
+ : [...current.sessions, session],
7900
+ }));
7901
+ setActive(provider, session.id);
7902
+ if (tabLocal()[provider]) {
7903
+ writeSeeds({ ...readSeeds(), [provider]: { sessionId: session.id, scope, accessToken, refreshToken } });
7904
+ }
7905
+ persist();
7906
+ syncPill();
7907
+ };
7908
+ /** Lets go of the session a provider was on, without forgetting what the vault holds of it. */
7909
+ const forgetActive = (provider) => {
7910
+ if (!active()[provider])
7911
+ return;
7912
+ setActive(provider, null);
7913
+ persist();
7914
+ syncPill();
7915
+ };
7916
+ /** The sessions offered for one provider, which are the ones this backend issued. */
7917
+ const queryDevtoolsAuthSessionsFor = (provider) => {
7918
+ const scope = scopeOf();
7919
+ return store().sessions.filter((session) => session.provider === provider && session.scope === scope);
7920
+ };
7921
+ /** How many sessions this provider has under a different backend, which is what a switch left behind. */
7922
+ const queryDevtoolsAuthOtherScopeCount = (provider) => {
7923
+ const scope = scopeOf();
7924
+ return store().sessions.filter((session) => session.provider === provider && session.scope !== scope).length;
7925
+ };
7926
+ const declaredIdOf = (account) => `declared:${account.provider}|${account.label}`;
7927
+ const viewOf = (options) => {
7928
+ const stored = store().credentials[options.id] ?? {};
7929
+ const values = Object.fromEntries(options.fields.map((field) => [field.name, stored[field.name] ?? field.default ?? '']));
7930
+ return {
7931
+ ...options,
7932
+ values,
7933
+ ready: options.fields.every((field) => values[field.name] !== ''),
7934
+ };
7935
+ };
7936
+ /**
7937
+ * The accounts on offer for one provider: the ones the application declared for the env in force, plus
7938
+ * the ones added in the panel. Empty while a production env is the pick - nothing keeps a real user's
7939
+ * password on a developer's machine.
7940
+ */
7941
+ const queryDevtoolsAuthAccountsFor = (provider) => {
7942
+ if (queryDevtoolsApiEnvIsProduction())
7943
+ return [];
7944
+ const scope = scopeOf();
7945
+ const envIds = queryDevtoolsApiEnvIds();
7946
+ const fromApp = declared()
7947
+ .filter((account) => account.provider === provider)
7948
+ .filter((account) => !account.envs?.length || account.envs.some((id) => envIds.includes(id)))
7949
+ .map((account) => viewOf({
7950
+ id: declaredIdOf(account),
7951
+ provider,
7952
+ label: account.label,
7953
+ loginQuery: account.loginQuery,
7954
+ fields: account.fields ?? DEFAULT_FIELDS,
7955
+ declared: true,
7956
+ buildArgs: account.buildArgs,
7957
+ note: account.note,
7958
+ }));
7959
+ const fromPanel = store()
7960
+ .accounts.filter((account) => account.provider === provider && account.scope === scope)
7961
+ .map((account) => viewOf({
7962
+ id: `local:${account.id}`,
7963
+ provider,
7964
+ label: account.label,
7965
+ loginQuery: account.loginQuery,
7966
+ fields: DEFAULT_FIELDS,
7967
+ declared: false,
7968
+ }));
7969
+ return [...fromApp, ...fromPanel];
7970
+ };
7971
+ /** Keeps one account's credentials on this machine. Refused while a production env is the pick. */
7972
+ const setQueryDevtoolsAuthCredentials = (options) => {
7973
+ if (queryDevtoolsApiEnvIsProduction())
7974
+ return;
7975
+ store.update((current) => ({
7976
+ ...current,
7977
+ credentials: { ...current.credentials, [options.accountId]: options.values },
7978
+ }));
7979
+ persist();
7980
+ syncPill();
7981
+ };
7982
+ /** Forgets what was typed in for one account, leaving the account itself on offer. */
7983
+ const clearQueryDevtoolsAuthCredentials = (accountId) => {
7984
+ store.update((current) => {
7985
+ const { [accountId]: _removed, ...rest } = current.credentials;
7986
+ return { ...current, credentials: rest };
7987
+ });
7988
+ persist();
7989
+ syncPill();
7990
+ };
7991
+ /** Adds an account the application does not declare, for the backend in force. */
7992
+ const addQueryDevtoolsAuthAccount = (options) => {
7993
+ const account = { ...options, id: nextId('account'), scope: scopeOf() };
7994
+ store.update((current) => ({ ...current, accounts: [...current.accounts, account] }));
7995
+ persist();
7996
+ syncPill();
7997
+ return `local:${account.id}`;
7998
+ };
7999
+ /** Removes an account added in the panel, and whatever was typed in for it. */
8000
+ const removeQueryDevtoolsAuthAccount = (accountId) => {
8001
+ const id = accountId.replace(/^local:/, '');
8002
+ store.update((current) => {
8003
+ const { [accountId]: _removed, ...credentials } = current.credentials;
8004
+ return { ...current, credentials, accounts: current.accounts.filter((account) => account.id !== id) };
8005
+ });
8006
+ persist();
8007
+ syncPill();
8008
+ };
8009
+ const renameQueryDevtoolsAuthSession = (options) => {
8010
+ store.update((current) => ({
8011
+ ...current,
8012
+ sessions: current.sessions.map((session) => session.id === options.sessionId ? { ...session, label: options.label } : session),
8013
+ }));
8014
+ persist();
8015
+ syncPill();
8016
+ };
8017
+ /** Drops one stored session. The live one it describes is left alone. */
8018
+ const forgetQueryDevtoolsAuthSession = (sessionId) => {
8019
+ store.update((current) => ({
8020
+ ...current,
8021
+ sessions: current.sessions.filter((session) => session.id !== sessionId),
8022
+ }));
8023
+ active.update((current) => Object.fromEntries(Object.entries(current).map(([provider, id]) => [provider, id === sessionId ? null : id])));
8024
+ persist();
8025
+ syncPill();
8026
+ };
8027
+ /** Empties the vault: every session, every account added in the panel, every credential. */
8028
+ const clearQueryDevtoolsAuthSessions = () => {
8029
+ store.set(EMPTY);
8030
+ active.set({});
8031
+ writeSeeds({});
8032
+ persist();
8033
+ syncPill();
8034
+ };
8035
+ const reload = () => globalThis.location?.reload();
8036
+ /**
8037
+ * Drops what the previous user left behind. Their responses are cached under keys the next user's
8038
+ * requests hit, and a bound secure query would go on rendering a body that was fetched for somebody else.
8039
+ */
8040
+ const clearClientData = (entry) => {
8041
+ const { repository } = entry.client;
8042
+ repository.unbindAllSecure();
8043
+ for (const cacheEntry of repository.subtle.cacheEntries())
8044
+ repository.subtle.evict(cacheEntry.key);
8045
+ void entry.client.clearPersistedQueries();
8046
+ };
8047
+ /**
8048
+ * Puts one stored session's tokens in force. The reload is the default because a switch only replaces
8049
+ * what the query layer holds: an application that keeps the user anywhere else - a profile service, the
8050
+ * router, an open form - is still showing the last one.
8051
+ */
8052
+ const switchQueryDevtoolsAuthSession = (options) => {
8053
+ const session = store().sessions.find((entry) => entry.id === options.sessionId);
8054
+ if (!session)
8055
+ return;
8056
+ const entry = live.get(session.provider);
8057
+ if (!entry || session.scope !== scopeOf())
8058
+ return;
8059
+ clearClientData(entry);
8060
+ setActive(session.provider, session.id);
8061
+ if (tabLocal()[session.provider]) {
8062
+ writeSeeds({
8063
+ ...readSeeds(),
8064
+ [session.provider]: {
8065
+ sessionId: session.id,
8066
+ scope: session.scope,
8067
+ accessToken: session.accessToken,
8068
+ refreshToken: session.refreshToken,
8069
+ },
8070
+ });
8071
+ }
8072
+ persist();
8073
+ entry.handle.setTokens(session.accessToken, session.refreshToken);
8074
+ syncPill();
8075
+ if (options.reload ?? queryDevtoolsSettings().reloadOnAuthSwitch)
8076
+ reload();
8077
+ };
8078
+ /**
8079
+ * Logs in as one account, through the provider's own login query - so the tokens are issued the way the
8080
+ * application issues them, and the vault picks the session up like any other login.
8081
+ */
8082
+ const loginQueryDevtoolsAuthAccount = (accountId) => {
8083
+ const account = queryDevtoolsAuthProviders()
8084
+ .flatMap((provider) => queryDevtoolsAuthAccountsFor(provider))
8085
+ .find((entry) => entry.id === accountId);
8086
+ if (!account?.ready)
8087
+ return;
8088
+ const entry = live.get(account.provider);
8089
+ const query = entry?.handle.queries?.[account.loginQuery];
8090
+ if (!entry || !query)
8091
+ return;
8092
+ clearClientData(entry);
8093
+ setActive(account.provider, null);
8094
+ persist();
8095
+ query.execute(account.buildArgs?.(account.values) ?? { body: account.values });
8096
+ };
8097
+ /** Ends the live session without forgetting it, so it can be switched back to. */
8098
+ const logoutQueryDevtoolsAuthSession = (provider) => {
8099
+ live.get(provider)?.handle.logout();
8100
+ };
8101
+ /**
8102
+ * Makes this tab's session its own, or hands it back to its siblings. Both reload: a tab that keeps its
8103
+ * own session runs no `BroadcastChannel` sync and writes no persistence cookie, and both are decided
8104
+ * while the provider is built.
8105
+ */
8106
+ const setQueryDevtoolsAuthTabLocal = (options) => {
8107
+ const { provider } = options;
8108
+ const seeds = readSeeds();
8109
+ if (!options.tabLocal) {
8110
+ const { [provider]: _dropped, ...rest } = seeds;
8111
+ writeSeeds(rest);
8112
+ reload();
8113
+ return;
8114
+ }
8115
+ const entry = live.get(provider);
8116
+ const accessToken = entry?.handle.accessToken();
8117
+ const refreshToken = entry?.handle.refreshToken();
8118
+ if (!accessToken || !refreshToken)
8119
+ return;
8120
+ const sessionId = active()[provider] ?? null;
8121
+ if (!sessionId)
8122
+ return;
8123
+ writeSeeds({ ...seeds, [provider]: { sessionId, scope: scopeOf(), accessToken, refreshToken } });
8124
+ reload();
8125
+ };
8126
+ /** What the floating pill offers, rebuilt whenever any of it changes. */
8127
+ const syncPill = () => {
8128
+ const rows = untracked(() => providers().map((provider) => {
8129
+ const sessions = queryDevtoolsAuthSessionsFor(provider);
8130
+ const accounts = queryDevtoolsAuthAccountsFor(provider);
8131
+ const activeId = active()[provider] ?? null;
8132
+ const current = sessions.find((session) => session.id === activeId);
8133
+ return {
8134
+ name: provider,
8135
+ current: current?.label ?? (live.get(provider)?.handle.accessToken() ? 'unnamed session' : 'anonymous'),
8136
+ tabLocal: tabLocal()[provider] === true,
8137
+ options: [
8138
+ ...sessions.map((session) => ({
8139
+ value: `session:${session.id}`,
8140
+ label: session.label,
8141
+ title: `Switch to ${session.label}`,
8142
+ selected: session.id === activeId,
8143
+ disabled: false,
8144
+ })),
8145
+ ...accounts.map((account) => ({
8146
+ value: `account:${account.id}`,
8147
+ label: `log in as ${account.label}`,
8148
+ title: account.ready
8149
+ ? `Logs in through ${account.loginQuery}`
8150
+ : 'Needs its credentials typed into the panel first',
8151
+ selected: false,
8152
+ disabled: !account.ready,
8153
+ })),
8154
+ ],
8155
+ pick: (value) => {
8156
+ if (value.startsWith('session:'))
8157
+ switchQueryDevtoolsAuthSession({ sessionId: value.slice(8) });
8158
+ else if (value.startsWith('account:'))
8159
+ loginQueryDevtoolsAuthAccount(value.slice(8));
8160
+ },
8161
+ toggleTabLocal: () => setQueryDevtoolsAuthTabLocal({ provider, tabLocal: !tabLocal()[provider] }),
8162
+ };
8163
+ }));
8164
+ setQueryDevtoolsAuthPill({ rows: rows.filter((row) => row.options.length > 0) });
8165
+ };
8166
+
7176
8167
  /** The neutral fault: nothing armed. */
7177
8168
  const EMPTY_QUERY_DEVTOOLS_FAULT = {
7178
8169
  latencyMs: 0,
@@ -8268,7 +9259,7 @@ const armQueryDevtoolsOverrideTransfer = (recorder, ops) => {
8268
9259
 
8269
9260
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
8270
9261
  /** The version of `@ethlete/query` this build was cut from. */
8271
- const QUERY_VERSION = '6.0.0-next.40';
9262
+ const QUERY_VERSION = '6.0.0-next.41';
8272
9263
 
8273
9264
  /** How deep a schema is expanded before a nested branch is given up on. */
8274
9265
  const MAX_DEPTH = 12;
@@ -9561,6 +10552,10 @@ const provideQueryDevtools = (options) => {
9561
10552
  initQueryDevtoolsMocks();
9562
10553
  initQueryDevtoolsFaults();
9563
10554
  setQueryDevtoolsSchemaLoader(options?.schema);
10555
+ setQueryDevtoolsApiEnvs(options?.apiEnvs);
10556
+ initQueryDevtoolsAuthSessions(options?.authAccounts);
10557
+ setQueryDevtoolsAuthSeedReader(readQueryDevtoolsAuthSeedFor);
10558
+ setQueryDevtoolsAuthProviderRegistrar(trackQueryDevtoolsAuthProvider);
9564
10559
  setQueryDevtoolsRegistrar(registerEntry);
9565
10560
  setQueryDevtoolsStatsFactory(createQueryDevtoolsStats);
9566
10561
  setQueryDevtoolsFormLinksFactory(createQueryDevtoolsFormLinks);
@@ -11861,14 +12856,14 @@ class V2Query {
11861
12856
  authProvider.tokens$.pipe(take(1)),
11862
12857
  ]).pipe(takeUntil(this.onAbort$), switchMap(([state, tokens]) => {
11863
12858
  if (state && isQueryStateLoading(state)) {
11864
- return EMPTY;
12859
+ return EMPTY$1;
11865
12860
  }
11866
12861
  if (isQueryStateFailure(state)) {
11867
12862
  this.updateEntityState({ type: 'failure', error: state.error, headers: {} }, meta, options);
11868
- return EMPTY;
12863
+ return EMPTY$1;
11869
12864
  }
11870
12865
  if (!state && !(tokens?.token || tokens?.refreshToken)) {
11871
- return EMPTY;
12866
+ return EMPTY$1;
11872
12867
  }
11873
12868
  return of(true);
11874
12869
  }), take(1));
@@ -15258,5 +16253,5 @@ const createWebSocketClient = (options) => {
15258
16253
  * Generated bundle index. Do not edit.
15259
16254
  */
15260
16255
 
15261
- export { AuthBearerRefreshStrategy, BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EMPTY_QUERY_DEVTOOLS_FAULT, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_QUERY_BATCH_TOMBSTONES, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QUERY_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStateType, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryDevtoolsOverrides, applyQueryDevtoolsTokenTtl, applyQueryFeatures, armAllQueryDevtoolsMocks, armQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, canOverrideQueryDevtoolsTokenTtl, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, clearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore, clearQueryDevtoolsTokenTtl, clearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides, collectLeafPaths, collectQueryDevtoolsSchemaComponents, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, countUnresolvedQueryDevtoolsOverrides, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDefaultRetryFn, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryBatch, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySubmission, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, currentQueryDevtoolsBatch, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, deleteQueryDevtoolsMock, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsSampleNumber, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, initQueryDevtoolsFaults, initQueryDevtoolsMocks, initQueryDevtoolsOverridePersistence, initQueryDevtoolsSettings, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isDateShapedLeaf, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed, isQueryDevtoolsRequestInterceptionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, loadQueryDevtoolsSchema, mapToPaginated, mapViolationsToFormErrors, matchesQueryDevtoolsMockPattern, matchesQueryDevtoolsMockQuery, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryDevtoolsOverrideTransfer, parseQueryRoute, patchQueryDevtoolsTokenPayload, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryBatchAlreadyRunning, queryBatchWithArgsUsed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsAbout, queryDevtoolsArmedMocks, queryDevtoolsArmedMocksRestored, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFaultsRestored, queryDevtoolsFnDetail, queryDevtoolsMockId, queryDevtoolsMocks, queryDevtoolsOverridePersistence, queryDevtoolsRequestPath, queryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsSettings, queryDevtoolsStorage, queryDevtoolsTokenTtls, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, readQueryDevtoolsStore, registerEthleteVersion, registerQueryDevtoolsEntry, registerQueryErrorParser, removeFrom, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, resolveQueryDevtoolsMock, resolveQueryDevtoolsMockForAttempt, restoredQueryDevtoolsOverrides, runDefaultQueryRetry, runInQueryDevtoolsBatch, runQueryErrorParsers, saveQueryDevtoolsMock, searchQueryField, seedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute, serializeBody, serializeQueryDevtoolsOverrideTransfer, setDefaultQueryRetryFn, setQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFaultsScope, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsMockResolver, setQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesFactory, setQueryDevtoolsOverridesScope, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsSchemaLoader, setQueryDevtoolsSettings, setQueryDevtoolsStatsFactory, setQueryDevtoolsTokenPayloadPatcher, setQueryDevtoolsTokenTtl, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, smartDuplicateArrayItem, snapshotQueryDevtoolsHandle, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRouteParts, sumQueryDevtoolsStats, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryState, symfonyQueryErrorParser, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, tombstoneOf, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, unwrapQuerySyncMessage, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withBearerAuthMultiTabSync, withDefaultRetry, withErrorHandling, withEthleteApiErrors, withHtmlErrorParsing, withInactivityLogout, withLogging, withLongPolling, withLongPollingUsedOnUnsupportedHttpMethod, withLongPollingUsedWithPolling, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryDevtoolsOverridePersistence, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, writeQueryDevtoolsStore };
16256
+ export { AuthBearerRefreshStrategy, BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DEFAULT_QUERY_PERSISTENCE_MAX_AGE, DEFAULT_QUERY_PERSISTENCE_MAX_ENTRIES, DEFAULT_QUERY_PERSISTENCE_VERSION, DEFAULT_QUERY_PERSISTENCE_WRITE_DELAY, DateArrayQueryField, DateQueryField, EMPTY_QUERY_DEVTOOLS_FAULT, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_QUERY_BATCH_TOMBSTONES, MAX_QUERY_DEVTOOLS_TOMBSTONES, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QUERY_DEVTOOLS_FAULT_STATUSES, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, QUERY_PERSISTENCE_STORE_VERSION, QUERY_SYNC_PROTOCOL_VERSION, QUERY_VERSION, QueryClientFeatureType, QueryDevtoolsComponent, QueryDirective, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStateType, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, addQueryDevtoolsAuthAccount, applyQueryDevtoolsOverrides, applyQueryDevtoolsTokenTtl, applyQueryFeatures, armAllQueryDevtoolsMocks, armQueryDevtoolsMock, armQueryDevtoolsOverrideTransfer, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, canOverrideQueryDevtoolsTokenTtl, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthSessions, clearQueryDevtoolsFaults, clearQueryDevtoolsMockStore, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsStore, clearQueryDevtoolsTokenTtl, clearQueryDevtoolsTombstones, clearRestoredQueryDevtoolsOverrides, collectLeafPaths, collectQueryDevtoolsSchemaComponents, collectQueryFormLinks, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, countUnresolvedQueryDevtoolsOverrides, createAuthGuard, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDefaultRetryFn, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createIndexedDbQueryPersistenceAdapter, createInfinityQueryConfig, createLegacyQueryCreator, createNoopQueryPersistenceAdapter, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryBatch, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryDevtoolsFormLinks, createQueryDevtoolsFormLinksRecorder, createQueryDevtoolsOverrides, createQueryDevtoolsOverridesRecorder, createQueryDevtoolsStats, createQueryDevtoolsStatsRecorder, createQueryErrorResponse, createQueryFeature, createQueryInvalidationFilter, createQueryKeyLockManager, createQueryObject, createQueryPersistenceEngine, createQueryRepository, createQuerySnapshotFn, createQueryStack, createQuerySubmission, createQuerySyncEngine, createQuerySyncTransport, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, currentQueryDevtoolsBatch, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, defineQueryForm, deleteQueryDevtoolsMock, describeQueryDevtoolsFeatures, detectContentTypeHeader, detectPaginationShape, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractHtmlErrorMessage, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, forgetQueryDevtoolsAuthSession, formatQueryDevtoolsDuration, generateQueryDevtoolsNumberPreset, generateQueryDevtoolsSampleNumber, generateQueryDevtoolsStringPreset, getBearerAuthProviderName, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, hasQueryDevtoolsOverridesAtPath, htmlErrorPayload, htmlQueryErrorParser, ignoreAutoRefresh, initQueryDevtoolsAuthSessions, initQueryDevtoolsFaults, initQueryDevtoolsMocks, initQueryDevtoolsOverridePersistence, initQueryDevtoolsSettings, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isDateShapedLeaf, isEmptyString, isGqlQueryConfig, isHtmlErrorPayload, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryDevtoolsFaultArmed, isQueryDevtoolsRequestInterceptionEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, isUnderInvalidatedUrl, legacyPrepareFallbackInjector, legacyPrepareWithoutInjectionContext, loadQueryDevtoolsSchema, loginQueryDevtoolsAuthAccount, logoutQueryDevtoolsAuthSession, mapToPaginated, mapViolationsToFormErrors, matchesQueryDevtoolsMockPattern, matchesQueryDevtoolsMockQuery, maybeExecute, measureQueryDevtoolsPayload, mergeHeaders, nestedEffect, noteQueryFormRead, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, parseQueryDevtoolsOverrideTransfer, parseQueryRoute, patchQueryDevtoolsTokenPayload, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideLegacyPrepareFallback, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryBatchAlreadyRunning, queryBatchWithArgsUsed, queryClientFeatureUsedMultipleTimes, queryComputed, queryComputedTillTruthy, queryDevtoolsAbout, queryDevtoolsApiEnvIds, queryDevtoolsApiEnvIsProduction, queryDevtoolsApiEnvScope, queryDevtoolsApiEnvValues, queryDevtoolsApiEnvs, queryDevtoolsArmedMocks, queryDevtoolsArmedMocksRestored, queryDevtoolsAuthAccountsFor, queryDevtoolsAuthActive, queryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthProviders, queryDevtoolsAuthSessions, queryDevtoolsAuthSessionsFor, queryDevtoolsAuthTabLocal, queryDevtoolsEntries, queryDevtoolsFaults, queryDevtoolsFaultsRestored, queryDevtoolsFnDetail, queryDevtoolsMockId, queryDevtoolsMocks, queryDevtoolsOverridePersistence, queryDevtoolsRequestPath, queryDevtoolsResponseHistory, queryDevtoolsRestoredOverridesScope, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, queryDevtoolsSettings, queryDevtoolsStorage, queryDevtoolsTokenTtls, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, readQueryDevtoolsAuthSeed, readQueryDevtoolsAuthSeedFor, readQueryDevtoolsStore, registerEthleteVersion, registerQueryDevtoolsAuthProvider, registerQueryDevtoolsEntry, registerQueryErrorParser, removeFrom, removeQueryDevtoolsAuthAccount, renameQueryDevtoolsAuthSession, request, resetExecuteState, resetPageOnError, resolveInvalidationUrl, resolveQueryDevtoolsFault, resolveQueryDevtoolsFaultForAttempt, resolveQueryDevtoolsMock, resolveQueryDevtoolsMockForAttempt, restoredQueryDevtoolsOverrides, runDefaultQueryRetry, runInQueryDevtoolsBatch, runQueryErrorParsers, saveQueryDevtoolsMock, searchQueryField, seedQueryDevtoolsSchemaBody, seedQueryDevtoolsSchemaRoute, serializeBody, serializeQueryDevtoolsOverrideTransfer, setDefaultQueryRetryFn, setQueryDevtoolsApiEnv, setQueryDevtoolsApiEnvs, setQueryDevtoolsAppInfo, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsAuthCredentials, setQueryDevtoolsAuthProviderRegistrar, setQueryDevtoolsAuthSeedReader, setQueryDevtoolsAuthTabLocal, setQueryDevtoolsFault, setQueryDevtoolsFaultResolver, setQueryDevtoolsFaultsScope, setQueryDevtoolsFormLinksFactory, setQueryDevtoolsMockResolver, setQueryDevtoolsOverridePersistence, setQueryDevtoolsOverridesFactory, setQueryDevtoolsOverridesScope, setQueryDevtoolsRegistrar, setQueryDevtoolsResponseHistory, setQueryDevtoolsSchemaLoader, setQueryDevtoolsSettings, setQueryDevtoolsStatsFactory, setQueryDevtoolsTokenPayloadPatcher, setQueryDevtoolsTokenTtl, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, smartDuplicateArrayItem, snapshotQueryDevtoolsHandle, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRouteParts, sumQueryDevtoolsStats, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryDevtoolsAuthSession, switchQueryState, symfonyQueryErrorParser, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, tombstoneOf, trackQueryDevtoolsAuthProvider, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, unwrapQuerySyncMessage, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withBearerAuthMultiTabSync, withDefaultRetry, withErrorHandling, withEthleteApiErrors, withHtmlErrorParsing, withInactivityLogout, withLogging, withLongPolling, withLongPollingUsedOnUnsupportedHttpMethod, withLongPollingUsedWithPolling, withMultiTabSync, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withQueryDevtoolsOverridePersistence, withQueryPersistence, withRefreshQuery, withResponseUpdate, withSuccessHandling, withSymfonyErrors, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal, wrapQuerySyncMessage, writeQueryDevtoolsStore };
15262
16257
  //# sourceMappingURL=ethlete-query.mjs.map