@bounded-sh/core 0.0.53 → 0.0.55

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 (38) hide show
  1. package/dist/client/config.d.ts +16 -0
  2. package/dist/client/create-client.d.ts +103 -0
  3. package/dist/client/operations.d.ts +14 -16
  4. package/dist/{index-DE-xgEvG.js → index-BXvTkFw2.js} +250 -61
  5. package/dist/index-BXvTkFw2.js.map +1 -0
  6. package/dist/{index-CbTo1jKG.mjs → index-qcTp-5Mt.mjs} +249 -62
  7. package/dist/index-qcTp-5Mt.mjs.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +3 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +1 -1
  12. package/dist/{realtime-store-DLErbHL_.mjs → realtime-store-BzKEmyxq.mjs} +2 -2
  13. package/dist/{realtime-store-DLErbHL_.mjs.map → realtime-store-BzKEmyxq.mjs.map} +1 -1
  14. package/dist/{realtime-store-B_Zaa1FL.js → realtime-store-DmzboN-H.js} +2 -2
  15. package/dist/{realtime-store-B_Zaa1FL.js.map → realtime-store-DmzboN-H.js.map} +1 -1
  16. package/dist/{sol-utils-v8cCs2pm.mjs → sol-utils-BhjxvQUj.mjs} +3 -5
  17. package/dist/sol-utils-BhjxvQUj.mjs.map +1 -0
  18. package/dist/{sol-utils-Dni65Z63.js → sol-utils-oBMCMbe_.js} +3 -5
  19. package/dist/sol-utils-oBMCMbe_.js.map +1 -0
  20. package/dist/{solana-message-C5nbu9zb.js → solana-message-I9w1TbAj.js} +2 -2
  21. package/dist/{solana-message-C5nbu9zb.js.map → solana-message-I9w1TbAj.js.map} +1 -1
  22. package/dist/{solana-message-CSjZx5xE.mjs → solana-message-XxVgbtp2.mjs} +2 -2
  23. package/dist/{solana-message-CSjZx5xE.mjs.map → solana-message-XxVgbtp2.mjs.map} +1 -1
  24. package/dist/{solana-operations-Dtsk6xFc.js → solana-operations-BUJkyVxN.js} +4 -9
  25. package/dist/solana-operations-BUJkyVxN.js.map +1 -0
  26. package/dist/{solana-operations-Bx91DDCy.mjs → solana-operations-ehEsfO5g.mjs} +4 -9
  27. package/dist/solana-operations-ehEsfO5g.mjs.map +1 -0
  28. package/dist/types.d.ts +13 -0
  29. package/dist/utils/api.d.ts +3 -0
  30. package/dist/utils/sol/poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZpDevnet-program.d.ts +1215 -321
  31. package/dist/utils/sol/poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZpMainnet-program.d.ts +1215 -321
  32. package/package.json +2 -2
  33. package/dist/index-CbTo1jKG.mjs.map +0 -1
  34. package/dist/index-DE-xgEvG.js.map +0 -1
  35. package/dist/sol-utils-Dni65Z63.js.map +0 -1
  36. package/dist/sol-utils-v8cCs2pm.mjs.map +0 -1
  37. package/dist/solana-operations-Bx91DDCy.mjs.map +0 -1
  38. package/dist/solana-operations-Dtsk6xFc.js.map +0 -1
@@ -3,7 +3,11 @@ import nacl from 'tweetnacl';
3
3
  import ReconnectingWebSocket from 'reconnecting-websocket';
4
4
  import BN from 'bn.js';
5
5
 
