@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
@@ -5,7 +5,11 @@ var nacl = require('tweetnacl');
5
5
  var ReconnectingWebSocket = require('reconnecting-websocket');
6
6
  var BN = require('bn.js');
7
7
 
8
- let clientConfig = {
8
+ // The pristine default config. `clientConfig` (the module global) starts as a
9
+ // copy of this and is mutated by init(); createClient() resolves fresh scoped
10
+ // configs from this pristine template so a scoped instance never inherits a
11
+ // prior init()'s state.
12
+ const DEFAULT_CLIENT_CONFIG = {
9
13
  // User configured settings
10
14
  name: '',
11
15
  logoUrl: '',
@@ -30,6 +34,7 @@ let clientConfig = {
30
34
  authProvider: null,
31
35
  isServer: false,
32
36
  };
37
+ let clientConfig = Object.assign({}, DEFAULT_CLIENT_CONFIG);
33
38
  let isInitialized = false;
34
39
  // Callers awaiting getConfig() before init() completes. Resolved synchronously
35
40
  // by init() — the previous 50ms setInterval poll added up to 50ms of latency
@@ -84,9 +89,32 @@ function getWebhookKeysUrl() {
84
89
  * Bounded must use the deterministic server aggregation, not the legacy AI query).
85
90
  */
86
91
  function isBoundedNetwork() {
87
- const net = clientConfig.network;
92
+ return isBoundedNetworkConfig(clientConfig);
93
+ }
94
+ /**
95
+ * Config-scoped variant of {@link isBoundedNetwork}: reports whether a SPECIFIC
96
+ * config (e.g. a scoped `createClient()` instance's) targets a Bounded network,
97
+ * rather than the module global. Used so a scoped client routes count/aggregate
98
+ * by its OWN network, not the global one.
99
+ */
100
+ function isBoundedNetworkConfig(config) {
101
+ const net = config.network;
88
102
  return typeof net === 'string' && net in BOUNDED_NETWORKS;
89
103
  }
104
+ /**
105
+ * Merge a partial config over a base config, applying the Bounded endpoint
106
+ * precedence rule used everywhere: defaults (or `base`) < network preset <
107
+ * explicit fields. Pure - never touches module state. Shared by `init` (base =
108
+ * the current global) and `createClient` (base = the pristine defaults) so both
109
+ * resolve endpoints identically. Throws on an unsupported `network`.
110
+ */
111
+ function resolveClientConfig(base, newConfig) {
112
+ if (newConfig.network !== undefined && !(newConfig.network in BOUNDED_NETWORKS)) {
113
+ throw new Error(`Unsupported Bounded network "${String(newConfig.network)}". Expected bounded, bounded-staging, or bounded-production.`);
114
+ }
115
+ const preset = newConfig.network ? BOUNDED_NETWORKS[newConfig.network] : {};
116
+ return Object.assign(Object.assign(Object.assign({}, base), preset), newConfig);
117
+ }
90
118
  function init(newConfig) {
91
119
  return new Promise((resolve, reject) => {
92
120
  if (!newConfig.appId) {
@@ -98,12 +126,15 @@ function init(newConfig) {
98
126
  // passed explicitly wins. No `/config` round-trip; browser SDKs still pass
99
127
  // one explicit authMethod so there is no hidden auth-provider fallback.
100
128
  // defaults (bounded-production) < network preset < explicit newConfig
101
- if (newConfig.network !== undefined && !(newConfig.network in BOUNDED_NETWORKS)) {
102
- reject(new Error(`Unsupported Bounded network "${String(newConfig.network)}". Expected bounded, bounded-staging, or bounded-production.`));
129
+ let resolved;
130
+ try {
131
+ resolved = resolveClientConfig(clientConfig, newConfig);
132
+ }
133
+ catch (error) {
134
+ reject(error instanceof Error ? error : new Error(String(error)));
103
135
  return;
104
136
  }
105
- const preset = newConfig.network ? BOUNDED_NETWORKS[newConfig.network] : {};
106
- clientConfig = Object.assign(Object.assign(Object.assign({}, clientConfig), preset), newConfig);
137
+ clientConfig = resolved;
107
138
  isInitialized = true;
108
139
  const waiters = initWaiters;
109
140
  initWaiters = [];
@@ -3420,7 +3451,7 @@ class ServerSessionManager {
3420
3451
  const address = kp.publicKey.toBase58();
3421
3452
  /* fetch nonce from auth API */
3422
3453
  const nonce = await genAuthNonce();
3423
- const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-C5nbu9zb.js'); });
3454
+ const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-I9w1TbAj.js'); });
3424
3455
  const message = await genSolanaMessage(address, nonce);
3425
3456
  /* sign the message */
3426
3457
  const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
@@ -4031,15 +4062,21 @@ async function refreshAuthSessionOnce(appId, isServer) {
4031
4062
  return refreshPromise;
4032
4063
  }
4033
4064
  async function makeApiRequest(method, urlPath, data, _overrides) {
4034
- var _a, _b, _c, _d, _e, _f;
4035
- const config = await getConfig();
4065
+ var _a, _b, _c, _d, _e, _f, _g;
4066
+ // A scoped createClient() instance passes its own resolved config; otherwise
4067
+ // fall back to the module-global init() config.
4068
+ const config = (_a = _overrides === null || _overrides === void 0 ? void 0 : _overrides._config) !== null && _a !== void 0 ? _a : await getConfig();
4069
+ const readOnly = (_overrides === null || _overrides === void 0 ? void 0 : _overrides._readOnly) === true;
4036
4070
  async function executeRequest() {
4037
4071
  var _a;
4038
4072
  // When _getAuthHeaders is provided (wallet client), use it as the sole auth source.
4039
- // Otherwise use the global createAuthHeader (default path).
4073
+ // A read-only scoped instance omits auth entirely. Otherwise use the
4074
+ // global createAuthHeader (default path).
4040
4075
  const authHeader = (_overrides === null || _overrides === void 0 ? void 0 : _overrides._getAuthHeaders)
4041
4076
  ? await _overrides._getAuthHeaders()
4042
- : await createAuthHeader(config.isServer);
4077
+ : readOnly
4078
+ ? {}
4079
+ : await createAuthHeader(config.isServer);
4043
4080
  const headers = Object.assign({ "Content-Type": "application/json", "X-App-Id": config.appId }, authHeader);
4044
4081
  // Apply custom headers from _overrides
4045
4082
  if (_overrides === null || _overrides === void 0 ? void 0 : _overrides.headers) {
@@ -4068,7 +4105,12 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4068
4105
  return await executeRequest();
4069
4106
  }
4070
4107
  catch (error) {
4071
- if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
4108
+ if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 401) {
4109
+ // Read-only scoped instance: it holds no session, so there is nothing
4110
+ // to refresh. Surface the 401 without touching the global session.
4111
+ if (readOnly) {
4112
+ throw error;
4113
+ }
4072
4114
  // Wallet client path: use client-specific retry, skip global fallback entirely
4073
4115
  if (_overrides === null || _overrides === void 0 ? void 0 : _overrides._clearAuth) {
4074
4116
  await _overrides._clearAuth();
@@ -4083,18 +4125,18 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4083
4125
  }
4084
4126
  return await executeRequest();
4085
4127
  }
4086
- catch (_g) {
4128
+ catch (_h) {
4087
4129
  throw error;
4088
4130
  }
4089
4131
  }
4090
- if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) == 404) {
4132
+ if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) == 404) {
4091
4133
  return { data: null, status: 404 };
4092
4134
  }
4093
- if ((_c = error.response) === null || _c === void 0 ? void 0 : _c.data) {
4135
+ if ((_d = error.response) === null || _d === void 0 ? void 0 : _d.data) {
4094
4136
  const responseData = typeof error.response.data === 'object'
4095
4137
  ? error.response.data
4096
4138
  : { message: String(error.response.data) };
4097
- const httpStatus = (_d = error.response) === null || _d === void 0 ? void 0 : _d.status;
4139
+ const httpStatus = (_e = error.response) === null || _e === void 0 ? void 0 : _e.status;
4098
4140
  // A policy/invariant denial (403 rule / 409 invariant) is surfaced as
4099
4141
  // a first-class BoundedDeclineError carrying `.decline`; every other
4100
4142
  // failure stays a bare enriched Error (unchanged behavior).
@@ -4109,9 +4151,9 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4109
4151
  // `if (e.code === 'invariant_violation')`) without parsing the message.
4110
4152
  // `code` is a stable category ('policy_denied' | 'invariant_violation');
4111
4153
  // `status` is the HTTP status; `requestId` correlates with the decision log.
4112
- apiError.code = (_e = responseData.code) !== null && _e !== void 0 ? _e : apiError.code;
4154
+ apiError.code = (_f = responseData.code) !== null && _f !== void 0 ? _f : apiError.code;
4113
4155
  apiError.status = httpStatus !== null && httpStatus !== void 0 ? httpStatus : responseData.status;
4114
- apiError.requestId = (_f = responseData.requestId) !== null && _f !== void 0 ? _f : apiError.requestId;
4156
+ apiError.requestId = (_g = responseData.requestId) !== null && _g !== void 0 ? _g : apiError.requestId;
4115
4157
  if (decline) {
4116
4158
  // Re-assert decline-owned fields after the envelope copy, then
4117
4159
  // notify the browser widget. Fire-and-forget: never delays or
@@ -4275,7 +4317,12 @@ async function canSendAuthenticatedLiveIntent(connection, isServer) {
4275
4317
  return false;
4276
4318
  return connectionMatchesAuthToken(connection, currentToken);
4277
4319
  }
4278
- async function getSubscriptionIdentity(effectiveAppId, isServer, overrides) {
4320
+ async function getSubscriptionIdentity(effectiveAppId, isServer, overrides, anonymous) {
4321
+ // A read-only scoped instance is unauthenticated by construction. Key it as
4322
+ // anonymous and never read the ambient global session (a different app's).
4323
+ if (anonymous) {
4324
+ return { key: `${effectiveAppId}:anon`, cacheable: false, token: null };
4325
+ }
4279
4326
  // Per-subscription wallet override (server WalletClient.subscribe): key by
4280
4327
  // the wallet's own opaque token material, mirroring getReadPrincipalKey. Do
4281
4328
  // not trust decoded claims or the unverified _walletAddress hint.
@@ -4477,7 +4524,13 @@ async function getFreshAuthToken(isServer) {
4477
4524
  throw rememberAmbientAuthFailure(normalizeAuthExpiredError(error, 'Authentication expired and refresh failed'));
4478
4525
  }
4479
4526
  }
4480
- async function getConnectionAuthToken(isServer, authTokenProvider) {
4527
+ async function getConnectionAuthToken(isServer, authTokenProvider, anonymous) {
4528
+ // A read-only scoped instance connects anonymously: no auth frame, and it
4529
+ // must never read (or refresh) the ambient global session - that session
4530
+ // belongs to a different app and would be rejected by this app's worker.
4531
+ if (anonymous) {
4532
+ return null;
4533
+ }
4481
4534
  if (!authTokenProvider) {
4482
4535
  return getFreshAuthToken(isServer);
4483
4536
  }
@@ -4565,9 +4618,12 @@ function replaySubscriptions(connection) {
4565
4618
  * exact bearer so two distinct tokens never share a flight or a lease, even
4566
4619
  * when their compact `#id#<principalKey>` hashes collide.
4567
4620
  */
4568
- function connectionLeaseKey(appId, routePath, principalKey, principalToken) {
4621
+ function connectionLeaseKey(appId, routePath, principalKey, principalToken, anonymous) {
4569
4622
  const roomKey = roomKeyFromRoutePath(routePath);
4570
- const base = roomKey ? `${appId}#room#${roomKey}` : appId;
4623
+ // A read-only scoped instance is anonymous and must never share a socket with
4624
+ // the authenticated global path even when it targets the same appId.
4625
+ const appKey = anonymous ? `${appId}#ro` : appId;
4626
+ const base = roomKey ? `${appKey}#room#${roomKey}` : appKey;
4571
4627
  if (!principalKey)
4572
4628
  return base;
4573
4629
  const bucket = `${base}#id#${principalKey}`;
@@ -4634,9 +4690,9 @@ function scheduleConnectionCloseIfIdle(connection) {
4634
4690
  * reuse. The lease is released once the caller has registered its
4635
4691
  * subscription/callback.
4636
4692
  */
4637
- async function acquireConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken) {
4693
+ async function acquireConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, source) {
4638
4694
  var _a;
4639
- const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken);
4695
+ const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken, source === null || source === void 0 ? void 0 : source.anonymous);
4640
4696
  connectionAcquisitions.set(leaseKey, ((_a = connectionAcquisitions.get(leaseKey)) !== null && _a !== void 0 ? _a : 0) + 1);
4641
4697
  let released = false;
4642
4698
  let releaseTimer = null;
@@ -4658,7 +4714,7 @@ async function acquireConnection(appId, isServer, routePath, authTokenProvider,
4658
4714
  scheduleConnectionCloseIfIdle(connection);
4659
4715
  };
4660
4716
  try {
4661
- const connection = await getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken);
4717
+ const connection = await getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, source);
4662
4718
  const releaseConnection = () => release(connection);
4663
4719
  // Registration is synchronous after the caller's await. This fail-safe
4664
4720
  // releases on the next task even if an option getter/callback throws
@@ -4671,15 +4727,15 @@ async function acquireConnection(appId, isServer, routePath, authTokenProvider,
4671
4727
  throw error;
4672
4728
  }
4673
4729
  }
4674
- async function getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken) {
4730
+ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, source) {
4675
4731
  // Singleflight: concurrent first-use of the same routing target shares one
4676
4732
  // in-flight connection rather than each creating an orphan socket. Keyed by
4677
4733
  // the identity-bucket lease key so distinct wallet tokens never coalesce.
4678
- const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken);
4734
+ const leaseKey = connectionLeaseKey(appId, routePath, principalKey, principalToken, source === null || source === void 0 ? void 0 : source.anonymous);
4679
4735
  const activeFlight = connectionFlights.get(leaseKey);
4680
4736
  if (activeFlight)
4681
4737
  return activeFlight;
4682
- const flight = getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey);
4738
+ const flight = getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey, source);
4683
4739
  connectionFlights.set(leaseKey, flight);
4684
4740
  try {
4685
4741
  return await flight;
@@ -4690,8 +4746,9 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
4690
4746
  }
4691
4747
  }
4692
4748
  }
4693
- async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey) {
4749
+ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey, source) {
4694
4750
  attachBrowserReconnectHooksOnce();
4751
+ const anonymous = (source === null || source === void 0 ? void 0 : source.anonymous) === true;
4695
4752
  // A per-room subscription gets its OWN connection routed to the room DO; all
4696
4753
  // other subscriptions share the app-level connection (project DO). The worker
4697
4754
  // derives the room from the `?path=` we attach below and is the final routing
@@ -4699,8 +4756,11 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4699
4756
  const roomKey = roomKeyFromRoutePath(routePath);
4700
4757
  // A wallet-scoped subscription (server WalletClient) gets its OWN connection
4701
4758
  // keyed by the wallet identity, so its WS authenticates as that wallet and
4702
- // never shares a connection (or token) with any top-level server caller.
4703
- const base = roomKey ? `${appId}#room#${roomKey}` : appId;
4759
+ // never shares a connection (or token) with any top-level server caller. A
4760
+ // read-only scoped (anonymous) instance is likewise isolated by an `#ro`
4761
+ // marker so it never shares the authenticated global socket on the same appId.
4762
+ const appKey = anonymous ? `${appId}#ro` : appId;
4763
+ const base = roomKey ? `${appKey}#room#${roomKey}` : appKey;
4704
4764
  const identityBucketKey = principalKey
4705
4765
  ? `${base}#id#${principalKey}`
4706
4766
  : base;
@@ -4738,7 +4798,7 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4738
4798
  }
4739
4799
  let currentAuthToken;
4740
4800
  try {
4741
- currentAuthToken = await getConnectionAuthToken(isServer, authTokenProvider);
4801
+ currentAuthToken = await getConnectionAuthToken(isServer, authTokenProvider, anonymous);
4742
4802
  }
4743
4803
  catch (error) {
4744
4804
  const authError = normalizeAuthExpiredError(error, 'Authentication expired and refresh failed');
@@ -4756,7 +4816,7 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4756
4816
  }
4757
4817
  connection.authFailure = null;
4758
4818
  }
4759
- let initialAuthToken = await getConnectionAuthToken(isServer, authTokenProvider);
4819
+ let initialAuthToken = await getConnectionAuthToken(isServer, authTokenProvider, anonymous);
4760
4820
  // Create new connection
4761
4821
  connection = {
4762
4822
  ws: null,
@@ -4782,18 +4842,25 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4782
4842
  idleCloseTimer: null,
4783
4843
  consecutiveAuthFailures: 0,
4784
4844
  authFailure: null,
4845
+ source,
4846
+ anonymous,
4785
4847
  };
4786
4848
  connections.set(connKey, connection);
4787
4849
  // URL provider for reconnection with fresh tokens
4788
4850
  const urlProvider = async () => {
4789
- const config = await getConfig();
4790
- const wsUrl = new URL(config.wsApiUrl);
4851
+ var _a;
4852
+ // A scoped createClient() connection targets its own realtime endpoint;
4853
+ // the default path reads the module-global config.
4854
+ const wsApiUrl = (_a = source === null || source === void 0 ? void 0 : source.wsApiUrl) !== null && _a !== void 0 ? _a : (await getConfig()).wsApiUrl;
4855
+ const wsUrl = new URL(wsApiUrl);
4791
4856
  // Always use v2 path
4792
4857
  wsUrl.pathname = WS_V2_PATH;
4793
4858
  // Set appId from the configured SDK source, or an explicit per-call
4794
4859
  // override passed through subscribeV2. Do not consult ambient globals:
4795
4860
  // app identity must come from init({ appId }) or an explicit call option.
4796
- wsUrl.searchParams.append('appId', appId || config.appId);
4861
+ // `appId` here is already the resolved effectiveAppId; fall back to the
4862
+ // global only on the default path when it was somehow empty.
4863
+ wsUrl.searchParams.append('appId', appId || (await getConfig()).appId);
4797
4864
  // Per-room connection: carry the room path so the worker routes this WS
4798
4865
  // to the room DO (appId#room#roomId) where the live view fan-out lives.
4799
4866
  if (connection.routePath) {
@@ -4807,7 +4874,7 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4807
4874
  try {
4808
4875
  authToken = initialAuthToken !== undefined
4809
4876
  ? initialAuthToken
4810
- : await getConnectionAuthToken(isServer, connection.authTokenProvider);
4877
+ : await getConnectionAuthToken(isServer, connection.authTokenProvider, connection.anonymous);
4811
4878
  initialAuthToken = undefined;
4812
4879
  }
4813
4880
  catch (error) {
@@ -4836,8 +4903,12 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
4836
4903
  connection.isConnected = true;
4837
4904
  // NOTE: Do NOT reset consecutiveAuthFailures here. It is reset when a
4838
4905
  // fresh auth token is actually obtained or on an explicit auth change.
4839
- // Schedule periodic token freshness checks
4840
- scheduleTokenRefresh(connection, isServer);
4906
+ // Schedule periodic token freshness checks. An anonymous (read-only
4907
+ // scoped) connection holds no token and must never poll or refresh the
4908
+ // ambient global session, so it is skipped.
4909
+ if (!connection.anonymous) {
4910
+ scheduleTokenRefresh(connection, isServer);
4911
+ }
4841
4912
  if (connection.pendingAuthToken) {
4842
4913
  connection.isAuthenticating = true;
4843
4914
  try {
@@ -5409,9 +5480,19 @@ function sendUnsubscribe(connection, subscriptionId) {
5409
5480
  * the subscription onto, since a WS connection routes to a single DO.
5410
5481
  */
5411
5482
  async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5412
- var _a, _b, _c, _d, _e;
5413
- const config = await getConfig();
5483
+ var _a, _b, _c, _d, _e, _f;
5484
+ const overrides = subscriptionOptions._overrides;
5485
+ // A scoped createClient() instance passes its own resolved config; the
5486
+ // default path reads the module-global init() config.
5487
+ const config = (_a = overrides === null || overrides === void 0 ? void 0 : overrides._config) !== null && _a !== void 0 ? _a : await getConfig();
5414
5488
  const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
5489
+ // A read-only scoped instance connects anonymously to its own realtime
5490
+ // endpoint - never sharing a socket, token, or the ambient session with the
5491
+ // global (default) app.
5492
+ const anonymous = (overrides === null || overrides === void 0 ? void 0 : overrides._readOnly) === true;
5493
+ const source = (overrides === null || overrides === void 0 ? void 0 : overrides._config)
5494
+ ? { wsApiUrl: overrides._config.wsApiUrl, anonymous }
5495
+ : undefined;
5415
5496
  // Use explicit appId override if provided, otherwise fall back to config
5416
5497
  const effectiveAppId = subscriptionOptions.appId || config.appId;
5417
5498
  // H1: scope the response cache to the caller's identity so a private snapshot
@@ -5420,12 +5501,12 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5420
5501
  // Per-subscription wallet override (server WalletClient.subscribe). When set,
5421
5502
  // the connection authenticates + caches under the wallet's identity instead of
5422
5503
  // any top-level server caller — so a `createWalletClient` caller can
5423
- // subscribe through its explicit wallet session.
5424
- const overrides = subscriptionOptions._overrides;
5425
- const authTokenProvider = (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders)
5504
+ // subscribe through its explicit wallet session. A read-only scoped instance
5505
+ // is anonymous, so it never carries an auth-token provider.
5506
+ const authTokenProvider = (!anonymous && (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders))
5426
5507
  ? async () => bearerFromAuthHeaders(await overrides._getAuthHeaders()) || null
5427
5508
  : undefined;
5428
- const identityInfo = await getSubscriptionIdentity(effectiveAppId, config.isServer, overrides);
5509
+ const identityInfo = await getSubscriptionIdentity(effectiveAppId, config.isServer, overrides, anonymous);
5429
5510
  const identity = identityInfo.key;
5430
5511
  const identityToken = identityInfo.token;
5431
5512
  const responseCacheEnabled = subscriptionOptions.cache === true && identityInfo.cacheable;
@@ -5445,7 +5526,7 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5445
5526
  let connection;
5446
5527
  let releaseConnection;
5447
5528
  try {
5448
- const acquisition = await acquireConnection(effectiveAppId, config.isServer, roomRoutePath, authTokenProvider, principalKey, principalToken);
5529
+ const acquisition = await acquireConnection(effectiveAppId, config.isServer, roomRoutePath, authTokenProvider, principalKey, principalToken, source);
5449
5530
  connection = acquisition.connection;
5450
5531
  releaseConnection = acquisition.release;
5451
5532
  }
@@ -5467,16 +5548,16 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5467
5548
  // (previously each new subscribe re-stringified every existing
5468
5549
  // subscription's shape/filter/sort — O(N²) with many live hooks).
5469
5550
  specKey = JSON.stringify([
5470
- (_a = subscriptionOptions.shape) !== null && _a !== void 0 ? _a : null,
5471
- (_b = subscriptionOptions.filter) !== null && _b !== void 0 ? _b : null,
5472
- (_c = subscriptionOptions.sort) !== null && _c !== void 0 ? _c : null,
5551
+ (_b = subscriptionOptions.shape) !== null && _b !== void 0 ? _b : null,
5552
+ (_c = subscriptionOptions.filter) !== null && _c !== void 0 ? _c : null,
5553
+ (_d = subscriptionOptions.sort) !== null && _d !== void 0 ? _d : null,
5473
5554
  ]);
5474
5555
  for (const sub of connection.subscriptions.values()) {
5475
5556
  // H1: also require the same principal identity (compact hash AND exact
5476
5557
  // token) — a different logged-in user must not piggy-back on (and
5477
5558
  // inherit lastData from) another user's existing subscription, even
5478
5559
  // with identical path/prompt/shape/filter/sort.
5479
- 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) {
5560
+ 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) {
5480
5561
  existingSubscription = sub;
5481
5562
  break;
5482
5563
  }
@@ -5569,7 +5650,7 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
5569
5650
  shape: subscriptionOptions.shape,
5570
5651
  limit: subscriptionOptions.limit,
5571
5652
  cursor: subscriptionOptions.cursor,
5572
- includeSubPaths: (_e = subscriptionOptions.includeSubPaths) !== null && _e !== void 0 ? _e : false,
5653
+ includeSubPaths: (_f = subscriptionOptions.includeSubPaths) !== null && _f !== void 0 ? _f : false,
5573
5654
  callbacks: [subscriptionOptions],
5574
5655
  lastData: undefined,
5575
5656
  dataRevision: 0,
@@ -5811,7 +5892,7 @@ async function doReconnectWithNewAuth() {
5811
5892
  console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
5812
5893
  }
5813
5894
  try {
5814
- const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-B_Zaa1FL.js'); });
5895
+ const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-DmzboN-H.js'); });
5815
5896
  await reconnectRealtimeStoreWithNewAuth();
5816
5897
  }
5817
5898
  catch (error) {
@@ -6666,8 +6747,15 @@ function uncacheableReadKey(appId, scope) {
6666
6747
  * decoded claims and never caller identity hints such as `_walletAddress`.
6667
6748
  */
6668
6749
  async function getReadPrincipalKey(overrides) {
6669
- const config = await getConfig();
6750
+ var _a;
6751
+ const config = (_a = overrides === null || overrides === void 0 ? void 0 : overrides._config) !== null && _a !== void 0 ? _a : await getConfig();
6670
6752
  const appId = config.appId || '';
6753
+ // A scoped read-only instance sends no auth and must never consult the ambient
6754
+ // global session for a principal - its reads are always uncacheable (no
6755
+ // per-user cache reuse, but also no cross-app/cross-user leak).
6756
+ if (overrides === null || overrides === void 0 ? void 0 : overrides._readOnly) {
6757
+ return { key: uncacheableReadKey(appId, 'ro'), cacheable: false, principalValue: null };
6758
+ }
6671
6759
  // makeApiRequest applies overrides.headers AFTER its computed auth header, so
6672
6760
  // caller-supplied Authorization is the real request auth when present.
6673
6761
  if (hasAuthHeader(overrides === null || overrides === void 0 ? void 0 : overrides.headers)) {
@@ -6688,7 +6776,7 @@ async function getReadPrincipalKey(overrides) {
6688
6776
  ? { key: `${appId}:o${principal}`, cacheable: true, principalValue: authValue }
6689
6777
  : { key: uncacheableReadKey(appId, 'o'), cacheable: false, principalValue: null };
6690
6778
  }
6691
- catch (_a) {
6779
+ catch (_b) {
6692
6780
  // If we can't resolve the override identity, do not read/write cache.
6693
6781
  return { key: uncacheableReadKey(appId, 'o'), cacheable: false, principalValue: null };
6694
6782
  }
@@ -6760,12 +6848,17 @@ function parseAggregateValue(result) {
6760
6848
  * @param opts - Optional filter prompt and overrides
6761
6849
  * @returns AggregateResult with the count value
6762
6850
  */
6851
+ /** Whether these opts target a Bounded network - honoring a scoped instance's
6852
+ * own config (`_overrides._config`) rather than the module global. */
6853
+ function optsTargetBounded(overrides) {
6854
+ return (overrides === null || overrides === void 0 ? void 0 : overrides._config) ? isBoundedNetworkConfig(overrides._config) : isBoundedNetwork();
6855
+ }
6763
6856
  async function count(path, opts = {}) {
6764
6857
  var _a, _b;
6765
6858
  // On Bounded, the worker has no AI query engine — a prompt-based count returns
6766
6859
  // the raw documents, not an aggregation. Use the deterministic server
6767
6860
  // aggregation (queryAggregate) instead.
6768
- if (isBoundedNetwork()) {
6861
+ if (optsTargetBounded(opts._overrides)) {
6769
6862
  const rows = await queryAggregate(path, { count: true }, { filter: opts.filter, _overrides: opts._overrides });
6770
6863
  return { value: (_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0 };
6771
6864
  }
@@ -6826,7 +6919,7 @@ async function boundedAggregate(path, operation, opts) {
6826
6919
  async function aggregate(path, operation, opts = {}) {
6827
6920
  // On Bounded, route through the deterministic server aggregation — the legacy
6828
6921
  // AI-prompt path returns raw documents on the worker (no AI query engine).
6829
- if (isBoundedNetwork()) {
6922
+ if (optsTargetBounded(opts._overrides)) {
6830
6923
  return { value: await boundedAggregate(path, operation, opts) };
6831
6924
  }
6832
6925
  let prefix;
@@ -7487,11 +7580,11 @@ async function setMany(many, options) {
7487
7580
  transactionResult = await handleEvmTransaction(curTx.evmTransaction, authProvider, options);
7488
7581
  }
7489
7582
  else if (curTx.serializedTransaction) {
7490
- const { handlePreBuiltTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-Dtsk6xFc.js'); });
7583
+ const { handlePreBuiltTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-BUJkyVxN.js'); });
7491
7584
  transactionResult = await handlePreBuiltTransaction(curTx, authProvider, options, expectedWritePaths);
7492
7585
  }
7493
7586
  else {
7494
- const { handleSolanaTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-Dtsk6xFc.js'); });
7587
+ const { handleSolanaTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-BUJkyVxN.js'); });
7495
7588
  transactionResult = await handleSolanaTransaction(curTx, authProvider, options, expectedWritePaths);
7496
7589
  }
7497
7590
  // Sync items after all transactions are confirmed
@@ -7820,6 +7913,100 @@ async function reconnectWithNewAuth() {
7820
7913
  return reconnectWithNewAuthV2();
7821
7914
  }
7822
7915
 
7916
+ /**
7917
+ * Scoped multi-app client factory.
7918
+ *
7919
+ * `createClient()` returns an isolated, READ-ONLY client bound to one Bounded
7920
+ * app, independent of the module-global `init()` client. It exists so a single
7921
+ * frontend can hold its normal authenticated client for its own app AND a
7922
+ * read-only client for a SECOND Bounded app (e.g. a shared steward/runtime app),
7923
+ * with live subscriptions on both and zero interference between them.
7924
+ *
7925
+ * Each instance carries its OWN fully-resolved config (endpoints + appId) and
7926
+ * routes through the SAME shared transport/subscription implementation as the
7927
+ * global path - it never forks that code. Isolation comes from threading the
7928
+ * instance config (and a read-only marker) through the existing per-request /
7929
+ * per-subscription override channel, plus the websocket layer already keying
7930
+ * connections by appId.
7931
+ *
7932
+ * ## v1 scope: read-only, unauthenticated
7933
+ *
7934
+ * A scoped instance is UNAUTHENTICATED. It sends no auth, never reads or
7935
+ * refreshes the global session, and connects to its app's realtime endpoint
7936
+ * anonymously. It is therefore only useful against collections a Bounded app
7937
+ * exposes with a public read rule (`read: true`) - private/per-user reads will
7938
+ * be denied by the target app's policy, exactly as an anonymous caller would be.
7939
+ *
7940
+ * Writes and any auth-requiring surface are NOT supported on a scoped instance
7941
+ * and throw a clear error. Per-instance authentication is a planned follow-up.
7942
+ */
7943
+ /** Error thrown when an unsupported (write / auth-requiring) call is made on a
7944
+ * read-only scoped client. */
7945
+ class ScopedClientReadOnlyError extends Error {
7946
+ constructor(operation) {
7947
+ super(`createClient() instances are read-only in v1: "${operation}" is not supported. ` +
7948
+ `Use the global init() client for authenticated writes, or read only public ` +
7949
+ `(read: true) collections on this scoped instance.`);
7950
+ this.name = 'ScopedClientReadOnlyError';
7951
+ }
7952
+ }
7953
+ /**
7954
+ * Create a read-only client scoped to a single Bounded app.
7955
+ *
7956
+ * @example
7957
+ * ```ts
7958
+ * // Global client stays the venue's own authenticated app:
7959
+ * await init({ appId: VENUE_APP, network: 'bounded-production', authMethod: 'email' });
7960
+ *
7961
+ * // A second, read-only client for the steward runtime app:
7962
+ * const steward = createClient({ appId: STEWARD_APP, network: 'bounded-production' });
7963
+ * const state = await steward.get('runtime/current');
7964
+ * const stop = await steward.subscribe('runtime/current', (d) => render(d));
7965
+ * ```
7966
+ */
7967
+ function createClient(config) {
7968
+ if (!config || !config.appId) {
7969
+ throw new Error('createClient requires an appId.');
7970
+ }
7971
+ // Resolve from the PRISTINE defaults (never the mutated global) so a scoped
7972
+ // instance is fully independent of any prior init(). Same precedence as init:
7973
+ // defaults < network preset < explicit fields.
7974
+ const resolved = resolveClientConfig(DEFAULT_CLIENT_CONFIG, config);
7975
+ // The override channel that carries this instance's identity through the
7976
+ // shared transport and subscription code. `_config` retargets endpoints +
7977
+ // appId; `_readOnly` omits auth and blocks ambient-session/global-cache reads.
7978
+ const instanceOverrides = () => ({ _config: resolved, _readOnly: true });
7979
+ // Merge caller-supplied opts with the instance overrides. The instance
7980
+ // `_config`/`_readOnly` are applied LAST so app code can never override the
7981
+ // routing/identity of a scoped call (but may still pass e.g. an AbortSignal).
7982
+ const withOverrides = (opts) => {
7983
+ var _a;
7984
+ const base = (opts !== null && opts !== void 0 ? opts : {});
7985
+ return Object.assign(Object.assign({}, base), { _overrides: Object.assign(Object.assign({}, ((_a = base._overrides) !== null && _a !== void 0 ? _a : {})), instanceOverrides()) });
7986
+ };
7987
+ return {
7988
+ appId: resolved.appId,
7989
+ getConfig: () => (Object.assign({}, resolved)),
7990
+ get: (path, opts) => get(path, withOverrides(opts)),
7991
+ getMany: (paths, opts) => getMany(paths, withOverrides(opts)),
7992
+ search: (path, query, opts) => search(path, query, withOverrides(opts)),
7993
+ queryAggregate: (path, spec, opts) => queryAggregate(path, spec, withOverrides(opts)),
7994
+ count: (path, opts) => count(path, withOverrides(opts)),
7995
+ aggregate: (path, operation, opts) => aggregate(path, operation, withOverrides(opts)),
7996
+ runQuery: (absolutePath, queryName, queryArgs, opts) => runQuery(absolutePath, queryName, queryArgs, withOverrides(opts)),
7997
+ runQueryMany: (many, opts) => runQueryMany(many, withOverrides(opts)),
7998
+ subscribe: (path, options) => {
7999
+ var _a;
8000
+ const opts = typeof options === 'function' ? { onData: options } : Object.assign({}, options);
8001
+ // Force this instance's scoped, read-only routing onto the subscription.
8002
+ opts._overrides = Object.assign(Object.assign({}, ((_a = opts._overrides) !== null && _a !== void 0 ? _a : {})), { _config: resolved, _readOnly: true });
8003
+ return subscribe(path, opts);
8004
+ },
8005
+ set: () => { throw new ScopedClientReadOnlyError('set'); },
8006
+ setMany: () => { throw new ScopedClientReadOnlyError('setMany'); },
8007
+ };
8008
+ }
8009
+
7823
8010
  /**
7824
8011
  * Live named-query subscriptions (subscribeQuery) - HTTP client poller (v1).
7825
8012
  *
@@ -8483,12 +8670,12 @@ function toMillis(seconds) {
8483
8670
 
8484
8671
  /** Load the Anchor/Web3 transaction builder only when an onchain write uses it. */
8485
8672
  async function buildSetDocumentsTransaction(connection, idl, programProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses) {
8486
- const implementation = await Promise.resolve().then(function () { return require('./sol-utils-Dni65Z63.js'); });
8673
+ const implementation = await Promise.resolve().then(function () { return require('./sol-utils-oBMCMbe_.js'); });
8487
8674
  return implementation.buildSetDocumentsTransaction(connection, idl, programProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses);
8488
8675
  }
8489
8676
  /** SIWS message creation is async already, so its onchain module can be lazy. */
8490
8677
  async function genSolanaMessage(address, nonce) {
8491
- const implementation = await Promise.resolve().then(function () { return require('./solana-message-C5nbu9zb.js'); });
8678
+ const implementation = await Promise.resolve().then(function () { return require('./solana-message-I9w1TbAj.js'); });
8492
8679
  return implementation.genSolanaMessage(address, nonce);
8493
8680
  }
8494
8681
  const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
@@ -9032,6 +9219,7 @@ exports.LiveIntentError = LiveIntentError;
9032
9219
  exports.MIN_QUERY_POLL_INTERVAL_MS = MIN_QUERY_POLL_INTERVAL_MS;
9033
9220
  exports.RATE_LIMIT_EVENT_NAME = RATE_LIMIT_EVENT_NAME;
9034
9221
  exports.ReactNativeSessionManager = ReactNativeSessionManager;
9222
+ exports.ScopedClientReadOnlyError = ScopedClientReadOnlyError;
9035
9223
  exports.ServerSessionManager = ServerSessionManager;
9036
9224
  exports.WebSessionManager = WebSessionManager;
9037
9225
  exports.aggregate = aggregate;
@@ -9046,6 +9234,7 @@ exports.closeAllQuerySubscriptions = closeAllQuerySubscriptions;
9046
9234
  exports.closeAllSubscriptions = closeAllSubscriptions;
9047
9235
  exports.convertRemainingAccounts = convertRemainingAccounts;
9048
9236
  exports.count = count;
9237
+ exports.createClient = createClient;
9049
9238
  exports.createSessionWithSignature = createSessionWithSignature;
9050
9239
  exports.defineLiveModule = defineLiveModule;
9051
9240
  exports.deriveDecline = deriveDecline;
@@ -9099,4 +9288,4 @@ exports.toLowerEvmAddress = toLowerEvmAddress;
9099
9288
  exports.toMillis = toMillis;
9100
9289
  exports.toSeconds = toSeconds;
9101
9290
  exports.withEffects = withEffects;
9102
- //# sourceMappingURL=index-DE-xgEvG.js.map
9291
+ //# sourceMappingURL=index-BXvTkFw2.js.map