@bounded-sh/core 0.0.2 → 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.mjs CHANGED
@@ -4909,6 +4909,13 @@ async function runExpressionMany(many) {
4909
4909
  throw error;
4910
4910
  }
4911
4911
  }
4912
+ /**
4913
+ * Write a document at `path`. Sugar for a one-element {@link setMany}.
4914
+ *
4915
+ * **Delete:** pass `null` as the document to delete it — `set(path, null)` is
4916
+ * the delete (there is no separate `del`/`remove`). It is routed through the
4917
+ * collection's policy `delete` rule and broadcasts a delete to subscribers.
4918
+ */
4912
4919
  async function set(path, document, options) {
4913
4920
  const result = await setMany([{ path, document }], options);
4914
4921
  // Clear cache entries that might be affected by this update
@@ -7490,6 +7497,7 @@ const functions = { invoke };
7490
7497
  // collection. This file is the consumer surface around that runtime:
7491
7498
  //
7492
7499
  // bounded.live.intent(roomPath, intent) -> POST {realtime}/live/intent
7500
+ // bounded.live.status(roomPath) -> GET {realtime}/live/status
7493
7501
  //
7494
7502
  // It mirrors `functions.invoke` exactly: it attaches the caller's session token
7495
7503
  // AUTOMATICALLY (the same token the SDK uses for data reads/writes), and throws
@@ -7502,18 +7510,14 @@ const functions = { invoke };
7502
7510
  // Addressing: clients address rooms BY PATH (the `path` field in the body). The
7503
7511
  // worker derives the roomId from `body.path` and sets X-Room-Id internally;
7504
7512
  // clients NEVER set X-Room-Id. The caller's address is taken server-side from
7505
- // the Authorization header (auth.userAddress) — it is NOT sent in the body.
7513
+ // the Authorization header (auth.userId/userAddress) — it is NOT sent in the
7514
+ // body.
7506
7515
  //
7507
7516
  // Subscribing to your view: a per-player view doc lives at
7508
- // `<roomPath>/view/<myAddress>` (the policy declares
7509
- // `rooms/$roomId/view/$addr` ephemeral with `read: $addr == @user.address`).
7510
- // That path has an EVEN segment count, so it is an ordinary document path —
7511
- // plain `subscribe('<roomPath>/view/<myAddress>', { onData })` already works.
7512
- // `subscribeView()` below is thin sugar over that (path construction + address
7513
- // defaulting); it adds zero new transport.
7514
- //
7515
- // Status polling (GET /live/status?path=<sessionCollection>/<roomId>) is out of
7516
- // scope for this piece — see follow-up.
7517
+ // `<roomPath>/view/<myUserId>` (the policy declares
7518
+ // `rooms/$roomId/view/$userId` ephemeral with `read: $userId == @user.id`).
7519
+ // Wallet-address keyed view paths remain supported through opts.address for
7520
+ // older policies, but new live rooms should key views by @user.id.
7517
7521
  // ---------------------------------------------------------------------------
7518
7522
  class LiveIntentError extends Error {
7519
7523
  constructor(message, statusCode, details) {
@@ -7594,7 +7598,7 @@ async function intent(roomPath, intent, opts = {}) {
7594
7598
  res = await fetch(`${base}/live/intent`, {
7595
7599
  method: 'POST',
7596
7600
  headers,
7597
- body: JSON.stringify({ path: roomPath, intent }),
7601
+ body: JSON.stringify({ path: normalizedRoomPath, intent }),
7598
7602
  signal: controller.signal,
7599
7603
  });
7600
7604
  }
@@ -7622,43 +7626,95 @@ async function intent(roomPath, intent, opts = {}) {
7622
7626
  }
7623
7627
  return (body !== null && body !== void 0 ? body : { ok: true });
7624
7628
  }
7629
+ /**
7630
+ * Fetch the runtime status for a live room.
7631
+ *
7632
+ * const s = await bounded.live.status('rooms/abc');
7633
+ * console.log(s.running, s.stopReason, s.generation, s.etag);
7634
+ *
7635
+ * This is diagnostic/ops surface. It reports whether the room facet exists,
7636
+ * whether it is currently ticking, why it last stopped, which module etag is
7637
+ * loaded, and the current generation used after terminal restarts.
7638
+ */
7639
+ async function status(roomPath, opts = {}) {
7640
+ var _a, _b, _c, _d, _e;
7641
+ if (!roomPath || typeof roomPath !== 'string') {
7642
+ throw new LiveIntentError('A room path is required');
7643
+ }
7644
+ const normalizedRoomPath = roomPath.replace(/\/$/, '');
7645
+ const config = await getConfig();
7646
+ const base = realtimeHttpBase(config.wsApiUrl);
7647
+ const headers = Object.assign({ 'X-App-Id': config.appId, 'X-Public-App-Id': config.appId }, ((_a = opts.headers) !== null && _a !== void 0 ? _a : {}));
7648
+ const controller = new AbortController();
7649
+ const timeoutMs = (_b = opts.timeoutMs) !== null && _b !== void 0 ? _b : 15000;
7650
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
7651
+ let res;
7652
+ try {
7653
+ res = await fetch(`${base}/live/status?path=${encodeURIComponent(normalizedRoomPath)}`, {
7654
+ method: 'GET',
7655
+ headers,
7656
+ signal: controller.signal,
7657
+ });
7658
+ }
7659
+ catch (err) {
7660
+ clearTimeout(timer);
7661
+ if ((err === null || err === void 0 ? void 0 : err.name) === 'AbortError') {
7662
+ throw new LiveIntentError(`Live status for "${normalizedRoomPath}" timed out after ${timeoutMs}ms`);
7663
+ }
7664
+ 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)}`);
7665
+ }
7666
+ clearTimeout(timer);
7667
+ let body = null;
7668
+ const text = await res.text();
7669
+ if (text) {
7670
+ try {
7671
+ body = JSON.parse(text);
7672
+ }
7673
+ catch (_f) {
7674
+ body = { raw: text };
7675
+ }
7676
+ }
7677
+ if (!res.ok) {
7678
+ 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}`;
7679
+ throw new LiveIntentError(message, res.status, body);
7680
+ }
7681
+ return body;
7682
+ }
7625
7683
  /**
7626
7684
  * Subscribe to YOUR per-player view of a room. Thin sugar over:
7627
7685
  *
7628
- * subscribe('<roomPath>/view/<myAddress>', { onData, onError })
7686
+ * subscribe('<roomPath>/view/<myUserId>', { onData, onError })
7629
7687
  *
7630
- * The address defaults to the logged-in user's address (from the session
7631
- * token's claims); pass `opts.address` to override. Returns the unsubscribe
7688
+ * The view id defaults to the logged-in user's @user.id (from the session token
7689
+ * claims); pass `opts.userId` to override. `opts.address` is kept as a legacy
7690
+ * alias for older wallet-address keyed policies. Returns the unsubscribe
7632
7691
  * function (a Promise<() => Promise<void>>, same as `subscribe`).
7633
7692
  *
7634
7693
  * Note: this is a browser-first helper (the WS subscription manager is
7635
7694
  * browser-oriented). Server consumers should use `live.intent`.
7636
7695
  */
