@bounded-sh/core 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -95,7 +95,7 @@ function getWebhookKeysUrl() {
95
95
  /**
96
96
  * True when init() configured a Bounded network (the Cloudflare-native stack).
97
97
  * Synchronous + non-blocking. Used to route reads/writes that behave differently
98
- * on Bounded vs the legacy Tarobase backend (e.g. `count`/`aggregate`, which on
98
+ * on Bounded vs the legacy backend (e.g. `count`/`aggregate`, which on
99
99
  * Bounded must use the deterministic server aggregation, not the legacy AI query).
100
100
  */
101
101
  function isBoundedNetwork() {
@@ -2950,7 +2950,7 @@ class WebSessionManager {
2950
2950
  // the RIGHT /session/refresh. Email overrides with humanAuthApiUrl; all
2951
2951
  // wallet/guest/signature flows default to authApiUrl (the bounded-auth issuer).
2952
2952
  const sessionIssuer = (issuer || config.authApiUrl || "").replace(/\/$/, "");
2953
- localStorage.setItem(this.TAROBASE_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
2953
+ localStorage.setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
2954
2954
  accessToken,
2955
2955
  idToken,
2956
2956
  refreshToken, appId: currentAppId }, (sessionIssuer ? { issuer: sessionIssuer } : {}))));
