@iqauth/sdk 2.6.4 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +173 -1
  2. package/dist/browser-session.d.mts +4 -4
  3. package/dist/browser-session.d.ts +4 -4
  4. package/dist/browser-session.js +212 -46
  5. package/dist/browser-session.mjs +3 -3
  6. package/dist/browser.d.mts +5 -5
  7. package/dist/browser.d.ts +5 -5
  8. package/dist/browser.js +293 -34
  9. package/dist/browser.mjs +5 -5
  10. package/dist/{chunk-BVV54LPI.mjs → chunk-25SSYDIP.mjs} +10 -4
  11. package/dist/{chunk-XAWYUPMO.mjs → chunk-4V7FKOTG.mjs} +242 -22
  12. package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
  13. package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
  14. package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
  15. package/dist/chunk-GLXSIGVS.mjs +66 -0
  16. package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
  17. package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
  18. package/dist/chunk-JRDVUWAL.mjs +46 -0
  19. package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
  20. package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
  21. package/dist/chunk-VYQ3ETCK.mjs +244 -0
  22. package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
  23. package/dist/chunk-WHT6WKTY.mjs +3180 -0
  24. package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
  25. package/dist/chunk-WSH4SW7F.mjs +490 -0
  26. package/dist/{chunk-W3F4JYGP.mjs → chunk-ZLJPABB7.mjs} +139 -23
  27. package/dist/cli/index.js +2 -2
  28. package/dist/cli/index.mjs +2 -2
  29. package/dist/{client-BNQe3AgF.d.ts → client-D8L-PaWr.d.mts} +59 -6
  30. package/dist/{client-kYlJFgPv.d.mts → client-DkPL0EPZ.d.ts} +59 -6
  31. package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
  32. package/dist/errors-Jl1Jtm-6.d.mts +107 -0
  33. package/dist/errors-Jl1Jtm-6.d.ts +107 -0
  34. package/dist/{express-CHpfa7D_.d.ts → express-Budysq4h.d.ts} +2 -2
  35. package/dist/{express-B6_1vBYZ.d.mts → express-DDTA3qV1.d.mts} +2 -2
  36. package/dist/express.d.mts +7 -6
  37. package/dist/express.d.ts +7 -6
  38. package/dist/express.js +563 -85
  39. package/dist/express.mjs +73 -34
  40. package/dist/fastify.d.mts +10 -0
  41. package/dist/fastify.d.ts +10 -0
  42. package/dist/fastify.js +589 -65
  43. package/dist/fastify.mjs +101 -11
  44. package/dist/hono.d.mts +10 -0
  45. package/dist/hono.d.ts +10 -0
  46. package/dist/hono.js +566 -65
  47. package/dist/hono.mjs +78 -11
  48. package/dist/index-Cko-d5po.d.mts +1848 -0
  49. package/dist/index-RNqwEcmY.d.ts +1848 -0
  50. package/dist/index.d.mts +56 -8
  51. package/dist/index.d.ts +56 -8
  52. package/dist/index.js +694 -75
  53. package/dist/index.mjs +30 -10
  54. package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
  55. package/dist/locales.d.mts +1 -1
  56. package/dist/locales.d.ts +1 -1
  57. package/dist/locales.js +36 -0
  58. package/dist/locales.mjs +1 -1
  59. package/dist/mobile.d.mts +77 -7
  60. package/dist/mobile.d.ts +77 -7
  61. package/dist/mobile.js +307 -46
  62. package/dist/mobile.mjs +98 -3
  63. package/dist/next.d.mts +10 -1
  64. package/dist/next.d.ts +10 -1
  65. package/dist/next.js +596 -205
  66. package/dist/next.mjs +83 -10
  67. package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
  68. package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
  69. package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
  70. package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
  71. package/dist/react-permissions.d.mts +52 -0
  72. package/dist/react-permissions.d.ts +52 -0
  73. package/dist/react-permissions.js +239 -0
  74. package/dist/react-permissions.mjs +98 -0
  75. package/dist/react.d.mts +9 -1624
  76. package/dist/react.d.ts +9 -1624
  77. package/dist/react.js +882 -73
  78. package/dist/react.mjs +71 -2631
  79. package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
  80. package/dist/server/handlers.d.mts +200 -4
  81. package/dist/server/handlers.d.ts +200 -4
  82. package/dist/server/handlers.js +530 -16
  83. package/dist/server/handlers.mjs +14 -3
  84. package/dist/server.d.mts +171 -8
  85. package/dist/server.d.ts +171 -8
  86. package/dist/server.js +579 -61
  87. package/dist/server.mjs +99 -12
  88. package/dist/service.d.mts +4 -4
  89. package/dist/service.d.ts +4 -4
  90. package/dist/service.js +212 -46
  91. package/dist/service.mjs +3 -3
  92. package/dist/{signIn-CiIBTJIh.d.mts → signIn-CReqfXsh.d.mts} +95 -3
  93. package/dist/{signIn-OCr88Zf8.d.ts → signIn-Cfa1GTpO.d.ts} +95 -3
  94. package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
  95. package/dist/test.mjs +3 -3
  96. package/dist/{tokens-DCyzzn8L.d.mts → tokens-9F6ETrzk.d.ts} +9 -2
  97. package/dist/{tokens-aHiGFr_E.d.ts → tokens-B06VtvUi.d.mts} +9 -2
  98. package/dist/{types-DZAflmmq.d.mts → types-Bn8O-OEd.d.mts} +164 -11
  99. package/dist/{types-DZAflmmq.d.ts → types-Bn8O-OEd.d.ts} +164 -11
  100. package/dist/{types-6bNdxesb.d.ts → types-DnU2LhXR.d.mts} +7 -1
  101. package/dist/{types-6bNdxesb.d.mts → types-DnU2LhXR.d.ts} +7 -1
  102. package/dist/webhooks.d.mts +113 -17
  103. package/dist/webhooks.d.ts +113 -17
  104. package/dist/webhooks.js +179 -15
  105. package/dist/webhooks.mjs +7 -1
  106. package/dist/ws.d.mts +2 -2
  107. package/dist/ws.d.ts +2 -2
  108. package/dist/ws.js +80 -30
  109. package/dist/ws.mjs +4 -4
  110. package/docs/error-handling.md +101 -0
  111. package/docs/guides/effective-permissions.md +171 -0
  112. package/docs/guides/invitations.md +65 -0
  113. package/package.json +19 -4
  114. package/dist/chunk-6TDJJER7.mjs +0 -217
  115. package/dist/chunk-UKZLOHZG.mjs +0 -83
  116. package/dist/errors-CDdl24MP.d.mts +0 -52
  117. package/dist/errors-CDdl24MP.d.ts +0 -52
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  TokensModule
3
- } from "./chunk-UNYDG2L4.mjs";
3
+ } from "./chunk-NUO2I65G.mjs";
4
4
  import {
5
5
  IQAuthError
6
- } from "./chunk-6I6RM4MN.mjs";
6
+ } from "./chunk-6PJRLRB4.mjs";
7
7
 