6
- let clientConfig = {
6
+ // The pristine default config. `clientConfig` (the module global) starts as a
7
+ // copy of this and is mutated by init(); createClient() resolves fresh scoped
8
+ // configs from this pristine template so a scoped instance never inherits a
9
+ // prior init()'s state.
10
+ const DEFAULT_CLIENT_CONFIG = {
7
11
  // User configured settings
8
12
  name: '',
9
13
  logoUrl: '',
@@ -28,6 +32,7 @@ let clientConfig = {
28
32
  authProvider: null,
29
33
  isServer: false,
30
34
  };
35
+ let clientConfig = Object.assign({}, DEFAULT_CLIENT_CONFIG);
31
36
  let isInitialized = false;
32
37
  // Callers awaiting getConfig() before init() completes. Resolved synchronously
33
38
  // by init() — the previous 50ms setInterval poll added up to 50ms of latency
@@ -82,9 +87,32 @@ function getWebhookKeysUrl() {
82
87
  * Bounded must use the deterministic server aggregation, not the legacy AI query).
83
88
  */
84
89
  function isBoundedNetwork() {
85
- const net = clientConfig.network;
90
+ return isBoundedNetworkConfig(clientConfig);
91
+ }
92
+ /**
93
+ * Config-scoped variant of {@link isBoundedNetwork}: reports whether a SPECIFIC
94
+ * config (e.g. a scoped `createClient()` instance's) targets a Bounded network,
95
+ * rather than the module global. Used so a scoped client routes count/aggregate
96
+ * by its OWN network, not the global one.
97
+ */
98
+ function isBoundedNetworkConfig(config) {
99
+ const net = config.network;
86
100
  return typeof net === 'string' && net in BOUNDED_NETWORKS;
87
101
  }
102
+ /**
103
+ * Merge a partial config over a base config, applying the Bounded endpoint
104
+ * precedence rule used everywhere: defaults (or `base`) < network preset <
105
+ * explicit fields. Pure - never touches module state. Shared by `init` (base =
106
+ * the current global) and `createClient` (base = the pristine defaults) so both
107
+ * resolve endpoints identically. Throws on an unsupported `network`.
108
+ */
109
+ function resolveClientConfig(base, newConfig) {
110
+ if (newConfig.network !== undefined && !(newConfig.network in BOUNDED_NETWORKS)) {
111
+ throw new Error(`Unsupported Bounded network "${String(newConfig.network)}". Expected bounded, bounded-staging, or bounded-production.`);
112
+ }
113
+ const preset = newConfig.network ? BOUNDED_NETWORKS[newConfig.network] : {};
114
+ return Object.assign(Object.assign(Object.assign({}, base), preset), newConfig);
115
+ }
88
116
  function init(newConfig) {
89
117
  return new Promise((resolve, reject) => {
90
118
  if (!newConfig.appId) {
@@ -96,12 +124,15 @@ function init(newConfig) {
96
124
  // passed explicitly wins. No `/config` round-trip; browser SDKs still pass
97
125
  // one explicit authMethod so there is no hidden auth-provider fallback.
98
126
  // defaults (bounded-production) < network preset < explicit newConfig
99
- if (newConfig.network !== undefined && !(newConfig.network in BOUNDED_NETWORKS)) {
100
- reject(new Error(`Unsupported Bounded network "${String(newConfig.network)}". Expected bounded, bounded-staging, or bounded-production.`));
127
+ let resolved;
128
+ try {
129
+ resolved = resolveClientConfig(clientConfig, newConfig);
130
+ }
131
+ catch (error) {
132
+ reject(error instanceof Error ? error : new Error(String(error)));
101
133
  return;
102
134
  }
103
- const preset = newConfig.network ? BOUNDED_NETWORKS[newConfig.network] : {};
104
- clientConfig = Object.assign(Object.assign(Object.assign({}, clientConfig), preset), newConfig);
135
+ clientConfig = resolved;
105
136
  isInitialized = true;
106
137
  const waiters = initWaiters;
107
138
  initWaiters = [];
@@ -3418,7 +3449,7 @@ class ServerSessionManager {
3418
3449
  const address = kp.publicKey.toBase58();
3419
3450
  /* fetch nonce from auth API */
3420
3451
  const nonce = await genAuthNonce();
3421
- const { genSolanaMessage } = await import('./solana-message-CSjZx5xE.mjs');
3452
+ const { genSolanaMessage } = await import('./solana-message-XxVgbtp2.mjs');
3422
3453
  const message = await genSolanaMessage(address, nonce);
3423
3454
  /* sign the message */
3424
3455
  const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
@@ -4029,15 +4060,21 @@ async function refreshAuthSessionOnce(appId, isServer) {
4029
4060
  return refreshPromise;
4030
4061
  }
4031
4062
  async function makeApiRequest(method, urlPath, data, _overrides) {
4032
- var _a, _b, _c, _d, _e, _f;
4033
- const config = await getConfig();
4063
+ var _a, _b, _c, _d, _e, _f, _g;
4064
+ // A scoped createClient() instance passes its own resolved config; otherwise
4065
+ // fall back to the module-global init() config.
4066
+ const config = (_a = _overrides === null || _overrides === void 0 ? void 0 : _overrides._config) !== null && _a !== void 0 ? _a : await getConfig();
4067
+ const readOnly = (_overrides === null || _overrides === void 0 ? void 0 : _overrides._readOnly) === true;
4034
4068
  async function executeRequest() {
4035
4069
  var _a;
4036
4070
  // When _getAuthHeaders is provided (wallet client), use it as the sole auth source.
4037
- // Otherwise use the global createAuthHeader (default path).
4071
+ // A read-only scoped instance omits auth entirely. Otherwise use the
4072
+ // global createAuthHeader (default path).
4038
4073
  const authHeader = (_overrides === null || _overrides === void 0 ? void 0 : _overrides._getAuthHeaders)
4039
4074
  ? await _overrides._getAuthHeaders()
4040
- : await createAuthHeader(config.isServer);
4075
+ : readOnly
4076
+ ? {}
4077
+ : await createAuthHeader(config.isServer);
4041
4078
  const headers = Object.assign({ "Content-Type": "application/json", "X-App-Id": config.appId }, authHeader);
4042
4079
  // Apply custom headers from _overrides
4043
4080
  if (_overrides === null || _overrides === void 0 ? void 0 : _overrides.headers) {
@@ -4066,7 +4103,12 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4066
4103
  return await executeRequest();
4067
4104
  }
4068
4105
  catch (error) {
4069
- if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
4106
+ if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 401) {
4107
+ // Read-only scoped instance: it holds no session, so there is nothing
4108
+ // to refresh. Surface the 401 without touching the global session.
4109
+ if (readOnly) {
4110
+ throw error;
4111
+ }
4070
4112
  // Wallet client path: use client-specific retry, skip global fallback entirely
4071
4113
  if (_overrides === null || _overrides === void 0 ? void 0 : _overrides._clearAuth) {
4072
4114
  await _overrides._clearAuth();
@@ -4081,18 +4123,18 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4081
4123
  }
4082
4124
  return await executeRequest();
4083
4125
  }
4084
- catch (_g) {
4126
+ catch (_h) {
4085
4127
  throw error;
4086
4128
  }
4087
4129
  }
4088
- if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) == 404) {
4130
+ if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) == 404) {
4089
4131
  return { data: null, status: 404 };
4090
4132
  }
4091
- if ((_c = error.response) === null || _c === void 0 ? void 0 : _c.data) {
4133
+ if ((_d = error.response) === null || _d === void 0 ? void 0 : _d.data) {
4092
4134
  const responseData = typeof error.response.data === 'object'
4093
4135
  ? error.response.data
4094
4136
  : { message: String(error.response.data) };
4095
- const httpStatus = (_d = error.response) === null || _d === void 0 ? void 0 : _d.status;
4137
+ const httpStatus = (_e = error.response) === null || _e === void 0 ? void 0 : _e.status;
4096
4138
  // A policy/invariant denial (403 rule / 409 invariant) is surfaced as
4097
4139
  // a first-class BoundedDeclineError carrying `.decline`; every other
4098
4140
  // failure stays a bare enriched Error (unchanged behavior).
@@ -4107,9 +4149,9 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4107
4149
  // `if (e.code === 'invariant_violation')`) without parsing the message.
4108
4150
  // `code` is a stable category ('policy_denied' | 'invariant_violation');
4109
4151
  // `status` is the HTTP status; `requestId` correlates with the decision log.
4110
- apiError.code = (_e = responseData.code) !== null && _e !== void 0 ? _e : apiError.code;
4152
+ apiError.code = (_f = responseData.code) !== null && _f !== void 0 ? _f : apiError.code;
4111
4153
  apiError.status = httpStatus !== null && httpStatus !== void 0 ? httpStatus : responseData.status;
4112
- apiError.requestId = (_f = responseData.requestId) !== null && _f !== void 0 ? _f : apiError.requestId;
4154
+ apiError.requestId = (_g = responseData.requestId) !== null && _g !== void 0 ? _g : apiError.requestId;
4113
4155
  if (decline) {
4114
4156
  // Re-assert decline-owned fields after the envelope copy, then
4115
4157
  // notify the browser widget. Fire-and-forget: never delays or
@@ -4273,7 +4315,12 @@ async function canSendAuthenticatedLiveIntent(connection, isServer) {
4273
4315
  return false;
4274
4316
  return connectionMatchesAuthToken(connection, currentToken);
4275
4317
  }
4276
- async function getSubscriptionIdentity(effectiveAppId, isServer, overrides) {
4318
+ async function getSubscriptionIdentity(effectiveAppId, isServer, overrides, anonymous) {
4319
+ // A read-only scoped instance is unauthenticated by construction. Key it as
4320
+ // anonymous and never read the ambient global session (a different app's).
4321
+ if (anonymous) {
4322
+ return { key: `${effectiveAppId}:anon`, cacheable: false, token: null };
4323
+ }
4277
4324
  // Per-subscription wallet override (server WalletClient.subscribe): key by
4278
4325
  // the wallet's own opaque token material, mirroring getReadPrincipalKey. Do
4279
4326
  // not trust decoded claims or the unverified _walletAddress hint.
@@ -4475,7 +4522,13 @@ async function getFreshAuthToken(isServer) {
4475
4522
  throw rememberAmbientAuthFailure(normalizeAuthExpiredError(error, 'Authentication expired and refresh failed'));
4476
4523
  }
4477
4524
  }
4478
- async function getConnectionAuthToken(isServer, authTokenProvider) {
4525
+ async function getConnectionAuthToken(isServer, authTokenProvider, anonymous) {
4526
+ // A read-only scoped instance connects anonymously: no auth frame, and it
4527
+ // must never read (or refresh) the ambient global session - that session
4528
+ // belongs to a different app and would be rejected by this app's worker.
4529
+ if (anonymous) {
4530
+ return null;
4531
+ }
4479
4532
  if (!authTokenProvider) {
4480
4533
  return getFreshAuthToken(isServer);
4481
4534
  }
@@ -4563,9 +4616,12 @@ function replaySubscriptions(connection) {
4563
4616
  * exact bearer so two distinct tokens never share a flight or a lease, even
4564
4617
  * when their compact `#id#<principalKey>` hashes collide.
4565
4618
  */
4566
- function connectionLeaseKey(appId, routePath, principalKey, principalToken) {
4619
+ function connectionLeaseKey(appId, routePath, principalKey, principalToken, anonymous) {
4567
4620
  const roomKey = roomKeyFromRoutePath(routePath);
4568
- const base = roomKey ? `${appId}#room#${roomKey}` : appId;
4621
+ // A read-only scoped instance is anonymous and must never share a socket with
4622
+ // the authenticated global path even when it targets the same appId.
4623
+ const appKey = anonymous ? `${appId}#ro` : appId;
4624
+ const base = roomKey ? `${appKey}#room#${roomKey}` : appKey;
4569
4625
  if (!principalKey)
4570
4626
  return base;
4571
4627
  const bucket = `${base}#id#${principalKey}`;
@@ -4632,9 +4688,9 @@ function scheduleConnectionCloseIfIdle(connection) {
4632
4688
  * reuse. The lease is released once the caller has registered its
4633
4689
  * subscription/callback.
4634
4690
  */
4635
- async function acquireConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken) {
4691
+ async function acquireConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, source) {
4636
4692
  var _a;
4637
- const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken);
4693
+ const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken, source === null || source === void 0 ? void 0 : source.anonymous);
4638
4694
  connectionAcquisitions.set(leaseKey, ((_a = connectionAcquisitions.get(leaseKey)) !== null && _a !== void 0 ? _a : 0) + 1);
4639
4695
  let released = false;
4640
4696
  let releaseTimer = null;
@@ -4656,7 +4712,7 @@ async function acquireConnection(appId, isServer, routePath, authTokenProvider,
4656
4712
  scheduleConnectionCloseIfIdle(connection);
4657
4713
  };
4658
4714
  try {
4659
- const connection = await getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken);
4715
+ const connection = await getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, source);
4660
4716
  const releaseConnection = () => release(connection);
4661
4717
  // Registration is synchronous after the caller's await. This fail-safe
4662
4718
  // releases on the next task even if an option getter/callback throws
@@ -4669,15 +4725,15 @@ async function acquireConnection(appId, isServer, routePath, authTokenProvider,
4669
4725
  throw error;
4670
4726
  }
4671
4727
  }
4672
- async function getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken) {
4728
+ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, source) {
4673
4729
  // Singleflight: concurrent first-use of the same routing target shares one
4674
4730
  // in-flight connection rather than each creating an orphan socket. Keyed by
4675
4731
  // the identity-bucket lease key so distinct wallet tokens never coalesce.
4676
- const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken);
4732
+ const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken, source === null || source === void 0 ? void 0 : source.anonymous);
4677
4733
  const activeFlight = connectionFlights.get(leaseKey);
4678
4734
  if (activeFlight)
4679
4735
  return activeFlight;
4680
- const flight = getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey);
4736
+ const flight = getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey, source);
4681
4737
  connectionFlights.set(leaseKey, flight);
4682
4738
  try {
4683
4739
  return await flight;
@@ -4688,8 +4744,9 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
4688
4744
  }
4689
4745
  }
4690
4746
  }
4691
- async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey) {
4747
+ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey, source) {
4692
4748
  attachBrowserReconnectHooksOnce();
4749
+ const anonymous = (source === null || source === void 0 ? void 0 : source.anonymous) === true;
4693
4750
  // A per-room subscription gets its OWN connection routed to the room DO; all
4694
4751
  // other subscriptions share the app-level connection (project DO). The worker
4695
4752
  // derives the room from the `?path=` we attach below and is the final routing
@@ -4697,8 +4754,11 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4697
4754
  const roomKey = roomKeyFromRoutePath(routePath);
4698
4755
  // A wallet-scoped subscription (server WalletClient) gets its OWN connection
4699
4756
  // keyed by the wallet identity, so its WS authenticates as that wallet and
4700
- // never shares a connection (or token) with any top-level server caller.
4701
- const base = roomKey ? `${appId}#room#${roomKey}` : appId;
4757
+ // never shares a connection (or token) with any top-level server caller. A
4758
+ // read-only scoped (anonymous) instance is likewise isolated by an `#ro`
4759
+ // marker so it never shares the authenticated global socket on the same appId.
4760
+ const appKey = anonymous ? `${appId}#ro` : appId;
4761
+ const base = roomKey ? `${appKey}#room#${roomKey}` : appKey;
4702
4762
  const identityBucketKey = principalKey
4703
4763
  ? `${base}#id#${principalKey}`
4704
4764
  : base;
@@ -4736,7 +4796,7 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4736
4796
  }
4737
4797
  let currentAuthToken;
4738
4798
  try {
4739
- currentAuthToken = await getConnectionAuthToken(isServer, authTokenProvider);
4799
+ currentAuthToken = await getConnectionAuthToken(isServer, authTokenProvider, anonymous);
4740
4800
  }
4741
4801
  catch (error) {
4742
4802
  const authError = normalizeAuthExpiredError(error, 'Authentication expired and refresh failed');
@@ -4754,7 +4814,7 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4754
4814
  }
4755
4815
  connection.authFailure = null;
4756
4816
  }
4757
- let initialAuthToken = await getConnectionAuthToken(isServer, authTokenProvider);
4817
+ let initialAuthToken = await getConnectionAuthToken(isServer, authTokenProvider, anonymous);
4758
4818
  // Create new connection
4759
4819
  connection = {
4760
4820
  ws: null,
@@ -4780,18 +4840,25 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4780
4840
  idleCloseTimer: null,
4781
4841
  consecutiveAuthFailures: 0,
4782
4842
  authFailure: null,
4843
+ source,
4844
+ anonymous,
4783
4845
  };
4784
4846
  connections.set(connKey, connection);
4785
4847
  // URL provider for reconnection with fresh tokens
4786
4848
  const urlProvider = async () => {
4787
- const config = await getConfig();
4788
- const wsUrl = new URL(config.wsApiUrl);
4849
+ var _a;
4850
+ // A scoped createClient() connection targets its own realtime endpoint;
4851
+ // the default path reads the module-global config.
4852
+ const wsApiUrl = (_a = source === null || source === void 0 ? void 0 : source.wsApiUrl) !== null && _a !== void 0 ? _a : (await getConfig()).wsApiUrl;
4853
+ const wsUrl = new URL(wsApiUrl);
4789
4854
  // Always use v2 path
4790
4855
  wsUrl.pathname = WS_V2_PATH;
4791
4856
  // Set appId from the configured SDK source, or an explicit per-call
4792
4857
  // override passed through subscribeV2. Do not consult ambient globals:
4793
4858
  // app identity must come from init({ appId }) or an explicit call option.
4794
- wsUrl.searchParams.append('appId', appId || config.appId);
4859
+ // `appId` here is already the resolved effectiveAppId; fall back to the
4860
+ // global only on the default path when it was somehow empty.
4861
+ wsUrl.searchParams.append('appId', appId || (await getConfig()).appId);
4795
4862
  // Per-room connection: carry the room path so the worker routes this WS
4796
4863
  // to the room DO (appId#room#roomId) where the live view fan-out lives.
4797
4864
  if (connection.routePath) {
@@ -4805,7 +4872,7 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4805
4872
  try {
4806
4873
  authToken = initialAuthToken !== undefined
4807
4874
  ? initialAuthToken
4808
- : await getConnectionAuthToken(isServer, connection.authTokenProvider);
4875
+ : await getConnectionAuthToken(isServer, connection.authTokenProvider, connection.anonymous);
4809
4876
  initialAuthToken = undefined;
4810
4877
  }
4811
4878
  catch (error) {
@@ -4834,8 +4901,12 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4834
4901
  connection.isConnected = true;
4835
4902
  // NOTE: Do NOT reset consecutiveAuthFailures here. It is reset when a
4836
4903
  // fresh auth token is actually obtained or on an explicit auth change.
4837
- // Schedule periodic token freshness checks
4838
- scheduleTokenRefresh(connection, isServer);
4904
+ // Schedule periodic token freshness checks. An anonymous (read-only
4905
+ // scoped) connection holds no token and must never poll or refresh the
4906
+ // ambient global session, so it is skipped.
4907
+ if (!connection.anonymous) {
4908
+ scheduleTokenRefresh(connection, isServer);
4909
+ }
4839
4910
  if (connection.pendingAuthToken) {
4840
4911
  connection.isAuthenticating = true;
4841
4912
  try {
@@ -5407,9 +5478,19 @@ function sendUnsubscribe(connection, subscriptionId) {
5407
5478
  * the subscription onto, since a WS connection routes to a single DO.
5408
5479
  */
5409
5480
  async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5410
- var _a, _b, _c, _d, _e;
5411
- const config = await getConfig();
5481
+ var _a, _b, _c, _d, _e, _f;
5482
+ const overrides = subscriptionOptions._overrides;
5483
+ // A scoped createClient() instance passes its own resolved config; the
5484
+ // default path reads the module-global init() config.
5485
+ const config = (_a = overrides === null || overrides === void 0 ? void 0 : overrides._config) !== null && _a !== void 0 ? _a : await getConfig();
5412
5486
  const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
5487
+ // A read-only scoped instance connects anonymously to its own realtime
5488
+ // endpoint - never sharing a socket, token, or the ambient session with the
5489
+ // global (default) app.
5490
+ const anonymous = (overrides === null || overrides === void 0 ? void 0 : overrides._readOnly) === true;
5491
+ const source = (overrides === null || overrides === void 0 ? void 0 : overrides._config)
5492
+ ? { wsApiUrl: overrides._config.wsApiUrl, anonymous }
5493
+ : undefined;
5413
5494
  // Use explicit appId override if provided, otherwise fall back to config
5414
5495
  const effectiveAppId = subscriptionOptions.appId || config.appId;
5415
5496
  // H1: scope the response cache to the caller's identity so a private snapshot
@@ -5418,12 +5499,12 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5418
5499
  // Per-subscription wallet override (server WalletClient.subscribe). When set,
5419
5500
  // the connection authenticates + caches under the wallet's identity instead of
5420
5501
  // any top-level server caller — so a `createWalletClient` caller can
5421
- // subscribe through its explicit wallet session.
5422
- const overrides = subscriptionOptions._overrides;
5423
- const authTokenProvider = (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders)
5502
+ // subscribe through its explicit wallet session. A read-only scoped instance
5503
+ // is anonymous, so it never carries an auth-token provider.
5504
+ const authTokenProvider = (!anonymous && (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders))
5424
5505
  ? async () => bearerFromAuthHeaders(await overrides._getAuthHeaders()) || null
5425
5506
  : undefined;
5426
- const identityInfo = await getSubscriptionIdentity(effectiveAppId, config.isServer, overrides);
5507
+ const identityInfo = await getSubscriptionIdentity(effectiveAppId, config.isServer, overrides, anonymous);
5427
5508
  const identity = identityInfo.key;
5428
5509
  const identityToken = identityInfo.token;
5429
5510
  const responseCacheEnabled = subscriptionOptions.cache === true && identityInfo.cacheable;
@@ -5443,7 +5524,7 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5443
5524
  let connection;
5444
5525
  let releaseConnection;
5445
5526
  try {
5446
- const acquisition = await acquireConnection(effectiveAppId, config.isServer, roomRoutePath, authTokenProvider, principalKey, principalToken);
5527
+ const acquisition = await acquireConnection(effectiveAppId, config.isServer, roomRoutePath, authTokenProvider, principalKey, principalToken, source);
5447
5528
  connection = acquisition.connection;
5448
5529
  releaseConnection = acquisition.release;
5449
5530
  }
@@ -5465,16 +5546,16 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5465
5546
  // (previously each new subscribe re-stringified every existing
5466
5547
  // subscription's shape/filter/sort — O(N²) with many live hooks).
5467
5548
  specKey = JSON.stringify([
5468
- (_a = subscriptionOptions.shape) !== null && _a !== void 0 ? _a : null,
5469
- (_b = subscriptionOptions.filter) !== null && _b !== void 0 ? _b : null,
5470
- (_c = subscriptionOptions.sort) !== null && _c !== void 0 ? _c : null,
5549
+ (_b = subscriptionOptions.shape) !== null && _b !== void 0 ? _b : null,
5550
+ (_c = subscriptionOptions.filter) !== null && _c !== void 0 ? _c : null,
5551
+ (_d = subscriptionOptions.sort) !== null && _d !== void 0 ? _d : null,
5471
5552
  ]);
5472
5553
  for (const sub of connection.subscriptions.values()) {
5473
5554
  // H1: also require the same principal identity (compact hash AND exact
5474
5555
  // token) — a different logged-in user must not piggy-back on (and
5475
5556
  // inherit lastData from) another user's existing subscription, even
5476
5557
  // with identical path/prompt/shape/filter/sort.
5477
- if (sub.identity === identity && sub.identityToken === identityToken && sub.path === normalizedPath && sub.prompt === subscriptionOptions.prompt && sub.specKey === specKey && sub.includeSubPaths === ((_d = subscriptionOptions.includeSubPaths) !== null && _d !== void 0 ? _d : false) && sub.limit === subscriptionOptions.limit && sub.cursor === subscriptionOptions.cursor) {
5558
+ if (sub.identity === identity && sub.identityToken === identityToken && sub.path === normalizedPath && sub.prompt === subscriptionOptions.prompt && sub.specKey === specKey && sub.includeSubPaths === ((_e = subscriptionOptions.includeSubPaths) !== null && _e !== void 0 ? _e : false) && sub.limit === subscriptionOptions.limit && sub.cursor === subscriptionOptions.cursor) {
5478
5559
  existingSubscription = sub;
5479
5560
  break;
5480
5561
  }
@@ -5567,7 +5648,7 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5567
5648
  shape: subscriptionOptions.shape,
5568
5649
  limit: subscriptionOptions.limit,
5569
5650
  cursor: subscriptionOptions.cursor,
5570
- includeSubPaths: (_e = subscriptionOptions.includeSubPaths) !== null && _e !== void 0 ? _e : false,
5651
+ includeSubPaths: (_f = subscriptionOptions.includeSubPaths) !== null && _f !== void 0 ? _f : false,
5571
5652
  callbacks: [subscriptionOptions],
5572
5653
  lastData: undefined,
5573
5654
  dataRevision: 0,
@@ -5809,7 +5890,7 @@ async function doReconnectWithNewAuth() {
5809
5890
  console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
5810
5891
  }
5811
5892
  try {
5812
- const { reconnectRealtimeStoreWithNewAuth } = await import('./realtime-store-DLErbHL_.mjs');
5893
+ const { reconnectRealtimeStoreWithNewAuth } = await import('./realtime-store-BzKEmyxq.mjs');
5813
5894
  await reconnectRealtimeStoreWithNewAuth();
5814
5895
  }
5815
5896
  catch (error) {
@@ -6664,8 +6745,15 @@ function uncacheableReadKey(appId, scope) {
6664
6745
  * decoded claims and never caller identity hints such as `_walletAddress`.
6665
6746
  */
6666
6747
  async function getReadPrincipalKey(overrides) {
6667
- const config = await getConfig();
6748
+ var _a;
6749
+ const config = (_a = overrides === null || overrides === void 0 ? void 0 : overrides._config) !== null && _a !== void 0 ? _a : await getConfig();
6668
6750
  const appId = config.appId || '';
6751
+ // A scoped read-only instance sends no auth and must never consult the ambient
6752
+ // global session for a principal - its reads are always uncacheable (no
6753
+ // per-user cache reuse, but also no cross-app/cross-user leak).
6754
+ if (overrides === null || overrides === void 0 ? void 0 : overrides._readOnly) {
6755
+ return { key: uncacheableReadKey(appId, 'ro'), cacheable: false, principalValue: null };
6756
+ }
6669
6757
  // makeApiRequest applies overrides.headers AFTER its computed auth header, so
6670
6758
  // caller-supplied Authorization is the real request auth when present.
6671
6759
  if (hasAuthHeader(overrides === null || overrides === void 0 ? void 0 : overrides.headers)) {
@@ -6686,7 +6774,7 @@ async function getReadPrincipalKey(overrides) {
6686
6774
  ? { key: `${appId}:o${principal}`, cacheable: true, principalValue: authValue }
6687
6775
  : { key: uncacheableReadKey(appId, 'o'), cacheable: false, principalValue: null };
6688
6776
  }
6689
- catch (_a) {
6777
+ catch (_b) {
6690
6778
  // If we can't resolve the override identity, do not read/write cache.
6691
6779
  return { key: uncacheableReadKey(appId, 'o'), cacheable: false, principalValue: null };
6692
6780
  }
@@ -6758,12 +6846,17 @@ function parseAggregateValue(result) {
6758
6846
  * @param opts - Optional filter prompt and overrides
6759
6847
  * @returns AggregateResult with the count value
6760
6848
  */
6849
+ /** Whether these opts target a Bounded network - honoring a scoped instance's
6850
+ * own config (`_overrides._config`) rather than the module global. */
6851
+ function optsTargetBounded(overrides) {
6852
+ return (overrides === null || overrides === void 0 ? void 0 : overrides._config) ? isBoundedNetworkConfig(overrides._config) : isBoundedNetwork();
6853
+ }
6761
6854
  async function count(path, opts = {}) {
6762
6855
  var _a, _b;
6763
6856
  // On Bounded, the worker has no AI query engine — a prompt-based count returns
6764
6857
  // the raw documents, not an aggregation. Use the deterministic server
6765
6858
  // aggregation (queryAggregate) instead.
6766
- if (isBoundedNetwork()) {
6859
+ if (optsTargetBounded(opts._overrides)) {
6767
6860
  const rows = await queryAggregate(path, { count: true }, { filter: opts.filter, _overrides: opts._overrides });
6768
6861
  return { value: (_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0 };
6769
6862
  }
@@ -6824,7 +6917,7 @@ async function boundedAggregate(path, operation, opts) {
6824
6917
  async function aggregate(path, operation, opts = {}) {
6825
6918
  // On Bounded, route through the deterministic server aggregation — the legacy
6826
6919
  // AI-prompt path returns raw documents on the worker (no AI query engine).
6827
- if (isBoundedNetwork()) {
6920
+ if (optsTargetBounded(opts._overrides)) {
6828
6921
  return { value: await boundedAggregate(path, operation, opts) };
6829
6922
  }
6830
6923
  let prefix;
@@ -7485,11 +7578,11 @@ async function setMany(many, options) {
7485
7578
  transactionResult = await handleEvmTransaction(curTx.evmTransaction, authProvider, options);
7486
7579
  }
7487
7580
  else if (curTx.serializedTransaction) {
7488
- const { handlePreBuiltTransaction } = await import('./solana-operations-Bx91DDCy.mjs');
7581
+ const { handlePreBuiltTransaction } = await import('./solana-operations-ehEsfO5g.mjs');
7489
7582
  transactionResult = await handlePreBuiltTransaction(curTx, authProvider, options, expectedWritePaths);
7490
7583
  }
7491
7584
  else {
7492
- const { handleSolanaTransaction } = await import('./solana-operations-Bx91DDCy.mjs');
7585
+ const { handleSolanaTransaction } = await import('./solana-operations-ehEsfO5g.mjs');
7493
7586
  transactionResult = await handleSolanaTransaction(curTx, authProvider, options, expectedWritePaths);
7494
7587
  }
7495
7588
  // Sync items after all transactions are confirmed
@@ -7818,6 +7911,100 @@ async function reconnectWithNewAuth() {
7818
7911
  return reconnectWithNewAuthV2();
7819
7912
  }
7820
7913
 
7914
+ /**
7915
+ * Scoped multi-app client factory.
7916
+ *
7917
+ * `createClient()` returns an isolated, READ-ONLY client bound to one Bounded
7918
+ * app, independent of the module-global `init()` client. It exists so a single
7919
+ * frontend can hold its normal authenticated client for its own app AND a
7920
+ * read-only client for a SECOND Bounded app (e.g. a shared steward/runtime app),
7921
+ * with live subscriptions on both and zero interference between them.
7922
+ *
7923
+ * Each instance carries its OWN fully-resolved config (endpoints + appId) and
7924
+ * routes through the SAME shared transport/subscription implementation as the
7925
+ * global path - it never forks that code. Isolation comes from threading the
7926
+ * instance config (and a read-only marker) through the existing per-request /
7927
+ * per-subscription override channel, plus the websocket layer already keying
7928
+ * connections by appId.
7929
+ *
7930
+ * ## v1 scope: read-only, unauthenticated
7931
+ *
7932
+ * A scoped instance is UNAUTHENTICATED. It sends no auth, never reads or
7933
+ * refreshes the global session, and connects to its app's realtime endpoint
7934
+ * anonymously. It is therefore only useful against collections a Bounded app
7935
+ * exposes with a public read rule (`read: true`) - private/per-user reads will
7936
+ * be denied by the target app's policy, exactly as an anonymous caller would be.
7937
+ *
7938
+ * Writes and any auth-requiring surface are NOT supported on a scoped instance
7939
+ * and throw a clear error. Per-instance authentication is a planned follow-up.
7940
+ */
7941
+ /** Error thrown when an unsupported (write / auth-requiring) call is made on a
7942
+ * read-only scoped client. */
7943
+ class ScopedClientReadOnlyError extends Error {
7944
+ constructor(operation) {
7945
+ super(`createClient() instances are read-only in v1: "${operation}" is not supported. ` +
7946
+ `Use the global init() client for authenticated writes, or read only public ` +
7947
+ `(read: true) collections on this scoped instance.`);
7948
+ this.name = 'ScopedClientReadOnlyError';
7949
+ }
7950
+ }
7951
+ /**
7952
+ * Create a read-only client scoped to a single Bounded app.
7953
+ *
7954
+ * @example
7955
+ * ```ts
7956
+ * // Global client stays the venue's own authenticated app:
7957
+ * await init({ appId: VENUE_APP, network: 'bounded-production', authMethod: 'email' });
7958
+ *
7959
+ * // A second, read-only client for the steward runtime app:
7960
+ * const steward = createClient({ appId: STEWARD_APP, network: 'bounded-production' });
7961
+ * const state = await steward.get('runtime/current');
7962
+ * const stop = await steward.subscribe('runtime/current', (d) => render(d));
7963
+ * ```
7964
+ */
7965
+ function createClient(config) {
7966
+ if (!config || !config.appId) {
7967
+ throw new Error('createClient requires an appId.');
7968
+ }
7969
+ // Resolve from the PRISTINE defaults (never the mutated global) so a scoped
7970
+ // instance is fully independent of any prior init(). Same precedence as init:
7971
+ // defaults < network preset < explicit fields.
7972
+ const resolved = resolveClientConfig(DEFAULT_CLIENT_CONFIG, config);
7973
+ // The override channel that carries this instance's identity through the
7974
+ // shared transport and subscription code. `_config` retargets endpoints +
7975
+ // appId; `_readOnly` omits auth and blocks ambient-session/global-cache reads.
7976
+ const instanceOverrides = () => ({ _config: resolved, _readOnly: true });
7977
+ // Merge caller-supplied opts with the instance overrides. The instance
7978
+ // `_config`/`_readOnly` are applied LAST so app code can never override the
7979
+ // routing/identity of a scoped call (but may still pass e.g. an AbortSignal).
7980
+ const withOverrides = (opts) => {
7981
+ var _a;
7982
+ const base = (opts !== null && opts !== void 0 ? opts : {});
7983
+ return Object.assign(Object.assign({}, base), { _overrides: Object.assign(Object.assign({}, ((_a = base._overrides) !== null && _a !== void 0 ? _a : {})), instanceOverrides()) });
7984
+ };
7985
+ return {
7986
+ appId: resolved.appId,
7987
+ getConfig: () => (Object.assign({}, resolved)),
7988
+ get: (path, opts) => get(path, withOverrides(opts)),
7989
+ getMany: (paths, opts) => getMany(paths, withOverrides(opts)),
7990
+ search: (path, query, opts) => search(path, query, withOverrides(opts)),
7991
+ queryAggregate: (path, spec, opts) => queryAggregate(path, spec, withOverrides(opts)),
7992
+ count: (path, opts) => count(path, withOverrides(opts)),
7993
+ aggregate: (path, operation, opts) => aggregate(path, operation, withOverrides(opts)),
7994
+ runQuery: (absolutePath, queryName, queryArgs, opts) => runQuery(absolutePath, queryName, queryArgs, withOverrides(opts)),
7995
+ runQueryMany: (many, opts) => runQueryMany(many, withOverrides(opts)),
7996
+ subscribe: (path, options) => {
7997
+ var _a;
7998
+ const opts = typeof options === 'function' ? { onData: options } : Object.assign({}, options);
7999
+ // Force this instance's scoped, read-only routing onto the subscription.
8000
+ opts._overrides = Object.assign(Object.assign({}, ((_a = opts._overrides) !== null && _a !== void 0 ? _a : {})), { _config: resolved, _readOnly: true });
8001
+ return subscribe(path, opts);
8002
+ },
8003
+ set: () => { throw new ScopedClientReadOnlyError('set'); },
8004
+ setMany: () => { throw new ScopedClientReadOnlyError('setMany'); },
8005
+ };
8006
+ }
8007
+
7821
8008
  /**
7822
8009
  * Live named-query subscriptions (subscribeQuery) - HTTP client poller (v1).
7823
8010
  *
@@ -8481,12 +8668,12 @@ function toMillis(seconds) {
8481
8668
 
8482
8669
  /** Load the Anchor/Web3 transaction builder only when an onchain write uses it. */
8483
8670
  async function buildSetDocumentsTransaction(connection, idl, programProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses) {
8484
- const implementation = await import('./sol-utils-v8cCs2pm.mjs');
8671
+ const implementation = await import('./sol-utils-BhjxvQUj.mjs');
8485
8672
  return implementation.buildSetDocumentsTransaction(connection, idl, programProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses);
8486
8673
  }
8487
8674
  /** SIWS message creation is async already, so its onchain module can be lazy. */
8488
8675
  async function genSolanaMessage(address, nonce) {
8489
- const implementation = await import('./solana-message-CSjZx5xE.mjs');
8676
+ const implementation = await import('./solana-message-XxVgbtp2.mjs');
8490
8677
  return implementation.genSolanaMessage(address, nonce);
8491
8678
  }
8492
8679
  const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
@@ -9018,5 +9205,5 @@ function defineLiveModule(mod) {
9018
9205
  return mod;
9019
9206
  }
9020
9207
 
9021
- export { refreshSession as $, reconnectWithNewAuth as A, subscribeQuery as B, closeAllQuerySubscriptions as C, canonicalizeArgs as D, canonicalizeResult as E, DEFAULT_QUERY_POLL_INTERVAL_MS as F, BoundedDeclineError as G, deriveDecline as H, InsufficientBalanceError as I, dispatchDeclineEvent as J, DECLINE_EVENT_NAME as K, dispatchRateLimitEvent as L, MIN_QUERY_POLL_INTERVAL_MS as M, hasActiveConnection as N, increment as O, serverTimestamp as P, now as Q, RATE_LIMIT_EVENT_NAME as R, toSeconds as S, toMillis as T, getIdToken as U, deriveUserIdentityFromIdToken as V, WebSessionManager as W, ReactNativeSessionManager as X, getActiveSessionManager as Y, ServerSessionManager as Z, createSessionWithSignature as _, getWebhookKeysUrl as a, revokeSession as a0, signSessionCreateMessage as a1, genAuthNonce as a2, buildSetDocumentsTransaction as a3, convertRemainingAccounts as a4, genSolanaMessage as a5, genSiweMessage as a6, normalizeEvmAddress as a7, toLowerEvmAddress as a8, assertCanonicalBoundedRuntimeWriteCalldata as a9, assertEvmTransactionMatchesWrite as aa, BOUNDED_RUNTIME_SET_DOCUMENTS_SELECTOR as ab, BOUNDED_RUNTIME_SET_DOCUMENTS_ATTESTED_SELECTOR as ac, functions as ad, invoke as ae, FunctionInvokeError as af, live as ag, intent as ah, status as ai, subscribeView as aj, LiveIntentError as ak, withEffects as al, isEffectResult as am, defineLiveModule as an, EFFECT_INTENT_ADDRESS as ao, bufferExports as b, get as c, getMany as d, docId as e, setMany as f, getConfig as g, setFile as h, init as i, getFiles as j, search as k, runQueryMany as l, runExpression as m, runExpressionMany as n, signMessage as o, signTransaction as p, queryAggregate as q, runQuery as r, set as s, signAndSubmitTransaction as t, count as u, aggregate as v, subscribe as w, closeAllSubscriptions as x, clearCache as y, getCachedData as z };
9022
- //# sourceMappingURL=index-CbTo1jKG.mjs.map
9208
+ export { ServerSessionManager as $, reconnectWithNewAuth as A, createClient as B, subscribeQuery as C, closeAllQuerySubscriptions as D, canonicalizeArgs as E, canonicalizeResult as F, DEFAULT_QUERY_POLL_INTERVAL_MS as G, BoundedDeclineError as H, InsufficientBalanceError as I, deriveDecline as J, dispatchDeclineEvent as K, DECLINE_EVENT_NAME as L, MIN_QUERY_POLL_INTERVAL_MS as M, dispatchRateLimitEvent as N, hasActiveConnection as O, increment as P, serverTimestamp as Q, RATE_LIMIT_EVENT_NAME as R, ScopedClientReadOnlyError as S, now as T, toSeconds as U, toMillis as V, getIdToken as W, deriveUserIdentityFromIdToken as X, WebSessionManager as Y, ReactNativeSessionManager as Z, getActiveSessionManager as _, getWebhookKeysUrl as a, createSessionWithSignature as a0, refreshSession as a1, revokeSession as a2, signSessionCreateMessage as a3, genAuthNonce as a4, buildSetDocumentsTransaction as a5, convertRemainingAccounts as a6, genSolanaMessage as a7, genSiweMessage as a8, normalizeEvmAddress as a9, toLowerEvmAddress as aa, assertCanonicalBoundedRuntimeWriteCalldata as ab, assertEvmTransactionMatchesWrite as ac, BOUNDED_RUNTIME_SET_DOCUMENTS_SELECTOR as ad, BOUNDED_RUNTIME_SET_DOCUMENTS_ATTESTED_SELECTOR as ae, functions as af, invoke as ag, FunctionInvokeError as ah, live as ai, intent as aj, status as ak, subscribeView as al, LiveIntentError as am, withEffects as an, isEffectResult as ao, defineLiveModule as ap, EFFECT_INTENT_ADDRESS as aq, bufferExports as b, get as c, getMany as d, docId as e, setMany as f, getConfig as g, setFile as h, init as i, getFiles as j, search as k, runQueryMany as l, runExpression as m, runExpressionMany as n, signMessage as o, signTransaction as p, queryAggregate as q, runQuery as r, set as s, signAndSubmitTransaction as t, count as u, aggregate as v, subscribe as w, closeAllSubscriptions as x, clearCache as y, getCachedData as z };
9209
+ //# sourceMappingURL=index-qcTp-5Mt.mjs.map