@@ -2961,7 +2961,7 @@ class WebSessionManager {
2961
2961
  static async getSession() {
2962
2962
  if (typeof window === "undefined")
2963
2963
  return null;
2964
- const session = localStorage.getItem(this.TAROBASE_SESSION_STORAGE_KEY);
2964
+ const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
2965
2965
  if (!session)
2966
2966
  return null;
2967
2967
  const sessionObj = JSON.parse(session);
@@ -2985,7 +2985,7 @@ class WebSessionManager {
2985
2985
  // Persist the ROTATED refresh token — the old one is now
2986
2986
  // single-use spent; reusing it would revoke the family.
2987
2987
  refreshed.refreshToken);
2988
- const newSession = localStorage.getItem(this.TAROBASE_SESSION_STORAGE_KEY);
2988
+ const newSession = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
2989
2989
  if (!newSession)
2990
2990
  return null;
2991
2991
  const newObj = JSON.parse(newSession);
@@ -3009,14 +3009,14 @@ class WebSessionManager {
3009
3009
  static clearSession() {
3010
3010
  if (typeof window === "undefined")
3011
3011
  return;
3012
- localStorage.removeItem(this.TAROBASE_SESSION_STORAGE_KEY);
3012
+ localStorage.removeItem(this.BOUNDED_SESSION_STORAGE_KEY);
3013
3013
  }
3014
3014
  /* ------------------------------------------------------------------ */
3015
3015
  /* IS-AUTH */
3016
3016
  /* ------------------------------------------------------------------ */
3017
3017
  static isAuthenticated() {
3018
3018
  // NOTE: this remains synchronous to stay backward compatible
3019
- return !!localStorage.getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3019
+ return !!localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3020
3020
  }
3021
3021
  /* ------------------------------------------------------------------ */
3022
3022
  /* TOKEN HELPERS */
@@ -3024,13 +3024,13 @@ class WebSessionManager {
3024
3024
  static getIdToken() {
3025
3025
  if (typeof window === "undefined")
3026
3026
  return null;
3027
- const session = localStorage.getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3027
+ const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3028
3028
  return session ? JSON.parse(session).idToken : null;
3029
3029
  }
3030
3030
  static getRefreshToken() {
3031
3031
  if (typeof window === "undefined")
3032
3032
  return null;
3033
- const session = localStorage.getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3033
+ const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3034
3034
  return session ? JSON.parse(session).refreshToken : null;
3035
3035
  }
3036
3036
  /** The issuer base that minted this session (for routing silent refresh). */
@@ -3038,7 +3038,7 @@ class WebSessionManager {
3038
3038
  var _a;
3039
3039
  if (typeof window === "undefined")
3040
3040
  return null;
3041
- const session = localStorage.getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3041
+ const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3042
3042
  return session ? ((_a = JSON.parse(session).issuer) !== null && _a !== void 0 ? _a : null) : null;
3043
3043
  }
3044
3044
  /* ------------------------------------------------------------------ */
@@ -3048,7 +3048,7 @@ class WebSessionManager {
3048
3048
  var _a;
3049
3049
  if (typeof window === "undefined")
3050
3050
  return;
3051
- const session = localStorage.getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3051
+ const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3052
3052
  if (!session)
3053
3053
  return;
3054
3054
  const sessionObj = JSON.parse(session);
@@ -3065,10 +3065,10 @@ class WebSessionManager {
3065
3065
  sessionObj.refreshToken = refreshToken;
3066
3066
  }
3067
3067
  (_a = sessionObj.appId) !== null && _a !== void 0 ? _a : (sessionObj.appId = config.appId);
3068
- localStorage.setItem(this.TAROBASE_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
3068
+ localStorage.setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
3069
3069
  }
3070
3070
  }
3071
- WebSessionManager.TAROBASE_SESSION_STORAGE_KEY = "tarobase_session_storage";
3071
+ WebSessionManager.BOUNDED_SESSION_STORAGE_KEY = "bounded_session_storage";
3072
3072
 
3073
3073
  let _config = null;
3074
3074
  class ReactNativeSessionManager {
@@ -3161,7 +3161,7 @@ class ReactNativeSessionManager {
3161
3161
  // Issuer base for silent refresh (email overrides w/ humanAuthApiUrl; wallet/
3162
3162
  // guest default to authApiUrl). See WebSessionManager for the rationale.
3163
3163
  const sessionIssuer = (issuer || config.authApiUrl || "").replace(/\/$/, "");
3164
- this.getStorage().setItem(this.TAROBASE_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
3164
+ this.getStorage().setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
3165
3165
  accessToken,
3166
3166
  idToken,
3167
3167
  refreshToken, appId: currentAppId }, (sessionIssuer ? { issuer: sessionIssuer } : {}))));
@@ -3170,7 +3170,7 @@ class ReactNativeSessionManager {
3170
3170
  /* GET */
3171
3171
  /* ------------------------------------------------------------------ */
3172
3172
  static async getSession() {
3173
- const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3173
+ const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3174
3174
  if (!session)
3175
3175
  return null;
3176
3176
  const sessionObj = JSON.parse(session);
@@ -3191,7 +3191,7 @@ class ReactNativeSessionManager {
3191
3191
  const refreshed = await refreshSession(refreshToken, sessionObj.issuer);
3192
3192
  if ((refreshed === null || refreshed === void 0 ? void 0 : refreshed.idToken) && (refreshed === null || refreshed === void 0 ? void 0 : refreshed.accessToken)) {
3193
3193
  await this.updateIdTokenAndAccessToken(refreshed.idToken, refreshed.accessToken, refreshed.refreshToken);
3194
- const newSession = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3194
+ const newSession = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3195
3195
  if (!newSession)
3196
3196
  return null;
3197
3197
  const newObj = JSON.parse(newSession);
@@ -3213,20 +3213,20 @@ class ReactNativeSessionManager {
3213
3213
  /* CLEAR */
3214
3214
  /* ------------------------------------------------------------------ */
3215
3215
  static clearSession() {
3216
- this.getStorage().removeItem(this.TAROBASE_SESSION_STORAGE_KEY);
3216
+ this.getStorage().removeItem(this.BOUNDED_SESSION_STORAGE_KEY);
3217
3217
  }
3218
3218
  /* ------------------------------------------------------------------ */
3219
3219
  /* IS-AUTH */
3220
3220
  /* ------------------------------------------------------------------ */
3221
3221
  static isAuthenticated() {
3222
- return !!this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3222
+ return !!this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3223
3223
  }
3224
3224
  /* ------------------------------------------------------------------ */
3225
3225
  /* TOKEN HELPERS */
3226
3226
  /* ------------------------------------------------------------------ */
3227
3227
  static getIdToken() {
3228
3228
  try {
3229
- const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3229
+ const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3230
3230
  return session ? JSON.parse(session).idToken : null;
3231
3231
  }
3232
3232
  catch (_a) {
@@ -3237,7 +3237,7 @@ class ReactNativeSessionManager {
3237
3237
  static getIssuer() {
3238
3238
  var _a;
3239
3239
  try {
3240
- const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3240
+ const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3241
3241
  return session ? ((_a = JSON.parse(session).issuer) !== null && _a !== void 0 ? _a : null) : null;
3242
3242
  }
3243
3243
  catch (_b) {
@@ -3246,7 +3246,7 @@ class ReactNativeSessionManager {
3246
3246
  }
3247
3247
  static getRefreshToken() {
3248
3248
  try {
3249
- const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3249
+ const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3250
3250
  return session ? JSON.parse(session).refreshToken : null;
3251
3251
  }
3252
3252
  catch (_a) {
@@ -3258,7 +3258,7 @@ class ReactNativeSessionManager {
3258
3258
  /* ------------------------------------------------------------------ */
3259
3259
  static async updateIdTokenAndAccessToken(idToken, accessToken, refreshToken) {
3260
3260
  var _a;
3261
- const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
3261
+ const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3262
3262
  if (!session)
3263
3263
  return;
3264
3264
  const sessionObj = JSON.parse(session);
@@ -3274,10 +3274,10 @@ class ReactNativeSessionManager {
3274
3274
  sessionObj.refreshToken = refreshToken;
3275
3275
  }
3276
3276
  (_a = sessionObj.appId) !== null && _a !== void 0 ? _a : (sessionObj.appId = config.appId);
3277
- this.getStorage().setItem(this.TAROBASE_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
3277
+ this.getStorage().setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
3278
3278
  }
3279
3279
  }
3280
- ReactNativeSessionManager.TAROBASE_SESSION_STORAGE_KEY = "tarobase_session_storage";
3280
+ ReactNativeSessionManager.BOUNDED_SESSION_STORAGE_KEY = "bounded_session_storage";
3281
3281
 
3282
3282
  /**
3283
3283
  * Return the active CLIENT session manager for the current runtime.
@@ -3798,9 +3798,9 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
3798
3798
  /* ENV helpers */
3799
3799
  /* ------------------------------------------------------------------ */
3800
3800
  // Canonical `BOUNDED_PRIVATE_KEY` (matches the CLI); legacy
3801
- // `TAROBASE_SOLANA_KEYPAIR` still honored. Only consulted when no explicit
3801
+ // `BOUNDED_SOLANA_KEYPAIR` still honored. Only consulted when no explicit
3802
3802
  // keypair was provided (createWalletClient passes one).
3803
- const ENV_KEYPAIRS = ["BOUNDED_PRIVATE_KEY", "TAROBASE_SOLANA_KEYPAIR"];
3803
+ const ENV_KEYPAIRS = ["BOUNDED_PRIVATE_KEY", "BOUNDED_SOLANA_KEYPAIR"];
3804
3804
  function loadKeypairFromEnv() {
3805
3805
  let secret;
3806
3806
  let found;
@@ -3923,7 +3923,7 @@ var serverSessionManager = /*#__PURE__*/Object.freeze({
3923
3923
  });
3924
3924
 
3925
3925
  /**
3926
- * Safe base64 helpers for tarobase-core.
3926
+ * Safe base64 helpers for bounded-core.
3927
3927
  *
3928
3928
  * Uses the global atob/btoa when available (browser, RN with polyfill),
3929
3929
  * falls back to the 'buffer' package (Node.js / SSR / RN without polyfill).
@@ -4103,7 +4103,7 @@ axiosRetry(apiClient, {
4103
4103
  shouldResetTimeout: true,
4104
4104
  onRetry: (retryCount, error, requestConfig) => {
4105
4105
  var _a;
4106
- console.warn(`[tarobase-sdk] retry ${retryCount} for ${(_a = requestConfig.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()} ${requestConfig.url} — ${error.code || error.message}`);
4106
+ console.warn(`[bounded-sdk] retry ${retryCount} for ${(_a = requestConfig.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()} ${requestConfig.url} — ${error.code || error.message}`);
4107
4107
  },
4108
4108
  });
4109
4109
  const refreshInFlight = new Map();
@@ -4162,8 +4162,8 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4162
4162
  : await createAuthHeader(config.isServer);
4163
4163
  const headers = Object.assign({ "Content-Type": "application/json", "X-Public-App-Id": config.appId, "X-App-Id": config.appId }, authHeader);
4164
4164
  if (typeof window !== "undefined" &&
4165
- window.CUSTOM_TAROBASE_APP_ID_HEADER) {
4166
- const customAppId = window.CUSTOM_TAROBASE_APP_ID_HEADER;
4165
+ window.CUSTOM_BOUNDED_APP_ID_HEADER) {
4166
+ const customAppId = window.CUSTOM_BOUNDED_APP_ID_HEADER;
4167
4167
  if (customAppId) {
4168
4168
  headers["X-App-Id"] = customAppId;
4169
4169
  }
@@ -4422,7 +4422,7 @@ function parseAggregateValue(result) {
4422
4422
  * Count items in a collection path. Returns a numeric result.
4423
4423
  *
4424
4424
  * This uses the AI query engine with a count-specific prompt prefix,
4425
- * so TaroBase will generate a $count aggregation pipeline and return
4425
+ * so Bounded will generate a $count aggregation pipeline and return
4426
4426
  * just the count rather than full documents.
4427
4427
  *
4428
4428
  * IMPORTANT: This only works for collections where the read policy is "true".
@@ -4929,6 +4929,13 @@ async function runExpressionMany(many) {
4929
4929
  throw error;
4930
4930
  }
4931
4931
  }
4932
+ /**
4933
+ * Write a document at `path`. Sugar for a one-element {@link setMany}.
4934
+ *
4935
+ * **Delete:** pass `null` as the document to delete it — `set(path, null)` is
4936
+ * the delete (there is no separate `del`/`remove`). It is routed through the
4937
+ * collection's policy `delete` rule and broadcasts a delete to subscribers.
4938
+ */
4932
4939
  async function set(path, document, options) {
4933
4940
  const result = await setMany([{ path, document }], options);
4934
4941
  // Clear cache entries that might be affected by this update
@@ -4947,10 +4954,10 @@ async function setMany(many, options) {
4947
4954
  let documents = [];
4948
4955
  const pathsToInvalidate = new Set();
4949
4956
  for (const { path, document: documentWithReservedFields } of many) {
4950
- // for setting, we should remove any pieces of the document that start with 'tarobase_' as they are internal and shouldn't be updated. The API would block it if it is one of the reserved ones
4957
+ // for setting, we should remove any pieces of the document that start with '_' as they are internal and shouldn't be updated. The API would block it if it is one of the reserved ones
4951
4958
  let document = documentWithReservedFields;
4952
4959
  if (documentWithReservedFields) {
4953
- document = Object.fromEntries(Object.entries(documentWithReservedFields).filter(([key]) => !key.startsWith('tarobase_')));
4960
+ document = Object.fromEntries(Object.entries(documentWithReservedFields).filter(([key]) => !key.startsWith('_')));
4954
4961
  }
4955
4962
  const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
4956
4963
  pathsToInvalidate.add(normalizedPath);
@@ -5074,7 +5081,7 @@ async function setMany(many, options) {
5074
5081
  var _a, _b, _c, _d, _e;
5075
5082
  // NOTE (backwards-compat revert): a program-allowlist on server-supplied
5076
5083
  // `preInstructions` was tried here for the audit-8 SOL-drain concern, but it
5077
- // broke legitimate onchain DeFi flows — tarobase routes Phoenix, Jupiter/swap,
5084
+ // broke legitimate onchain DeFi flows — bounded routes Phoenix, Jupiter/swap,
5078
5085
  // SPL-Token and ATA-creation instructions through standard-path preInstructions
5079
5086
  // (see sol-helper remaining-accounts-extractor). A correct audit-8 fix must be
5080
5087
  // DeFi-aware (e.g. block ONLY a raw System Transfer/TransferWithSeed, never a
@@ -5158,10 +5165,10 @@ async function setMany(many, options) {
5158
5165
  const transaction = web3_js.VersionedTransaction.deserialize(txBytes);
5159
5166
  // Validate the transaction before signing: ensure only allowed programs
5160
5167
  // and no unauthorized System program instructions (e.g., SOL transfers)
5161
- const TAROBASE_PROGRAM = 'poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZp';
5168
+ const BOUNDED_PROGRAM = 'poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZp';
5162
5169
  const COMPUTE_BUDGET = 'ComputeBudget111111111111111111111111111111';
5163
5170
  const SYSTEM_PROGRAM = '11111111111111111111111111111111';
5164
- const ALLOWED_PROGRAMS = new Set([TAROBASE_PROGRAM, COMPUTE_BUDGET, SYSTEM_PROGRAM]);
5171
+ const ALLOWED_PROGRAMS = new Set([BOUNDED_PROGRAM, COMPUTE_BUDGET, SYSTEM_PROGRAM]);
5165
5172
  // System program instruction discriminators (first 4 bytes, little-endian u32)
5166
5173
  const SYSTEM_TRANSFER = 2; // Transfer instruction index
5167
5174
  const SYSTEM_TRANSFER_WITH_SEED = 11;
@@ -5755,8 +5762,8 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
5755
5762
  if (appId && appId !== config.appId) {
5756
5763
  wsUrl.searchParams.append('appId', appId);
5757
5764
  }
5758
- else if (typeof window !== 'undefined' && window.CUSTOM_TAROBASE_APP_ID_HEADER) {
5759
- wsUrl.searchParams.append('appId', window.CUSTOM_TAROBASE_APP_ID_HEADER);
5765
+ else if (typeof window !== 'undefined' && window.CUSTOM_BOUNDED_APP_ID_HEADER) {
5766
+ wsUrl.searchParams.append('appId', window.CUSTOM_BOUNDED_APP_ID_HEADER);
5760
5767
  }
5761
5768
  else {
5762
5769
  wsUrl.searchParams.append('appId', config.appId);
@@ -6616,7 +6623,7 @@ function serverTimestamp() {
6616
6623
  // ---------------------------------------------------------------------------
6617
6624
  // IDB helpers (lazy-loaded, non-blocking)
6618
6625
  // ---------------------------------------------------------------------------
6619
- const IDB_NAME = 'tarobase-realtime';
6626
+ const IDB_NAME = 'bounded-realtime';
6620
6627
  const IDB_STORE = 'subscriptions';
6621
6628
  const IDB_VERSION = 1;
6622
6629
  let idbPromise = null;
@@ -7054,10 +7061,10 @@ class RealtimeStore {
7054
7061
  const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
7055
7062
  const collectionPath = this.getCollectionPath(normalizedPath);
7056
7063
  const optimisticDoc = Object.assign(Object.assign({ _id: normalizedPath, pathId: normalizedPath }, resolvedDoc), {
7057
- // System timestamp field name differs by stack: the Bounded worker stamps
7058
- // the neutral `_updatedAt`; legacy Tarobase stamps `tarobase_updated_at`.
7064
+ // System timestamp field name: the Bounded worker stamps the neutral
7065
+ // `_updatedAt`; the underscore-prefixed `_updated_at` metadata mirror.
7059
7066
  // Match it so the optimistic doc lines up with the server's confirmation.
7060
- [isBoundedNetwork() ? '_updatedAt' : 'tarobase_updated_at']: Date.now() });
7067
+ [isBoundedNetwork() ? '_updatedAt' : '_updated_at']: Date.now() });
7061
7068
  const sub = this.findSubscriptionByPath(collectionPath);
7062
7069
  let prevDoc = null;
7063
7070
  if (sub) {
@@ -7510,6 +7517,7 @@ const functions = { invoke };
7510
7517
  // collection. This file is the consumer surface around that runtime:
7511
7518
  //
7512
7519
  // bounded.live.intent(roomPath, intent) -> POST {realtime}/live/intent
7520
+ // bounded.live.status(roomPath) -> GET {realtime}/live/status
7513
7521
  //
7514
7522
  // It mirrors `functions.invoke` exactly: it attaches the caller's session token
7515
7523
  // AUTOMATICALLY (the same token the SDK uses for data reads/writes), and throws
@@ -7522,18 +7530,14 @@ const functions = { invoke };
7522
7530
  // Addressing: clients address rooms BY PATH (the `path` field in the body). The
7523
7531
  // worker derives the roomId from `body.path` and sets X-Room-Id internally;
7524
7532
  // clients NEVER set X-Room-Id. The caller's address is taken server-side from
7525
- // the Authorization header (auth.userAddress) — it is NOT sent in the body.
7533
+ // the Authorization header (auth.userId/userAddress) — it is NOT sent in the
7534
+ // body.
7526
7535
  //
7527
7536
  // Subscribing to your view: a per-player view doc lives at
7528
- // `<roomPath>/view/<myAddress>` (the policy declares
7529
- // `rooms/$roomId/view/$addr` ephemeral with `read: $addr == @user.address`).
7530
- // That path has an EVEN segment count, so it is an ordinary document path —
7531
- // plain `subscribe('<roomPath>/view/<myAddress>', { onData })` already works.
7532
- // `subscribeView()` below is thin sugar over that (path construction + address
7533
- // defaulting); it adds zero new transport.
7534
- //
7535
- // Status polling (GET /live/status?path=<sessionCollection>/<roomId>) is out of
7536
- // scope for this piece — see follow-up.
7537
+ // `<roomPath>/view/<myUserId>` (the policy declares
7538
+ // `rooms/$roomId/view/$userId` ephemeral with `read: $userId == @user.id`).
7539
+ // Wallet-address keyed view paths remain supported through opts.address for
7540
+ // older policies, but new live rooms should key views by @user.id.
7537
7541
  // ---------------------------------------------------------------------------
7538
7542
  class LiveIntentError extends Error {
7539
7543
  constructor(message, statusCode, details) {
@@ -7614,7 +7618,7 @@ async function intent(roomPath, intent, opts = {}) {
7614
7618
  res = await fetch(`${base}/live/intent`, {
7615
7619
  method: 'POST',
7616
7620
  headers,
7617
- body: JSON.stringify({ path: roomPath, intent }),
7621
+ body: JSON.stringify({ path: normalizedRoomPath, intent }),
7618
7622
  signal: controller.signal,
7619
7623
  });
7620
7624
  }
@@ -7642,43 +7646,95 @@ async function intent(roomPath, intent, opts = {}) {
7642
7646
  }
7643
7647
  return (body !== null && body !== void 0 ? body : { ok: true });
7644
7648
  }
7649
+ /**
7650
+ * Fetch the runtime status for a live room.
7651
+ *
7652
+ * const s = await bounded.live.status('rooms/abc');
7653
+ * console.log(s.running, s.stopReason, s.generation, s.etag);
7654
+ *
7655
+ * This is diagnostic/ops surface. It reports whether the room facet exists,
7656
+ * whether it is currently ticking, why it last stopped, which module etag is
7657
+ * loaded, and the current generation used after terminal restarts.
7658
+ */
7659
+ async function status(roomPath, opts = {}) {
7660
+ var _a, _b, _c, _d, _e;
7661
+ if (!roomPath || typeof roomPath !== 'string') {
7662
+ throw new LiveIntentError('A room path is required');
7663
+ }
7664
+ const normalizedRoomPath = roomPath.replace(/\/$/, '');
7665
+ const config = await getConfig();
7666
+ const base = realtimeHttpBase(config.wsApiUrl);
7667
+ const headers = Object.assign({ 'X-App-Id': config.appId, 'X-Public-App-Id': config.appId }, ((_a = opts.headers) !== null && _a !== void 0 ? _a : {}));
7668
+ const controller = new AbortController();
7669
+ const timeoutMs = (_b = opts.timeoutMs) !== null && _b !== void 0 ? _b : 15000;
7670
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
7671
+ let res;
7672
+ try {
7673
+ res = await fetch(`${base}/live/status?path=${encodeURIComponent(normalizedRoomPath)}`, {
7674
+ method: 'GET',
7675
+ headers,
7676
+ signal: controller.signal,
7677
+ });
7678
+ }
7679
+ catch (err) {
7680
+ clearTimeout(timer);
7681
+ if ((err === null || err === void 0 ? void 0 : err.name) === 'AbortError') {
7682
+ throw new LiveIntentError(`Live status for "${normalizedRoomPath}" timed out after ${timeoutMs}ms`);
7683
+ }
7684
+ throw new LiveIntentError(`Failed to reach the realtime worker: ${(_c = err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : String(err)}`);
7685
+ }
7686
+ clearTimeout(timer);
7687
+ let body = null;
7688
+ const text = await res.text();
7689
+ if (text) {
7690
+ try {
7691
+ body = JSON.parse(text);
7692
+ }
7693
+ catch (_f) {
7694
+ body = { raw: text };
7695
+ }
7696
+ }
7697
+ if (!res.ok) {
7698
+ const message = (_e = (_d = body === null || body === void 0 ? void 0 : body.error) !== null && _d !== void 0 ? _d : body === null || body === void 0 ? void 0 : body.message) !== null && _e !== void 0 ? _e : `Live status failed with HTTP ${res.status}`;
7699
+ throw new LiveIntentError(message, res.status, body);
7700
+ }
7701
+ return body;
7702
+ }
7645
7703
  /**
7646
7704
  * Subscribe to YOUR per-player view of a room. Thin sugar over:
7647
7705
  *
7648
- * subscribe('<roomPath>/view/<myAddress>', { onData, onError })
7706
+ * subscribe('<roomPath>/view/<myUserId>', { onData, onError })
7649
7707
  *
7650
- * The address defaults to the logged-in user's address (from the session
7651
- * token's claims); pass `opts.address` to override. Returns the unsubscribe
7708
+ * The view id defaults to the logged-in user's @user.id (from the session token
7709
+ * claims); pass `opts.userId` to override. `opts.address` is kept as a legacy
7710
+ * alias for older wallet-address keyed policies. Returns the unsubscribe
7652
7711
  * function (a Promise<() => Promise<void>>, same as `subscribe`).
7653
7712
  *
7654
7713
  * Note: this is a browser-first helper (the WS subscription manager is
7655
7714
  * browser-oriented). Server consumers should use `live.intent`.
7656
7715
  */
7657
7716
  async function subscribeView(roomPath, opts) {
7658
- var _a;
7717
+ var _a, _b, _c;
7659
7718
  if (!roomPath || typeof roomPath !== 'string') {
7660
7719
  throw new LiveIntentError('A room path is required');
7661
7720
  }
7662
7721
  if (!opts || typeof opts.onData !== 'function') {
7663
7722
  throw new LiveIntentError('subscribeView requires an onData callback');
7664
7723
  }
7665
- let address = opts.address;
7666
- if (!address) {
7724
+ let viewUserId = (_a = opts.userId) !== null && _a !== void 0 ? _a : opts.address;
7725
+ if (!viewUserId) {
7667
7726
  const config = await getConfig();
7668
7727
  const info = await getUserInfo(config.isServer);
7669
- // getUserInfo returns the RAW idToken payload the wallet address lives in
7670
- // the `custom:walletAddress` claim, NOT a flat `address` field. Reading only
7671
- // `.address` meant auto-resolution NEVER worked, forcing every caller (browser
7672
- // AND server) to pass opts.address the client having to hand the backend an
7673
- // identity it already authenticated. Resolve it from the actual claim so a
7674
- // logged-in caller never needs to pass its own address.
7675
- address = (_a = info === null || info === void 0 ? void 0 : info.address) !== null && _a !== void 0 ? _a : info === null || info === void 0 ? void 0 : info['custom:walletAddress'];
7728
+ // getUserInfo returns the RAW idToken payload. The universal live view key
7729
+ // is @user.id (`custom:userId`); wallet-address keyed views are still
7730
+ // resolved as a compatibility fallback for older policies.
7731
+ viewUserId = (_c = (_b = info === null || info === void 0 ? void 0 : info['custom:userId']) !== null && _b !== void 0 ? _b : info === null || info === void 0 ? void 0 : info['custom:walletAddress']) !== null && _c !== void 0 ? _c : info === null || info === void 0 ? void 0 : info.address;
7676
7732
  }
7677
- if (!address || typeof address !== 'string') {
7678
- throw new LiveIntentError('Could not resolve a player address for subscribeView; pass opts.address or log in first');
7733
+ if (!viewUserId || typeof viewUserId !== 'string') {
7734
+ throw new LiveIntentError('Could not resolve a player view id for subscribeView; pass opts.userId or log in first');
7679
7735
  }
7680
7736
  const normalizedRoomPath = roomPath.replace(/\/$/, '');
7681
- const viewPath = `${normalizedRoomPath}/view/${address}`;
7737
+ const viewPath = `${normalizedRoomPath}/view/${viewUserId}`;
7682
7738
  // This is a LIVE/room subscription by construction (the caller hands us the
7683
7739
  // room path), so we route its connection to the per-room DO where the live
7684
7740
  // view fan-out runs. App code passes NO routing — the room path is the helper's
@@ -7689,7 +7745,7 @@ async function subscribeView(roomPath, opts) {
7689
7745
  return subscribeV2(viewPath, { onData: opts.onData, onError: opts.onError }, normalizedRoomPath);
7690
7746
  }
7691
7747
  /** The `bounded.live` namespace surface. */
7692
- const live = { intent, subscribeView };
7748
+ const live = { intent, status, subscribeView };
7693
7749
 
7694
7750
  // ---------------------------------------------------------------------------
7695
7751
  // live-effects.ts -- AUTHOR-facing types + helpers for writing a Bounded live
@@ -7778,6 +7834,7 @@ exports.invokeFunction = invoke;
7778
7834
  exports.isEffectResult = isEffectResult;
7779
7835
  exports.live = live;
7780
7836
  exports.liveIntent = intent;
7837
+ exports.liveStatus = status;
7781
7838
  exports.queryAggregate = queryAggregate;
7782
7839
  exports.reconnectWithNewAuth = reconnectWithNewAuth;
7783
7840
  exports.refreshSession = refreshSession;