8
8
  // src/modules/auth.ts
9
9
  function parseLoginResponse(data, browserSessionMode) {
@@ -36,17 +36,27 @@ function parseLoginResponse(data, browserSessionMode) {
36
36
  tenants: data.tenants
37
37
  };
38
38
  }
39
+ if (data.type === "scope_selection" && data.scopeSelectionToken && data.scopes && data.tenantId) {
40
+ return {
41
+ status: "scope_selection",
42
+ scopeSelectionToken: data.scopeSelectionToken,
43
+ tenantId: data.tenantId,
44
+ scopes: data.scopes
45
+ };
46
+ }
39
47
  throw new Error("Unexpected login response shape");
40
48
  }
41
49
  var AuthModule = class {
42
50
  constructor(http) {
43
51
  this.http = http;
44
52
  }
45
- async login(email, password) {
53
+ async login(email, password, opts) {
54
+ const body = { email, password };
55
+ if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
46
56
  const data = await this.http.request(
47
57
  "POST",
48
58
  "/api/v1/auth/login",
49
- { email, password },
59
+ body,
50
60
  { skipAutoRefresh: true }
51
61
  );
52
62
  return parseLoginResponse(data, this.http.isBrowserSession());
@@ -84,13 +94,29 @@ var AuthModule = class {
84
94
  method
85
95
  }, { skipAutoRefresh: true });
86
96
  }
87
- async selectTenant(tenantSelectionToken, tenantId) {
97
+ async selectTenant(tenantSelectionToken, tenantId, opts) {
98
+ const body = { tenantSelectionToken, tenantId };
99
+ if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
88
100
  const data = await this.http.request(
89
101
  "POST",
90
102
  "/api/v1/auth/select-tenant",
103
+ body,
104
+ { skipAutoRefresh: true }
105
+ );
106
+ return parseLoginResponse(data, this.http.isBrowserSession());
107
+ }
108
+ /**
109
+ * Task #171 — redeem a scope-selection token + chosen membership for a
110
+ * real authenticated session. `membershipId` must be one of the scopes
111
+ * returned in the prior `scope_selection` envelope.
112
+ */
113
+ async selectScope(scopeSelectionToken, membershipId) {
114
+ const data = await this.http.request(
115
+ "POST",
116
+ "/api/v1/auth/select-scope",
91
117
  {
92
- tenantSelectionToken,
93
- tenantId
118
+ scopeSelectionToken,
119
+ membershipId
94
120
  },
95
121
  { skipAutoRefresh: true }
96
122
  );
@@ -484,14 +510,14 @@ var OidcModule = class {
484
510
  */
485
511
  async handleCallback(params) {
486
512
  if (!params.state) {
487
- throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing state parameter");
513
+ throw new IQAuthError("config_invalid", "OIDC callback missing state parameter");
488
514
  }
489
515
  if (!params.code) {
490
- throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing code parameter");
516
+ throw new IQAuthError("config_invalid", "OIDC callback missing code parameter");
491
517
  }
492
518
  const stored = await this.stateStore.get(params.state);
493
519
  if (!stored) {
494
- throw new IQAuthError("VALIDATION_ERROR", "Unknown or expired OIDC state");
520
+ throw new IQAuthError("config_invalid", "Unknown or expired OIDC state");
495
521
  }
496
522
  let tokens;
497
523
  try {
@@ -509,7 +535,7 @@ var OidcModule = class {
509
535
  if (tokens.id_token) {
510
536
  if (!this.tokensModule) {
511
537
  throw new IQAuthError(
512
- "INTERNAL_ERROR",
538
+ "config_invalid",
513
539
  "OIDC handleCallback received an id_token but no TokensModule is configured for verification"
514
540
  );
515
541
  }
@@ -520,7 +546,7 @@ var OidcModule = class {
520
546
  const tokenNonce = typeof claimsBag.nonce === "string" ? claimsBag.nonce : void 0;
521
547
  if (!tokenNonce || tokenNonce !== stored.nonce) {
522
548
  throw new IQAuthError(
523
- "TOKEN_INVALID",
549
+ "token_invalid",
524
550
  "OIDC id_token nonce did not match the stored value"
525
551
  );
526
552
  }
@@ -721,6 +747,9 @@ var AppsModule = class {
721
747
  * @remarks Wraps GET /api/v1/apps/:appKey
722
748
  */
723
749
  async get(appKey) {
750
+ if (typeof appKey !== "string" || appKey.trim() === "") {
751
+ throw new IQAuthError("VALIDATION_ERROR", "appKey is required (no env-var fallback).", 400);
752
+ }
724
753
  return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(appKey)}`);
725
754
  }
726
755
  /**
@@ -740,6 +769,16 @@ var AppsModule = class {
740
769
  401
741
770
  );
742
771
  }
772
+ if (!manifest || typeof manifest.key !== "string" || manifest.key.trim() === "") {
773
+ throw new IQAuthError("VALIDATION_ERROR", "manifest.key (appKey) is required for register().", 400);
774
+ }
775
+ if (!manifest.environment || !["production", "staging", "development"].includes(manifest.environment)) {
776
+ throw new IQAuthError(
777
+ "ENVIRONMENT_REQUIRED",
778
+ "manifest.environment is required and must be 'production', 'staging', or 'development'. This guards against a dev workstation silently overwriting a production app's permission tree.",
779
+ 400
780
+ );
781
+ }
743
782
  return this.http.request("POST", "/api/v1/apps/sync", manifest);
744
783
  }
745
784
  /**
@@ -749,11 +788,14 @@ var AppsModule = class {
749
788
  * @remarks Uses GET /api/v1/apps/:appKey — catches 404 errors
750
789
  */
751
790
  async isRegistered(appKey) {
791
+ if (typeof appKey !== "string" || appKey.trim() === "") {
792
+ throw new IQAuthError("VALIDATION_ERROR", "appKey is required (no env-var fallback).", 400);
793
+ }
752
794
  try {
753
795
  await this.get(appKey);
754
796
  return true;
755
797
  } catch (err) {
756
- if (err.code === "NOT_FOUND" || err.status === 404) {
798
+ if (err.code === "app_not_found" || err.code === "NOT_FOUND" || err.status === 404) {
757
799
  return false;
758
800
  }
759
801
  throw err;
@@ -790,6 +832,20 @@ var RolesModule = class {
790
832
  };
791
833
 
792
834
  // src/modules/permissionGroups.ts
835
+ function assertAppKey(appKey, callsite) {
836
+ if (typeof appKey !== "string" || appKey.trim() === "") {
837
+ throw new IQAuthError(
838
+ "VALIDATION_ERROR",
839
+ `appKey is required for ${callsite} (no env-var fallback, no 'product' alias).`,
840
+ 400
841
+ );
842
+ }
843
+ }
844
+ function assertNodeKey(nodeKey, callsite) {
845
+ if (typeof nodeKey !== "string" || nodeKey.trim() === "") {
846
+ throw new IQAuthError("VALIDATION_ERROR", `nodeKey is required for ${callsite}.`, 400);
847
+ }
848
+ }
793
849
  var PermissionGroupsModule = class {
794
850
  constructor(http) {
795
851
  this.http = http;
@@ -810,7 +866,14 @@ var PermissionGroupsModule = class {
810
866
  return this.http.request("GET", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`);
811
867
  }
812
868
  async addPermission(tenantId, groupId, data) {
813
- return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`, data);
869
+ assertAppKey(data?.appKey, "permissionGroups.addPermission");
870
+ assertNodeKey(data?.nodeKey, "permissionGroups.addPermission");
871
+ return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`, {
872
+ appKey: data.appKey,
873
+ nodeKey: data.nodeKey,
874
+ effect: data.effect,
875
+ weight: data.weight
876
+ });
814
877
  }
815
878
  async removePermission(tenantId, groupId, permissionId) {
816
879
  return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions/${permissionId}`);
@@ -834,21 +897,51 @@ var PermissionGroupsModule = class {
834
897
  return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`);
835
898
  }
836
899
  async addUserOverride(tenantId, userId, data) {
837
- return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`, data);
900
+ assertAppKey(data?.appKey, "permissionGroups.addUserOverride");
901
+ assertNodeKey(data?.nodeKey, "permissionGroups.addUserOverride");
902
+ return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`, {
903
+ appKey: data.appKey,
904
+ nodeKey: data.nodeKey,
905
+ effect: data.effect,
906
+ weight: data.weight,
907
+ expiresAt: data.expiresAt
908
+ });
838
909
  }
839
910
  async removeUserOverride(tenantId, userId, overrideId) {
840
911
  return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides/${overrideId}`);
841
912
  }
913
+ /**
914
+ * Task #130 — `appKey` is REQUIRED. The legacy `product` query alias is no
915
+ * longer accepted at the SDK boundary; pass it as `appKey` instead. The
916
+ * server still accepts `product=` from raw HTTP callers during the
917
+ * deprecation window, but the SDK will not silently translate it.
918
+ */
842
919
  async getEffectivePermissions(tenantId, userId, params) {
843
- const query = new URLSearchParams();
844
- if (params.product) query.set("product", params.product);
845
- if (params.appKey) query.set("appKey", params.appKey);
846
- const qs = query.toString();
847
- return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/effective${qs ? `?${qs}` : ""}`);
920
+ assertAppKey(params?.appKey, "permissionGroups.getEffectivePermissions");
921
+ const qs = new URLSearchParams({ appKey: params.appKey }).toString();
922
+ return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/effective?${qs}`);
848
923
  }
849
924
  async checkPermission(tenantId, userId, appKey, nodeKey) {
925
+ assertAppKey(appKey, "permissionGroups.checkPermission");
926
+ assertNodeKey(nodeKey, "permissionGroups.checkPermission");
850
927
  return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/check`, { appKey, nodeKey });
851
928
  }
929
+ /**
930
+ * Task #130 — every entry in `checks` must include a non-empty `appKey`
931
+ * AND `nodeKey`. The SDK validates the whole batch before sending so a
932
+ * single misconfigured entry can't slip through and silently report
933
+ * `allowed: false` from the server's per-entry validation branch.
934
+ */
935
+ async batchCheckPermissions(tenantId, userId, checks) {
936
+ if (!Array.isArray(checks) || checks.length === 0) {
937
+ throw new IQAuthError("VALIDATION_ERROR", "checks must be a non-empty array of { appKey, nodeKey }.", 400);
938
+ }
939
+ checks.forEach((c, i) => {
940
+ assertAppKey(c?.appKey, `permissionGroups.batchCheckPermissions[${i}]`);
941
+ assertNodeKey(c?.nodeKey, `permissionGroups.batchCheckPermissions[${i}]`);
942
+ });
943
+ return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/batch-check`, { checks });
944
+ }
852
945
  };
853
946
 
854
947
  // src/modules/apiKeys.ts
@@ -1365,7 +1458,7 @@ var HttpClient = class {
1365
1458
  headers: this.buildHeaders(),
1366
1459
  ...this.isBrowserSession() ? { credentials: "include" } : (() => {
1367
1460
  const refreshToken = this.config.getRefreshToken();
1368
- if (!refreshToken) throw new IQAuthError("TOKEN_INVALID", "No refresh token available");
1461
+ if (!refreshToken) throw new IQAuthError("config_invalid", "No refresh token available");
1369
1462
  return { body: JSON.stringify({ refreshToken }) };
1370
1463
  })()
1371
1464
  });
@@ -1382,7 +1475,7 @@ var HttpClient = class {
1382
1475
  return;
1383
1476
  }
1384
1477
  if (!body.data.accessToken || !body.data.refreshToken) {
1385
- throw new IQAuthError("TOKEN_INVALID", "Refresh response did not include a token pair");
1478
+ throw new IQAuthError("token_invalid", "Refresh response did not include a token pair");
1386
1479
  }
1387
1480
  const tokens = {
1388
1481
  accessToken: body.data.accessToken,
@@ -1400,7 +1493,7 @@ var HttpClient = class {
1400
1493
  return this.requestWithRetry(method, path, body, options, false);
1401
1494
  }
1402
1495
  async requestWithRetry(method, path, body, options, hasRetried) {
1403
- if (this.config.autoRefresh && !options?.skipAutoRefresh && !this.isBrowserSession() && this.config.getRefreshToken() && this.isTokenExpiringSoon()) {
1496
+ if (this.config.autoRefresh && this.config.proactiveRefresh !== false && !options?.skipAutoRefresh && !this.isBrowserSession() && this.config.getRefreshToken() && this.isTokenExpiringSoon()) {
1404
1497
  await this.attemptRefresh();
1405
1498
  }
1406
1499
  const url = `${this.config.baseUrl}${path}`;
@@ -1475,6 +1568,10 @@ var IQAuthClient = class _IQAuthClient {
1475
1568
  this._refreshToken = tokens.refreshToken;
1476
1569
  },
1477
1570
  autoRefresh: "autoRefresh" in config ? config.autoRefresh !== false : true,
1571
+ // `'app-state'` is mobile-only — on any other environment we treat it
1572
+ // as the default `true` (proactive refresh ON). Only the mobile client
1573
+ // disables proactive refresh and replaces it with an AppState listener.
1574
+ proactiveRefresh: "autoRefresh" in config && config.autoRefresh === "app-state" && _IQAuthClient.resolveEnvironment(config) === "mobile" ? false : true,
1478
1575
  onTokenRefresh: "onTokenRefresh" in config ? config.onTokenRefresh : void 0,
1479
1576
  sessionHeaderName: config.sessionHeaderName,
1480
1577
  sessionHeaderValue: config.sessionHeaderValue,
@@ -1515,6 +1612,13 @@ var IQAuthClient = class _IQAuthClient {
1515
1612
  static forServer(config) {
1516
1613
  return new _IQAuthClient({ ...config, environment: "server" });
1517
1614
  }
1615
+ /**
1616
+ * Construct a mobile-environment client. NOTE: this constructor does NOT
1617
+ * subscribe to React Native's `AppState` even when `autoRefresh: 'app-state'`
1618
+ * is passed — it only disables the per-request proactive refresh. Use
1619
+ * `createMobileClient` from `@iqauth/sdk/mobile` if you want the full
1620
+ * AppState-driven refresh behavior (recommended for Expo / React Native).
1621
+ */
1518
1622
  static forMobile(config) {
1519
1623
  return new _IQAuthClient({ ...config, environment: "mobile" });
1520
1624
  }
@@ -1531,6 +1635,18 @@ var IQAuthClient = class _IQAuthClient {
1531
1635
  getRefreshToken() {
1532
1636
  return this._refreshToken;
1533
1637
  }
1638
+ /**
1639
+ * Task #126: Eagerly fetch JWKS + OIDC discovery so the first verify() /
1640
+ * refresh round-trip on the request hot path doesn't pay the discovery
1641
+ * fetch latency. Safe to call repeatedly. Errors are swallowed; callers
1642
+ * may fire-and-forget. Called automatically by `iqAuth({...}).attachHelpers()`.
1643
+ */
1644
+ async prewarm() {
1645
+ await Promise.all([
1646
+ this.tokens.prewarm(),
1647
+ this.oidc.getDiscovery().catch(() => void 0)
1648
+ ]);
1649
+ }
1534
1650
  getCurrentClaims() {
1535
1651
  if (!this._accessToken) return null;
1536
1652
  return this.tokens.decode(this._accessToken);
package/dist/cli/index.js CHANGED
@@ -538,10 +538,10 @@ async function getCtx(flags) {
538
538
  const env = await loadEnv(flags.get("env-file") || ".env");
539
539
  const baseUrl = flags.get("base-url") || env.IQAUTH_ISSUER;
540
540
  const token = flags.get("token") || env.IQAUTH_ADMIN_TOKEN || env.IQAUTH_SECRET_KEY;
541
- const app = flags.get("app") || env.IQAUTH_APP_ID || env.IQAUTH_APP_KEY;
541
+ const app = flags.get("app") || env.IQAUTH_APP_ID;
542
542
  if (!baseUrl) throw new Error("Missing --base-url (or IQAUTH_ISSUER in env).");
543
543
  if (!token) throw new Error("Missing --token (or IQAUTH_ADMIN_TOKEN / IQAUTH_SECRET_KEY in env).");
544
- if (!app) throw new Error("Missing --app <appId|appKey> (or IQAUTH_APP_ID in env).");
544
+ if (!app) throw new Error("Missing --app <appId> (or IQAUTH_APP_ID in env). The `IQAUTH_APP_KEY` env-var fallback has been removed (Task #130) \u2014 pass --app explicitly.");
545
545
  return { baseUrl, token, app };
546
546
  }
547
547
  async function runKeys(argv) {
@@ -17,12 +17,12 @@ async function run() {
17
17
  return;
18
18
  }
19
19
  case "doctor": {
20
- const { runDoctor } = await import("../doctor-YYNHNMLD.mjs");
20
+ const { runDoctor } = await import("../doctor-JAFXWU3X.mjs");
21
21
  await runDoctor(rest);
22
22
  return;
23
23
  }
24
24
  case "keys": {
25
- const { runKeys } = await import("../keys-NLWFAOEM.mjs");
25
+ const { runKeys } = await import("../keys-6Y776TG2.mjs");
26
26
  await runKeys(rest);
27
27
  return;
28
28
  }
@@ -1,5 +1,5 @@
1
- import { I as IQAuthEnvironment, T as TokenPair, W as IQAuthRetryConfig, L as LoginResult, a$ as SignupRequest, D as MfaVerifyResult, d as SessionUser, h as Session, U as UserProfile, H as ProvisionUserRequest, K as ProvisionUserResponse, G as UserPermissions, J as JwtClaims, O as OidcDiscovery, t as JwksResponse, u as OidcTokenResponse, b0 as HostedClientContext, i as TenantInfo, C as CreateTenantRequest, j as UpdateTenantRequest, P as PromoteToVendorRequest, k as PromoteToVendorResult, a7 as TenantUser, l as InviteTenantUserRequest, m as InviteTenantUserResult, n as TenantUserRoleUpdate, M as MigrateUserRequest, E as PasswordPolicy, F as MfaPolicy, B as BrandingConfig, _ as AppInfo, $ as PermissionNodeInfo, Z as AppManifest, a0 as AppSyncResult, a1 as Role, a2 as CreateRoleRequest, a3 as UpdateRoleRequest, a4 as AssignRoleRequest, a5 as UserRoleAssignment, a8 as PermissionGroup, a9 as GroupPermission, aa as AddGroupPermissionRequest, ab as InheritanceRelation, a6 as UserGroupAssignment, ac as UserPermissionOverride, ad as AddUserOverrideRequest, ae as EffectivePermission, af as PermissionCheckResult, ah as CreateApiKeyRequest, ai as CreateApiKeyResult, ag as ApiKeyInfo, aj as ApiKeyIntrospection, al as CreateInviteRequest, ak as Invitation, am as InviteValidation, an as AcceptInviteRequest, ap as CreateWebhookRequest, aq as CreateWebhookResult, ao as WebhookEndpoint, ar as WebhookDelivery, as as WebhookTestResult, at as Entitlement, au as GrantEntitlementRequest, av as Vendor, aw as CreateVendorRequest, ax as UpdateVendorRequest, az as CreateSourceRequest, ay as Source, aA as UpdateSourceRequest, aC as CreateClientRequest, aB as Client, aD as UpdateClientRequest, aE as HierarchyVendor, aH as HierarchyLink, aL as MembershipWithDetails, aJ as CreateMembershipRequest, aI as Membership, aK as UpdateMembershipRequest, aM as AvailableScopesTree, aQ as ScopeSwitchResult, aR as GdprExportData, aS as PinStatus, aU as MfaAvailableMethods, aV as TotpEnrollResult, aW as TotpVerifyResult, aX as SmsEnrollResult, y as MfaEnrollment, aY as EmailEnrollResult, aZ as BackupCodesResult, a_ as BackupCodeCountResult, o as UpdateBrandingRequest, q as UploadAssetRequest, p as BrandingAsset, r as BrandingDomainMapping, a as IQAuthClientConfig, c as IQAuthBrowserSessionClientConfig, b as IQAuthTokenClientConfig } from './types-DZAflmmq.js';
2
- import { T as TokensModule } from './tokens-aHiGFr_E.js';
1
+ import { d as IQAuthEnvironment, T as TokenPair, Y as IQAuthRetryConfig, b1 as ScopeHint, L as LoginResult, b2 as SignupRequest, K as MfaVerifyResult, S as SessionUser, m as Session, U as UserProfile, V as ProvisionUserRequest, W as ProvisionUserResponse, R as UserPermissions, J as JwtClaims, O as OidcDiscovery, y as JwksResponse, z as OidcTokenResponse, b3 as HostedClientContext, n as TenantInfo, C as CreateTenantRequest, o as UpdateTenantRequest, P as PromoteToVendorRequest, p as PromoteToVendorResult, a9 as TenantUser, q as InviteTenantUserRequest, r as InviteTenantUserResult, s as TenantUserRoleUpdate, M as MigrateUserRequest, N as PasswordPolicy, Q as MfaPolicy, B as BrandingConfig, a0 as AppInfo, a1 as PermissionNodeInfo, $ as AppManifest, a2 as AppSyncResult, a3 as Role, a4 as CreateRoleRequest, a5 as UpdateRoleRequest, a6 as AssignRoleRequest, a7 as UserRoleAssignment, aa as PermissionGroup, ab as GroupPermission, ac as AddGroupPermissionRequest, ad as InheritanceRelation, a8 as UserGroupAssignment, ae as UserPermissionOverride, af as AddUserOverrideRequest, ag as EffectivePermission, ah as PermissionCheckResult, aj as CreateApiKeyRequest, ak as CreateApiKeyResult, ai as ApiKeyInfo, al as ApiKeyIntrospection, an as CreateInviteRequest, am as Invitation, ao as InviteValidation, ap as AcceptInviteRequest, ar as CreateWebhookRequest, as as CreateWebhookResult, aq as WebhookEndpoint, at as WebhookDelivery, au as WebhookTestResult, av as Entitlement, aw as GrantEntitlementRequest, ax as Vendor, ay as CreateVendorRequest, az as UpdateVendorRequest, aB as CreateSourceRequest, aA as Source, aC as UpdateSourceRequest, aE as CreateClientRequest, aD as Client, aF as UpdateClientRequest, aG as HierarchyVendor, aJ as HierarchyLink, aN as MembershipWithDetails, aL as CreateMembershipRequest, aK as Membership, aM as UpdateMembershipRequest, aO as AvailableScopesTree, aS as ScopeSwitchResult, aT as GdprExportData, aU as PinStatus, aW as MfaAvailableMethods, aX as TotpEnrollResult, aY as TotpVerifyResult, aZ as SmsEnrollResult, G as MfaEnrollment, a_ as EmailEnrollResult, a$ as BackupCodesResult, b0 as BackupCodeCountResult, t as UpdateBrandingRequest, v as UploadAssetRequest, u as BrandingAsset, w as BrandingDomainMapping, e as IQAuthClientConfig, I as IQAuthBrowserSessionClientConfig, f as IQAuthTokenClientConfig } from './types-Bn8O-OEd.mjs';
2
+ import { T as TokensModule } from './tokens-B06VtvUi.mjs';
3
3
 
4
4
  /**
5
5
  * SOURCE REFS:
@@ -18,6 +18,13 @@ interface HttpClientConfig {
18
18
  getApiKey: () => string | undefined;
19
19
  setTokens: (tokens: TokenPair) => void;
20
20
  autoRefresh: boolean;
21
+ /**
22
+ * When false, the per-request "expiring soon" proactive refresh is skipped.
23
+ * Reactive refresh on a TOKEN_EXPIRED response still fires when `autoRefresh` is true.
24
+ * Used by the mobile client's `'app-state'` mode where the AppState listener
25
+ * drives proactive refresh instead.
26
+ */
27
+ proactiveRefresh?: boolean;
21
28
  onTokenRefresh?: (tokens: TokenPair) => void;
22
29
  sessionHeaderName?: string;
23
30
  sessionHeaderValue?: string;
@@ -49,7 +56,9 @@ declare class HttpClient {
49
56
  declare class AuthModule {
50
57
  private http;
51
58
  constructor(http: HttpClient);
52
- login(email: string, password: string): Promise<LoginResult>;
59
+ login(email: string, password: string, opts?: {
60
+ scopeHint?: ScopeHint;
61
+ }): Promise<LoginResult>;
53
62
  signup(input: SignupRequest): Promise<LoginResult>;
54
63
  completeMfa(mfaChallengeToken: string, code: string, method?: string): Promise<MfaVerifyResult>;
55
64
  completeMfaWithBackup(mfaChallengeToken: string, backupCode: string): Promise<MfaVerifyResult>;
@@ -57,7 +66,15 @@ declare class AuthModule {
57
66
  sent: boolean;
58
67
  method: string;
59
68
  }>;
60
- selectTenant(tenantSelectionToken: string, tenantId: string): Promise<LoginResult>;
69
+ selectTenant(tenantSelectionToken: string, tenantId: string, opts?: {
70
+ scopeHint?: ScopeHint;
71
+ }): Promise<LoginResult>;
72
+ /**
73
+ * Task #171 — redeem a scope-selection token + chosen membership for a
74
+ * real authenticated session. `membershipId` must be one of the scopes
75
+ * returned in the prior `scope_selection` envelope.
76
+ */
77
+ selectScope(scopeSelectionToken: string, membershipId: string): Promise<LoginResult>;
61
78
  logout(): Promise<{
62
79
  message: string;
63
80
  }>;
@@ -594,11 +611,33 @@ declare class PermissionGroupsModule {
594
611
  removeUserOverride(tenantId: string, userId: string, overrideId: string): Promise<{
595
612
  message: string;
596
613
  }>;
614
+ /**
615
+ * Task #130 — `appKey` is REQUIRED. The legacy `product` query alias is no
616
+ * longer accepted at the SDK boundary; pass it as `appKey` instead. The
617
+ * server still accepts `product=` from raw HTTP callers during the
618
+ * deprecation window, but the SDK will not silently translate it.
619
+ */
597
620
  getEffectivePermissions(tenantId: string, userId: string, params: {
598
- product?: string;
599
- appKey?: string;
621
+ appKey: string;
600
622
  }): Promise<EffectivePermission[]>;
601
623
  checkPermission(tenantId: string, userId: string, appKey: string, nodeKey: string): Promise<PermissionCheckResult>;
624
+ /**
625
+ * Task #130 — every entry in `checks` must include a non-empty `appKey`
626
+ * AND `nodeKey`. The SDK validates the whole batch before sending so a
627
+ * single misconfigured entry can't slip through and silently report
628
+ * `allowed: false` from the server's per-entry validation branch.
629
+ */
630
+ batchCheckPermissions(tenantId: string, userId: string, checks: Array<{
631
+ appKey: string;
632
+ nodeKey: string;
633
+ }>): Promise<{
634
+ results: Array<{
635
+ appKey: string;
636
+ nodeKey: string;
637
+ allowed: boolean;
638
+ error?: string;
639
+ }>;
640
+ }>;
602
641
  }
603
642
 
604
643
  declare class ApiKeysModule {
@@ -835,11 +874,25 @@ declare class IQAuthClient {
835
874
  constructor(config: IQAuthClientConfig);
836
875
  static forBrowserSession(config: Omit<IQAuthBrowserSessionClientConfig, "environment">): IQAuthClient;
837
876
  static forServer(config: IQAuthTokenClientConfig): IQAuthClient;
877
+ /**
878
+ * Construct a mobile-environment client. NOTE: this constructor does NOT
879
+ * subscribe to React Native's `AppState` even when `autoRefresh: 'app-state'`
880
+ * is passed — it only disables the per-request proactive refresh. Use
881
+ * `createMobileClient` from `@iqauth/sdk/mobile` if you want the full
882
+ * AppState-driven refresh behavior (recommended for Expo / React Native).
883
+ */
838
884
  static forMobile(config: IQAuthTokenClientConfig): IQAuthClient;
839
885
  static forService(config: IQAuthTokenClientConfig): IQAuthClient;
840
886
  setTokens(tokens: TokenPair): void;
841
887
  getAccessToken(): string | undefined;
842
888
  getRefreshToken(): string | undefined;
889
+ /**
890
+ * Task #126: Eagerly fetch JWKS + OIDC discovery so the first verify() /
891
+ * refresh round-trip on the request hot path doesn't pay the discovery
892
+ * fetch latency. Safe to call repeatedly. Errors are swallowed; callers
893
+ * may fire-and-forget. Called automatically by `iqAuth({...}).attachHelpers()`.
894
+ */
895
+ prewarm(): Promise<void>;
843
896
  private getCurrentClaims;
844
897
  private static resolveEnvironment;
845
898
  }
@@ -1,5 +1,5 @@
1
- import { I as IQAuthEnvironment, T as TokenPair, W as IQAuthRetryConfig, L as LoginResult, a$ as SignupRequest, D as MfaVerifyResult, d as SessionUser, h as Session, U as UserProfile, H as ProvisionUserRequest, K as ProvisionUserResponse, G as UserPermissions, J as JwtClaims, O as OidcDiscovery, t as JwksResponse, u as OidcTokenResponse, b0 as HostedClientContext, i as TenantInfo, C as CreateTenantRequest, j as UpdateTenantRequest, P as PromoteToVendorRequest, k as PromoteToVendorResult, a7 as TenantUser, l as InviteTenantUserRequest, m as InviteTenantUserResult, n as TenantUserRoleUpdate, M as MigrateUserRequest, E as PasswordPolicy, F as MfaPolicy, B as BrandingConfig, _ as AppInfo, $ as PermissionNodeInfo, Z as AppManifest, a0 as AppSyncResult, a1 as Role, a2 as CreateRoleRequest, a3 as UpdateRoleRequest, a4 as AssignRoleRequest, a5 as UserRoleAssignment, a8 as PermissionGroup, a9 as GroupPermission, aa as AddGroupPermissionRequest, ab as InheritanceRelation, a6 as UserGroupAssignment, ac as UserPermissionOverride, ad as AddUserOverrideRequest, ae as EffectivePermission, af as PermissionCheckResult, ah as CreateApiKeyRequest, ai as CreateApiKeyResult, ag as ApiKeyInfo, aj as ApiKeyIntrospection, al as CreateInviteRequest, ak as Invitation, am as InviteValidation, an as AcceptInviteRequest, ap as CreateWebhookRequest, aq as CreateWebhookResult, ao as WebhookEndpoint, ar as WebhookDelivery, as as WebhookTestResult, at as Entitlement, au as GrantEntitlementRequest, av as Vendor, aw as CreateVendorRequest, ax as UpdateVendorRequest, az as CreateSourceRequest, ay as Source, aA as UpdateSourceRequest, aC as CreateClientRequest, aB as Client, aD as UpdateClientRequest, aE as HierarchyVendor, aH as HierarchyLink, aL as MembershipWithDetails, aJ as CreateMembershipRequest, aI as Membership, aK as UpdateMembershipRequest, aM as AvailableScopesTree, aQ as ScopeSwitchResult, aR as GdprExportData, aS as PinStatus, aU as MfaAvailableMethods, aV as TotpEnrollResult, aW as TotpVerifyResult, aX as SmsEnrollResult, y as MfaEnrollment, aY as EmailEnrollResult, aZ as BackupCodesResult, a_ as BackupCodeCountResult, o as UpdateBrandingRequest, q as UploadAssetRequest, p as BrandingAsset, r as BrandingDomainMapping, a as IQAuthClientConfig, c as IQAuthBrowserSessionClientConfig, b as IQAuthTokenClientConfig } from './types-DZAflmmq.mjs';
2
- import { T as TokensModule } from './tokens-DCyzzn8L.mjs';
1
+ import { d as IQAuthEnvironment, T as TokenPair, Y as IQAuthRetryConfig, b1 as ScopeHint, L as LoginResult, b2 as SignupRequest, K as MfaVerifyResult, S as SessionUser, m as Session, U as UserProfile, V as ProvisionUserRequest, W as ProvisionUserResponse, R as UserPermissions, J as JwtClaims, O as OidcDiscovery, y as JwksResponse, z as OidcTokenResponse, b3 as HostedClientContext, n as TenantInfo, C as CreateTenantRequest, o as UpdateTenantRequest, P as PromoteToVendorRequest, p as PromoteToVendorResult, a9 as TenantUser, q as InviteTenantUserRequest, r as InviteTenantUserResult, s as TenantUserRoleUpdate, M as MigrateUserRequest, N as PasswordPolicy, Q as MfaPolicy, B as BrandingConfig, a0 as AppInfo, a1 as PermissionNodeInfo, $ as AppManifest, a2 as AppSyncResult, a3 as Role, a4 as CreateRoleRequest, a5 as UpdateRoleRequest, a6 as AssignRoleRequest, a7 as UserRoleAssignment, aa as PermissionGroup, ab as GroupPermission, ac as AddGroupPermissionRequest, ad as InheritanceRelation, a8 as UserGroupAssignment, ae as UserPermissionOverride, af as AddUserOverrideRequest, ag as EffectivePermission, ah as PermissionCheckResult, aj as CreateApiKeyRequest, ak as CreateApiKeyResult, ai as ApiKeyInfo, al as ApiKeyIntrospection, an as CreateInviteRequest, am as Invitation, ao as InviteValidation, ap as AcceptInviteRequest, ar as CreateWebhookRequest, as as CreateWebhookResult, aq as WebhookEndpoint, at as WebhookDelivery, au as WebhookTestResult, av as Entitlement, aw as GrantEntitlementRequest, ax as Vendor, ay as CreateVendorRequest, az as UpdateVendorRequest, aB as CreateSourceRequest, aA as Source, aC as UpdateSourceRequest, aE as CreateClientRequest, aD as Client, aF as UpdateClientRequest, aG as HierarchyVendor, aJ as HierarchyLink, aN as MembershipWithDetails, aL as CreateMembershipRequest, aK as Membership, aM as UpdateMembershipRequest, aO as AvailableScopesTree, aS as ScopeSwitchResult, aT as GdprExportData, aU as PinStatus, aW as MfaAvailableMethods, aX as TotpEnrollResult, aY as TotpVerifyResult, aZ as SmsEnrollResult, G as MfaEnrollment, a_ as EmailEnrollResult, a$ as BackupCodesResult, b0 as BackupCodeCountResult, t as UpdateBrandingRequest, v as UploadAssetRequest, u as BrandingAsset, w as BrandingDomainMapping, e as IQAuthClientConfig, I as IQAuthBrowserSessionClientConfig, f as IQAuthTokenClientConfig } from './types-Bn8O-OEd.js';
2
+ import { T as TokensModule } from './tokens-9F6ETrzk.js';
3
3
 
4
4
  /**
5
5
  * SOURCE REFS:
@@ -18,6 +18,13 @@ interface HttpClientConfig {
18
18
  getApiKey: () => string | undefined;
19
19
  setTokens: (tokens: TokenPair) => void;
20
20
  autoRefresh: boolean;
21
+ /**
22
+ * When false, the per-request "expiring soon" proactive refresh is skipped.
23
+ * Reactive refresh on a TOKEN_EXPIRED response still fires when `autoRefresh` is true.
24
+ * Used by the mobile client's `'app-state'` mode where the AppState listener
25
+ * drives proactive refresh instead.
26
+ */
27
+ proactiveRefresh?: boolean;
21
28
  onTokenRefresh?: (tokens: TokenPair) => void;
22
29
  sessionHeaderName?: string;
23
30
  sessionHeaderValue?: string;
@@ -49,7 +56,9 @@ declare class HttpClient {
49
56
  declare class AuthModule {
50
57
  private http;
51
58
  constructor(http: HttpClient);
52
- login(email: string, password: string): Promise<LoginResult>;
59
+ login(email: string, password: string, opts?: {
60
+ scopeHint?: ScopeHint;
61
+ }): Promise<LoginResult>;
53
62
  signup(input: SignupRequest): Promise<LoginResult>;
54
63
  completeMfa(mfaChallengeToken: string, code: string, method?: string): Promise<MfaVerifyResult>;
55
64
  completeMfaWithBackup(mfaChallengeToken: string, backupCode: string): Promise<MfaVerifyResult>;
@@ -57,7 +66,15 @@ declare class AuthModule {
57
66
  sent: boolean;
58
67
  method: string;
59
68
  }>;
60
- selectTenant(tenantSelectionToken: string, tenantId: string): Promise<LoginResult>;
69
+ selectTenant(tenantSelectionToken: string, tenantId: string, opts?: {
70
+ scopeHint?: ScopeHint;
71
+ }): Promise<LoginResult>;
72
+ /**
73
+ * Task #171 — redeem a scope-selection token + chosen membership for a
74
+ * real authenticated session. `membershipId` must be one of the scopes
75
+ * returned in the prior `scope_selection` envelope.
76
+ */
77
+ selectScope(scopeSelectionToken: string, membershipId: string): Promise<LoginResult>;
61
78
  logout(): Promise<{
62
79
  message: string;
63
80
  }>;
@@ -594,11 +611,33 @@ declare class PermissionGroupsModule {
594
611
  removeUserOverride(tenantId: string, userId: string, overrideId: string): Promise<{
595
612
  message: string;
596
613
  }>;
614
+ /**
615
+ * Task #130 — `appKey` is REQUIRED. The legacy `product` query alias is no
616
+ * longer accepted at the SDK boundary; pass it as `appKey` instead. The
617
+ * server still accepts `product=` from raw HTTP callers during the
618
+ * deprecation window, but the SDK will not silently translate it.
619
+ */
597
620
  getEffectivePermissions(tenantId: string, userId: string, params: {
598
- product?: string;
599
- appKey?: string;
621
+ appKey: string;
600
622
  }): Promise<EffectivePermission[]>;
601
623
  checkPermission(tenantId: string, userId: string, appKey: string, nodeKey: string): Promise<PermissionCheckResult>;
624
+ /**
625
+ * Task #130 — every entry in `checks` must include a non-empty `appKey`
626
+ * AND `nodeKey`. The SDK validates the whole batch before sending so a
627
+ * single misconfigured entry can't slip through and silently report
628
+ * `allowed: false` from the server's per-entry validation branch.
629
+ */
630
+ batchCheckPermissions(tenantId: string, userId: string, checks: Array<{
631
+ appKey: string;
632
+ nodeKey: string;
633
+ }>): Promise<{
634
+ results: Array<{
635
+ appKey: string;
636
+ nodeKey: string;
637
+ allowed: boolean;
638
+ error?: string;
639
+ }>;
640
+ }>;
602
641
  }
603
642
 
604
643
  declare class ApiKeysModule {
@@ -835,11 +874,25 @@ declare class IQAuthClient {
835
874
  constructor(config: IQAuthClientConfig);
836
875
  static forBrowserSession(config: Omit<IQAuthBrowserSessionClientConfig, "environment">): IQAuthClient;
837
876
  static forServer(config: IQAuthTokenClientConfig): IQAuthClient;
877
+ /**
878
+ * Construct a mobile-environment client. NOTE: this constructor does NOT
879
+ * subscribe to React Native's `AppState` even when `autoRefresh: 'app-state'`
880
+ * is passed — it only disables the per-request proactive refresh. Use
881
+ * `createMobileClient` from `@iqauth/sdk/mobile` if you want the full
882
+ * AppState-driven refresh behavior (recommended for Expo / React Native).
883
+ */
838
884
  static forMobile(config: IQAuthTokenClientConfig): IQAuthClient;
839
885
  static forService(config: IQAuthTokenClientConfig): IQAuthClient;
840
886
  setTokens(tokens: TokenPair): void;
841
887
  getAccessToken(): string | undefined;
842
888
  getRefreshToken(): string | undefined;
889
+ /**
890
+ * Task #126: Eagerly fetch JWKS + OIDC discovery so the first verify() /
891
+ * refresh round-trip on the request hot path doesn't pay the discovery
892
+ * fetch latency. Safe to call repeatedly. Errors are swallowed; callers
893
+ * may fire-and-forget. Called automatically by `iqAuth({...}).attachHelpers()`.
894
+ */
895
+ prewarm(): Promise<void>;
843
896
  private getCurrentClaims;
844
897
  private static resolveEnvironment;
845
898
  }
@@ -5,8 +5,8 @@ import {
5
5
  } from "./chunk-X3K3WOBR.mjs";
6
6
  import {
7
7
  parsePublishableKey
8
- } from "./chunk-WQWBJSSS.mjs";
9
- import "./chunk-6I6RM4MN.mjs";
8
+ } from "./chunk-HVHNYPDC.mjs";
9
+ import "./chunk-6PJRLRB4.mjs";
10
10
  import "./chunk-Y6FXYEAI.mjs";
11
11
 
12
12
  // src/cli/doctor.ts