7637
7696
  async function subscribeView(roomPath, opts) {
7638
- var _a;
7697
+ var _a, _b, _c;
7639
7698
  if (!roomPath || typeof roomPath !== 'string') {
7640
7699
  throw new LiveIntentError('A room path is required');
7641
7700
  }
7642
7701
  if (!opts || typeof opts.onData !== 'function') {
7643
7702
  throw new LiveIntentError('subscribeView requires an onData callback');
7644
7703
  }
7645
- let address = opts.address;
7646
- if (!address) {
7704
+ let viewUserId = (_a = opts.userId) !== null && _a !== void 0 ? _a : opts.address;
7705
+ if (!viewUserId) {
7647
7706
  const config = await getConfig();
7648
7707
  const info = await getUserInfo(config.isServer);
7649
- // getUserInfo returns the RAW idToken payload the wallet address lives in
7650
- // the `custom:walletAddress` claim, NOT a flat `address` field. Reading only
7651
- // `.address` meant auto-resolution NEVER worked, forcing every caller (browser
7652
- // AND server) to pass opts.address the client having to hand the backend an
7653
- // identity it already authenticated. Resolve it from the actual claim so a
7654
- // logged-in caller never needs to pass its own address.
7655
- 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'];
7708
+ // getUserInfo returns the RAW idToken payload. The universal live view key
7709
+ // is @user.id (`custom:userId`); wallet-address keyed views are still
7710
+ // resolved as a compatibility fallback for older policies.
7711
+ 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;
7656
7712
  }
7657
- if (!address || typeof address !== 'string') {
7658
- throw new LiveIntentError('Could not resolve a player address for subscribeView; pass opts.address or log in first');
7713
+ if (!viewUserId || typeof viewUserId !== 'string') {
7714
+ throw new LiveIntentError('Could not resolve a player view id for subscribeView; pass opts.userId or log in first');
7659
7715
  }
7660
7716
  const normalizedRoomPath = roomPath.replace(/\/$/, '');
7661
- const viewPath = `${normalizedRoomPath}/view/${address}`;
7717
+ const viewPath = `${normalizedRoomPath}/view/${viewUserId}`;
7662
7718
  // This is a LIVE/room subscription by construction (the caller hands us the
7663
7719
  // room path), so we route its connection to the per-room DO where the live
7664
7720
  // view fan-out runs. App code passes NO routing — the room path is the helper's
@@ -7669,7 +7725,7 @@ async function subscribeView(roomPath, opts) {
7669
7725
  return subscribeV2(viewPath, { onData: opts.onData, onError: opts.onError }, normalizedRoomPath);
7670
7726
  }
7671
7727
  /** The `bounded.live` namespace surface. */
7672
- const live = { intent, subscribeView };
7728
+ const live = { intent, status, subscribeView };
7673
7729
 
7674
7730
  // ---------------------------------------------------------------------------
7675
7731
  // live-effects.ts -- AUTHOR-facing types + helpers for writing a Bounded live
@@ -7722,5 +7778,5 @@ function defineLiveModule(mod) {
7722
7778
  return mod;
7723
7779
  }
7724
7780
 
7725
- export { EFFECT_INTENT_ADDRESS, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, ReactNativeSessionManager, RealtimeStore, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithSignature, defineLiveModule, deriveUserIdentityFromIdToken, functions, genAuthNonce, genSolanaMessage, get, getActiveSessionManager, getCachedData, getConfig, getFiles, getIdToken, getMany, getRealtimeStore, getWebhookKeysUrl, hasActiveConnection, increment, init, invoke as invokeFunction, isEffectResult, live, intent as liveIntent, queryAggregate, reconnectWithNewAuth, refreshSession, resetRealtimeStore, revokeSession, runExpression, runExpressionMany, runQuery, runQueryMany, search, serverTimestamp, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe, subscribeView as subscribeLiveView, withEffects, wsDelete, wsGet, wsGetMany, wsQuery, wsSet };
7781
+ export { EFFECT_INTENT_ADDRESS, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, ReactNativeSessionManager, RealtimeStore, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithSignature, defineLiveModule, deriveUserIdentityFromIdToken, functions, genAuthNonce, genSolanaMessage, get, getActiveSessionManager, getCachedData, getConfig, getFiles, getIdToken, getMany, getRealtimeStore, getWebhookKeysUrl, hasActiveConnection, increment, init, invoke as invokeFunction, isEffectResult, live, intent as liveIntent, status as liveStatus, queryAggregate, reconnectWithNewAuth, refreshSession, resetRealtimeStore, revokeSession, runExpression, runExpressionMany, runQuery, runQueryMany, search, serverTimestamp, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe, subscribeView as subscribeLiveView, withEffects, wsDelete, wsGet, wsGetMany, wsQuery, wsSet };
7726
7782
  //# sourceMappingURL=index.mjs.map