js-bao-wss-client 2.2.0-alpha.2 → 2.2.0-alpha.4

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/README.md CHANGED
@@ -740,7 +740,7 @@ The client emits `documentMetadataChanged` whenever local metadata changes or se
740
740
  - Typical emissions:
741
741
  - **created/local**: immediately after `documents.create(...)` updates local cache. `changedFields` often includes `createdAt`, `pendingCreate`, `localOnly`, and optionally `title`.
742
742
  - **updated/local**: after local changes such as `documents.update(...)` (optimistic `title`), sync status updates (`lastSyncedAt`, `hasUnsyncedLocalChanges`), or localBytes refresh.
743
- - **updated/server**: after `documents.list({ refreshFromServer: true })` or network-first list merges server metadata (e.g., `title`, `permission`), `changedFields` reflects updated properties.
743
+ - **updated/server**: after `me.ownedDocuments({ refreshFromServer: true })` or network-first list merges server metadata (e.g., `title`, `permission`), `changedFields` reflects updated properties.
744
744
  - **evicted/local**: after `documents.evict(id)` or `documents.evictAll(...)`; `metadata` is `null`.
745
745
  - **deleted/server or local**: the first delete seen (server push, list refresh, or local `documents.delete`) emits a single `deleted` event; subsequent delete/evict/list refreshes for the same doc are suppressed to avoid duplicates (including 404/offline fallbacks after a successful delete).
746
746
 
@@ -810,11 +810,11 @@ await client.goOnline();
810
810
 
811
811
  ## Metadata Cache and Local Documents
812
812
 
813
- The client maintains an IndexedDB-backed metadata index so apps can render lists and document summaries offline. Local listing is merged into `documents.list(...)`; the former `documents.listLocal()` is removed.
813
+ The client maintains an IndexedDB-backed metadata index so apps can render lists and document summaries offline. Local listing is merged into `me.ownedDocuments(...)`; the former `documents.listLocal()` is removed.
814
814
 
815
815
  ```typescript
816
816
  // List documents (cache-first with background refresh by default)
817
- const docs = await client.documents.list({
817
+ const docs = await client.me.ownedDocuments({
818
818
  includeRoot: false,
819
819
  // Default behavior is cache-first with background refresh when local cache exists
820
820
  // You can control it explicitly with waitForLoad (see below)
@@ -841,7 +841,7 @@ client.setRetentionPolicy({
841
841
 
842
842
  Notes:
843
843
 
844
- - The client updates the local metadata cache automatically when `documents.list()` returns server data (including last-known permission and root doc metadata). Root is always cached from the server but filtered out of the returned list unless you pass `includeRoot: true`.
844
+ - The client updates the local metadata cache automatically when `me.ownedDocuments()` returns server data (including last-known permission and root doc metadata). Root is always cached from the server but filtered out of the returned list unless you pass `includeRoot: true`.
845
845
  - Cache updates emit `documentMetadataChanged` events (typically with `action: "updated"`, `source: "server"`).
846
846
  - Local eviction emits `documentMetadataChanged` with `action: "evicted"`, `metadata: null`.
847
847
  - Delete emits a single `documentMetadataChanged` with `action: "deleted"`, then evicts locally without a second emission.
@@ -900,19 +900,19 @@ Events: `pendingCreateCommitted`, `pendingCreateFailed` help drive UI state.
900
900
 
901
901
  ## Root Documents
902
902
 
903
- Some apps use a per-user root document. The server always returns the root in list responses (unless tag-filtered), and the client caches it. By default `documents.list()` filters it out; pass `includeRoot: true` to surface it (works offline after it’s cached).
903
+ Some apps use a per-user root document. The server always returns the root in list responses (unless tag-filtered), and the client caches it. By default `me.ownedDocuments()` filters it out; pass `includeRoot: true` to surface it (works offline after it’s cached).
904
904
 
905
905
  ```typescript
906
906
  // Exclude root (default)
907
- const docs = await client.documents.list();
907
+ const docs = await client.me.ownedDocuments();
908
908
 
909
909
  // Include root document(s)
910
- const all = await client.documents.list({ includeRoot: true });
910
+ const all = await client.me.ownedDocuments({ includeRoot: true });
911
911
  ```
912
912
 
913
913
  ## Behavior changes
914
914
 
915
- - **Root documents listing**: `documents.list()` excludes root docs by default. Opt-in with `{ includeRoot: true }`.
915
+ - **Root documents listing**: `me.ownedDocuments()` excludes root docs by default. Opt-in with `{ includeRoot: true }`.
916
916
  - **Offline mode requests**: When `networkMode` is `"offline"`, HTTP calls fail fast with code `OFFLINE`.
917
917
  - **Open options**: `documents.open()` uses `{ waitForLoad, enableNetworkSync, retainLocal, availabilityWaitMs }`. Older options like `waitForPermission`, `offlineWritePolicy`, per-doc `offline`, `provisionalPermission`, and `startNetwork` are removed.
918
918
  - **Create return shape**: `documents.create()` returns `{ metadata }` (no `Y.Doc`).
@@ -1160,8 +1160,11 @@ const { metadata } = await client.documents.create({
1160
1160
  });
1161
1161
  console.log("Created document:", metadata.documentId);
1162
1162
 
1163
- // List all documents user has access to
1164
- const documents = await client.documents.list();
1163
+ // List documents. There are two readers, and they are disjoint: the documents
1164
+ // the user owns, and the documents shared with them. Reading both is what it
1165
+ // takes to see everything the user has access to.
1166
+ const owned = await client.me.ownedDocuments(); // DocumentInfo[]
1167
+ const { items: shared } = await client.me.sharedDocuments(); // page 1 of { items, nextCursor }
1165
1168
 
1166
1169
  // Get document details (network)
1167
1170
  const docInfo = await client.documents.get(documentId);
@@ -1284,9 +1287,6 @@ await client.document(documentId).transferOwnership(newOwnerId);
1284
1287
  const accessResult = await client.documents.validateAccess(documentId);
1285
1288
  if (accessResult.hasAccess) {
1286
1289
  console.log("User has access:", accessResult.permission);
1287
- if (accessResult.viaInvitation) {
1288
- console.log("Access via invitation");
1289
- }
1290
1290
  }
1291
1291
  ```
1292
1292
 
@@ -1680,50 +1680,35 @@ function encodeRFC5987(value) {
1680
1680
 
1681
1681
  The canonical request uses only `origin + pathname`, so all disposition variants reuse the same cache entry. Metadata can live in memory (as shown) or in IndexedDB if you need to survive worker restarts. Because cached responses are stored without `Content-Disposition`, each hit reapplies headers based on the active request. Extend the sample with background eviction or cache versioning as needed.
1682
1682
 
1683
- ### Document Invitations
1684
-
1685
- ```typescript
1686
- // Create an invitation
1687
- const invitation = await client.documents.createInvitation(
1688
- documentId,
1689
- "user@example.com",
1690
- "read-write" // 'read-write' | 'reader'
1691
- );
1692
- console.log("Invitation created:", invitation.invitationId);
1683
+ ### Document Sharing
1693
1684
 
1694
- // List all invitations for a document
1695
- const invitations = await client.documents.listInvitations(documentId);
1685
+ Per-document invitations were removed in client `3.0.0`. Sharing is
1686
+ immediate: `updatePermissions` grants access to a registered user on the spot,
1687
+ and for an email that has not registered yet it writes a deferred grant plus an
1688
+ app-level invitation whose `inviteToken` the recipient redeems.
1696
1689
 
1697
- // Update an invitation (changes permission)
1698
- const updatedInvitation = await client.documents.updateInvitation(
1699
- documentId,
1700
- "user@example.com",
1701
- "reader"
1702
- );
1690
+ ```typescript
1691
+ // Share with an existing user (takes effect immediately) or defer the grant
1692
+ // for an email that has not registered yet.
1693
+ const grant = await client.documents.updatePermissions(documentId, {
1694
+ email: "user@example.com",
1695
+ permission: "read-write", // 'read-write' | 'reader'
1696
+ });
1697
+ if (grant.deferred) {
1698
+ console.log("Deferred until they accept:", grant.inviteToken);
1699
+ }
1703
1700
 
1704
- // Get specific invitation
1705
- const inv = await client.documents.getInvitation(
1706
- documentId,
1707
- "user@example.com"
1708
- );
1701
+ // Deferred grants still waiting on their recipient
1702
+ const pending = await client.documents.listPendingInvitations(documentId);
1709
1703
 
1710
- // Delete an invitation
1711
- await client.documents.deleteInvitation(documentId, invitationId);
1704
+ // Withdraw access (a live grant or a pending deferred one)
1705
+ await client.documents.removePermission(documentId, {
1706
+ email: "user@example.com",
1707
+ });
1712
1708
 
1713
- // Accept or decline (invitee)
1714
- await client.document(documentId).acceptInvitation();
1715
- await client.document(documentId).declineInvitation(invitationId);
1716
- ```
1717
-
1718
- ### Pending Document Invitations (for the current user)
1719
-
1720
- ```typescript
1721
- // List documents you’ve been invited to (pending, unexpired)
1722
- const pending = await client.me.pendingDocumentInvitations();
1723
- // Each item includes a best-effort `document` block with metadata (title, tags, createdAt, lastModified, createdBy)
1724
- for (const inv of pending) {
1725
- console.log(inv.document?.title, inv.document?.tags);
1726
- }
1709
+ // The recipient redeems the app-level invitation, which resolves every
1710
+ // deferred grant issued to their email.
1711
+ await client.invitations.accept(inviteToken);
1727
1712
  ```
1728
1713
 
1729
1714
  ### Users
@@ -1734,53 +1719,6 @@ const user = await client.users.getBasic("u01H...");
1734
1719
  console.log(user.name, user.email, user.appRole);
1735
1720
  ```
1736
1721
 
1737
- ### Invitation events
1738
-
1739
- The client emits a unified `invitation` event for real-time invitation changes delivered over the WebSocket. Payload:
1740
-
1741
- - `{ type: "invitation"; action: "created" | "updated" | "cancelled" | "declined" | "accepted"; invitationId; documentId; permission; title?; invitedBy?; invitedAt?; expiresAt?; acceptedBy?; document?: { title?; tags?; createdAt?; lastModified?; createdBy? } }`
1742
-
1743
- #### Actions — who receives each
1744
-
1745
- Events are **targeted**: most actions are delivered to only one side of the invitation (inviter _or_ invitee, not both). A consumer that only subscribes from the invitee side will never see `accepted`, and a consumer that only subscribes from the inviter side will never see `created`, `updated`, or `cancelled`. Both sides of the UI should handle the actions relevant to them.
1746
-
1747
- | `action` | Delivered to | When it fires |
1748
- | ------------ | --------------------- | ----------------------------------------------------------------------------- |
1749
- | `created` | **Invitee only** | A new pending invitation has been issued to this user. |
1750
- | `updated` | **Invitee only** | An existing pending invitation's permission / expiry was changed. |
1751
- | `cancelled` | **Invitee only** | The inviter (or an admin) cancelled a pending invitation before acceptance. |
1752
- | `declined` | **Invitee + inviter** | The invitee explicitly declined; both sides are notified. |
1753
- | `accepted` | **Inviter only** | The invitee accepted; `acceptedBy` carries the accepting user's `userId`. |
1754
-
1755
- Consumers writing a `switch` on `evt.action` should include a `default` branch that's either a no-op or a warning rather than throwing — new action values may be added in the future (non-breaking).
1756
-
1757
- Example:
1758
-
1759
- ```ts
1760
- client.on("invitation", (evt) => {
1761
- switch (evt.action) {
1762
- case "created":
1763
- case "updated":
1764
- case "cancelled":
1765
- // invitee-side: refresh pending-invitations list / badge
1766
- break;
1767
- case "declined":
1768
- // inviter-side: invite is no longer pending
1769
- break;
1770
- case "accepted":
1771
- // inviter-side: new collaborator
1772
- console.log(`${evt.acceptedBy} accepted invitation to ${evt.documentId}`);
1773
- break;
1774
- default:
1775
- // Unknown action — log and ignore rather than throw, so the client
1776
- // stays forward-compatible if new action values are added later.
1777
- console.warn("Unknown invitation action", evt);
1778
- }
1779
- });
1780
- ```
1781
-
1782
- Use this to refresh invitation lists or badge counts without polling.
1783
-
1784
1722
  ## Large Language Models (LLM)
1785
1723
 
1786
1724
  > **Deprecated.** The direct LLM client API (`client.llm.*`) is deprecated and will be removed in a future major release. Use managed prompts (`client.prompts.execute`) or a workflow `llm.chat` step instead.
@@ -3485,13 +3423,12 @@ class CollaborativeEditor {
3485
3423
  permission: "read-write" | "reader"
3486
3424
  ) {
3487
3425
  try {
3488
- const invitation = await this.client.documents.createInvitation(
3489
- documentId,
3426
+ const grant = await this.client.documents.updatePermissions(documentId, {
3490
3427
  email,
3491
- permission
3492
- );
3493
- console.log("Invitation sent:", invitation.invitationId);
3494
- return invitation;
3428
+ permission,
3429
+ });
3430
+ console.log("Shared with", email, grant);
3431
+ return grant;
3495
3432
  } catch (error) {
3496
3433
  console.error("Failed to invite user:", error);
3497
3434
  throw error;
@@ -37,7 +37,7 @@ export { JsBaoError, isJsBaoError, LockTimeoutError, JsBaoApiError, isJsBaoApiEr
37
37
  export type { JsBaoErrorCode } from "./errors";
38
38
  export { AuthError, AUTH_CODES, googleWebClientAvailable, } from "./internal/authController";
39
39
  export type { AuthCode, GoogleClientConfig, GoogleClientsConfig, } from "./internal/authController";
40
- export type { DocumentInfo, DocumentPermissionEntry, DocumentInvitation, DocumentInvitationResponse, DocumentAccessResult, DocumentGroupPermissionEntry, DocumentAliasInfo, DocumentAliasScope, PermissionUpdateResult, DirectPermissionGrant, DeferredPermissionGrant, PendingInvitationEntry, PendingGroupInvitationEntry, LinkAccessResult, } from "./api/documentsApi";
40
+ export type { DocumentInfo, DocumentPermissionEntry, DocumentAccessResult, DocumentGroupPermissionEntry, DocumentAliasInfo, DocumentAliasScope, PermissionUpdateResult, DirectPermissionGrant, DeferredPermissionGrant, PendingInvitationEntry, PendingGroupInvitationEntry, LinkAccessResult, } from "./api/documentsApi";
41
41
  export type { UserProfile, SharedDocument, SharedDocumentListResult, SharedDocumentsOptions, OwnedDocumentsOptions, } from "./api/meApi";
42
42
  export type { SessionInfo } from "./api/sessionApi";
43
43
  export type { BasicUserInfo, BatchUserProfile } from "./api/usersApi";
@@ -93,7 +93,7 @@ export type { DocumentDebugSnapshot, DocumentPermission, LocalDocumentEntry, Loc
93
93
  export type { LogLevel } from "./internal/logger";
94
94
  export type { RequestOptions } from "./internal/httpClient";
95
95
  export type { LocksAPI } from "./api/locksApi";
96
- export type { FunctionsAPI, FunctionInvokeOptions, FunctionInvokeResult, FunctionInvokeStatus, } from "./api/functionsApi";
96
+ export type { FunctionsAPI, FunctionInvokeOptions, FunctionInvokeResult, FunctionInvokeStatus, FunctionInvokeLimits, FunctionStartOptions, FunctionStartResult, FunctionRunRef, } from "./api/functionsApi";
97
97
  export type { LockHandle, LockContention, AcquireResponse, AcquireOptions, BlockingAcquireOptions, ReleaseResult, RenewResult, LockStatus, LockListEntry, LockListResult, } from "./api/locksApi";
98
98
  export type { ResourceMetadataAPI } from "./api/resourceMetadataApi";
99
99
  export type { ResourceMetadataReadResult, ResourceMetadataWriteResult, ResourceMetadataBatchRequestItem, ResourceMetadataBatchParams, ResourceMetadataBatchCategoryResult, ResourceMetadataBatchResourceResult, ResourceMetadataBatchResult, ResourceMetadataListEntry, ResourceMetadataListResult, ResourceMetadataDeleteResult, ResourceMetadataResolveParams, ResourceMetadataResolveResult, } from "./api/resourceMetadataApi";
@@ -865,64 +865,80 @@ export interface NotificationEvent {
865
865
  createdAt: string;
866
866
  }
867
867
  /**
868
- * Real-time notification that a document invitation has changed state.
868
+ * A message a server function sent to this user or this connection.
869
869
  *
870
- * Emitted over the `invitation` event channel (`client.on("invitation", ...)`).
870
+ * Emitted over the `directMessage` event channel
871
+ * (`client.on("directMessage", ...)`) when a function calls `ctx.users.send`
872
+ * or `ctx.connections.send` while this client is connected. `payload` is the
873
+ * function's own value, passed through unread by the platform.
871
874
  *
872
- * **Important:** events are targeted most actions are delivered to only one
873
- * side of the invitation (inviter _or_ invitee, not both). Consumers must
874
- * write a switch that handles every action value, or they will silently drop
875
- * events. See {@link InvitationEvent.action} for the full list and who
876
- * receives each.
875
+ * It is a LIVE frame with no durable record behind it: a client that was
876
+ * offline when the function ran does not receive it later. `functionKey` is
877
+ * the frame's only attribution a function running in system mode acts for
878
+ * nobody, so there is no user to name.
877
879
  */
878
- export interface InvitationEvent {
879
- type: "invitation";
880
- /**
881
- * The lifecycle transition that just occurred for this invitation.
882
- *
883
- * **Targeting (who receives each action — note the asymmetry):**
884
- *
885
- * - `"created"` — sent to the **invitee only**. A new invitation has been
886
- * sent to them; their UI should surface it.
887
- * - `"updated"` — sent to the **invitee only**. An existing pending
888
- * invitation was changed (e.g. permission re-issued /
889
- * expiry extended).
890
- * - `"cancelled"`sent to the **invitee only**. The inviter (or an admin)
891
- * cancelled the pending invitation; the invitee's UI
892
- * should drop it from their pending list.
893
- * - `"declined"` — sent to **both the invitee and the inviter**. The
894
- * invitee explicitly declined; both sides' UIs should
895
- * update (invitee removes from pending, inviter sees the
896
- * outcome).
897
- * - `"accepted"` — sent to the **inviter only**. The invitee accepted and
898
- * now holds a `DocumentPermission`; the inviter's UI can
899
- * update collaborator lists. `acceptedBy` carries the
900
- * accepting user's `userId`.
901
- *
902
- * New action values may be added in the future (non-breaking); consumer
903
- * switches should include a `default` branch that is either a no-op or a
904
- * warning rather than throwing.
905
- */
906
- action: "created" | "updated" | "cancelled" | "declined" | "accepted";
907
- invitationId: string;
908
- documentId: string;
909
- permission: string;
910
- title?: string;
911
- invitedBy?: string;
912
- invitedAt?: string;
913
- expiresAt?: string;
914
- /**
915
- * UserId of the invitee who accepted. Populated only when `action === "accepted"`.
916
- */
917
- acceptedBy?: string;
918
- document?: {
919
- documentId?: string;
920
- title?: string;
921
- tags?: string[];
922
- createdAt?: string;
923
- lastModified?: string;
924
- createdBy?: string;
925
- };
880
+ export interface DirectMessageEvent {
881
+ type: "direct.message";
882
+ payload: unknown;
883
+ functionKey: string;
884
+ /** When the platform sent it, ISO 8601. */
885
+ sentAt: string;
886
+ }
887
+ /**
888
+ * A message a server function published to a channel this client has joined.
889
+ *
890
+ * Emitted over the `channelMessage` event channel
891
+ * (`client.on("channelMessage", ...)`) for every channel this client holds a
892
+ * live membership in — the `channel` field says which. `payload` is the
893
+ * function's own value, passed through unread by the platform.
894
+ *
895
+ * Like {@link DirectMessageEvent} it is a LIVE frame with no durable record
896
+ * behind it, and `functionKey` is its only attribution: a function publishing
897
+ * from a database-change trigger runs as the app and acts for no user.
898
+ */
899
+ export interface ChannelMessageEvent {
900
+ type: "channel.message";
901
+ channel: string;
902
+ payload: unknown;
903
+ functionKey: string;
904
+ /** When the platform sent it, ISO 8601. */
905
+ sentAt: string;
906
+ }
907
+ /**
908
+ * A live channel membership, returned by
909
+ * {@link JsBaoClient.subscribeToChannel}.
910
+ *
911
+ * `expiresAt` is epoch milliseconds and is the membership's whole lifetime:
912
+ * expiry is the only revocation a channel grant has, so past it the server
913
+ * stops delivering even though this socket stays open. Renewing means asking
914
+ * the authorizing function for another grant and subscribing again — which
915
+ * replaces this membership rather than adding one.
916
+ */
917
+ export interface ChannelSubscription {
918
+ channel: string;
919
+ expiresAt: number;
920
+ /** Leave the channel. Idempotent. */
921
+ unsubscribe: () => void;
922
+ }
923
+ /**
924
+ * A channel subscription the server refused when nothing was waiting on it.
925
+ *
926
+ * Emitted over the `channelSubscribeFailed` event channel
927
+ * (`client.on("channelSubscribeFailed", ...)`). The case this exists for is
928
+ * RECONNECT: after the socket comes back the client presents each held grant
929
+ * again, and a grant that expired while the connection was down is refused with
930
+ * no pending call to reject. The registration for that channel — and only that
931
+ * channel — is dropped, and this is how an app hears about it, so it can ask
932
+ * its authorizing function for a fresh grant and subscribe again.
933
+ *
934
+ * A refusal that answers a {@link JsBaoClient.subscribeToChannel} call is
935
+ * reported by rejecting that promise instead, so a failure is never announced
936
+ * twice. `message` is the server's uniform refusal: an expired, tampered,
937
+ * cross-app or cross-user grant are deliberately indistinguishable.
938
+ */
939
+ export interface ChannelSubscribeFailedEvent {
940
+ channel: string;
941
+ message: string;
926
942
  }
927
943
  /**
928
944
  * Payload of the `workflowStatus` event (`client.on("workflowStatus", ...)`).
@@ -1600,8 +1616,10 @@ export interface JsBaoEvents {
1600
1616
  pendingCreateFailed: PendingCreateFailedEvent;
1601
1617
  meUpdated: MeUpdatedEvent;
1602
1618
  meUpdateFailed: MeUpdateFailedEvent;
1603
- invitation: InvitationEvent;
1604
1619
  notification: NotificationEvent;
1620
+ directMessage: DirectMessageEvent;
1621
+ channelMessage: ChannelMessageEvent;
1622
+ channelSubscribeFailed: ChannelSubscribeFailedEvent;
1605
1623
  workflowStatus: WorkflowStatusEvent;
1606
1624
  workflowStarted: WorkflowStartedEvent;
1607
1625
  syncPerf: {
@@ -1959,6 +1977,50 @@ export declare class JsBaoClient extends Observable<any> {
1959
1977
  /** Registry of active database subscriptions — routes inbound `db.change`
1960
1978
  * frames to the right callback and drives reconnect re-subscribe. */
1961
1979
  private dbSubscriptions;
1980
+ /**
1981
+ * Channel memberships this client is holding, `channel → grant` (#3184).
1982
+ *
1983
+ * The GRANT is stored, not just the channel, because a membership is keyed
1984
+ * by a connection: after a reconnect the server has no record of it, and the
1985
+ * only thing that re-establishes one is presenting the credential again. A
1986
+ * grant that has expired in the meantime is refused, and the refusal drops
1987
+ * exactly that channel's registration.
1988
+ */
1989
+ private channelGrants;
1990
+ /**
1991
+ * Subscribes waiting for their own channel's ack (D3184-007).
1992
+ *
1993
+ * `sent` records whether the frame this call is waiting on ever reached the
1994
+ * wire — R3184-002. A subscribe made before the socket opened registers its
1995
+ * grant and nudges the connection, but sends nothing; the open handler has
1996
+ * to send THAT attempt rather than queue a second one behind it, or the
1997
+ * caller waits out the full 20 s timeout on a connection that came up
1998
+ * immediately.
1999
+ */
2000
+ private pendingChannelSubscribes;
2001
+ /**
2002
+ * How many times this channel has been left — R3184-005.
2003
+ *
2004
+ * A subscribe queued behind an in-flight one is a continuation that has not
2005
+ * run yet, and `unsubscribeFromChannel` cannot cancel a `.then`. Without a
2006
+ * generation, leaving a channel while a renewal was queued would run that
2007
+ * renewal afterwards: the registration would come back, a subscribe would go
2008
+ * out, and the client would be delivered to on a channel it had explicitly
2009
+ * left. Each attempt captures the count it was created under and does
2010
+ * nothing if it has moved.
2011
+ */
2012
+ private channelEpochs;
2013
+ /**
2014
+ * One subscribe at a time per channel — CR3184-004.
2015
+ *
2016
+ * The server answers a subscribe with a frame that names the CHANNEL and
2017
+ * nothing finer, which is what lets subscriptions to DIFFERENT channels be
2018
+ * told apart. Two requests for the SAME channel cannot be: the first answer
2019
+ * would settle both, so an expired grant racing a renewal would reject the
2020
+ * renewal too and drop a registration the server had accepted. Chaining them
2021
+ * keeps every answer attributable to the request that asked for it.
2022
+ */
2023
+ private channelSubscribeChain;
1962
2024
  /** Sub-API for managing documents (list, create, get, delete, share).
1963
2025
  * @group Sub-APIs */
1964
2026
  documents: DocumentsAPI;
@@ -2900,8 +2962,8 @@ export declare class JsBaoClient extends Observable<any> {
2900
2962
  private handlePendingCreateCommitted;
2901
2963
  private handlePendingCreateFailed;
2902
2964
  private handleDocumentMetadataUpdate;
2903
- private handleInvitationMessage;
2904
2965
  private handleNotificationMessage;
2966
+ private handleDirectMessage;
2905
2967
  private handleWorkflowStatusMessage;
2906
2968
  private handleWorkflowStartedMessage;
2907
2969
  private handleOfflineAuthMessage;
@@ -3112,6 +3174,94 @@ export declare class JsBaoClient extends Observable<any> {
3112
3174
  /** Check if an offline grant is stored locally.
3113
3175
  * @group Offline & Sync */
3114
3176
  hasOfflineGrantStored(): Promise<boolean>;
3177
+ /**
3178
+ * Join a channel a server function authorized.
3179
+ *
3180
+ * `grant` is the token `ctx.channels.authorize` handed back: a signed,
3181
+ * short-lived credential naming this app, this channel and this user. The
3182
+ * promise resolves on the server's ack FOR THIS CHANNEL, so concurrent
3183
+ * subscribes cannot resolve each other, and rejects on the server's uniform
3184
+ * refusal — an expired, tampered, cross-app or cross-user grant are
3185
+ * deliberately indistinguishable, so the rejection says only that the grant
3186
+ * was not accepted.
3187
+ *
3188
+ * Calling it again with a fresh grant RENEWS the membership: expiry is the
3189
+ * only revocation a channel has, so an app that wants a long-lived channel
3190
+ * re-invokes its authorizing function before `expiresAt` and subscribes
3191
+ * again. Frames arrive as `client.on("channelMessage", …)`.
3192
+ *
3193
+ * Two calls for the SAME channel run one after the other, so a renewal
3194
+ * issued while an earlier subscribe is still in flight is answered on its own
3195
+ * merits rather than by whichever frame arrives first.
3196
+ *
3197
+ * @group Realtime
3198
+ */
3199
+ subscribeToChannel(channel: string, grant: string): Promise<ChannelSubscription>;
3200
+ /** The generation `unsubscribeFromChannel` bumps — R3184-005. */
3201
+ private channelEpoch;
3202
+ /**
3203
+ * Re-present a stored grant after a reconnect — through the SAME chain an
3204
+ * explicit subscribe uses (SO3184-006).
3205
+ *
3206
+ * The reconnect pass used to write straight to the socket, which put an
3207
+ * attempt on the wire that the per-channel bookkeeping knew nothing about.
3208
+ * The server's answer names the channel and nothing finer, so a refusal of
3209
+ * the re-issued (possibly expired) grant would settle a RENEWAL the app made
3210
+ * in the meantime: the fresh subscribe would reject and its registration
3211
+ * would be deleted, while the server had accepted it and was delivering to
3212
+ * it. Chaining keeps every answer attributable to the request that asked for
3213
+ * it, exactly as `subscribeToChannel` does.
3214
+ *
3215
+ * Nobody is waiting on this one, so its refusal is announced as
3216
+ * `channelSubscribeFailed` (CR3184-005) rather than rejecting a promise.
3217
+ */
3218
+ private reissueChannelSubscribe;
3219
+ /**
3220
+ * Give up on every subscribe waiting for an answer, because the socket that
3221
+ * would have carried it is gone (SO3184-006).
3222
+ *
3223
+ * The GRANTS stay: the socket failed, not the credential, and the reconnect
3224
+ * pass presents each one again. What must not stay is a pending attempt —
3225
+ * its answer can never arrive, and leaving it in the per-channel chain would
3226
+ * hold the re-issue behind it for the full 20 s timeout.
3227
+ */
3228
+ private abortPendingChannelSubscribes;
3229
+ private startChannelSubscribe;
3230
+ /**
3231
+ * Put the subscribes that never reached the wire onto it — R3184-002.
3232
+ *
3233
+ * A call made while the socket was down registered its grant, created its
3234
+ * promise and sent nothing. On open its frame is sent for the attempt that
3235
+ * is already waiting, so the ack settles the original call. Channels with
3236
+ * nothing pending go the other way, through the ordinary re-issue.
3237
+ *
3238
+ * Returns the channels it sent for, so the reconnect pass does not queue a
3239
+ * second attempt for them.
3240
+ */
3241
+ private flushUnsentChannelSubscribes;
3242
+ /**
3243
+ * Leave a channel. Idempotent, and safe on a closed socket: the
3244
+ * registration goes either way, so a reconnect does not bring it back.
3245
+ *
3246
+ * A subscribe still in flight, or queued behind one, is cancelled with it:
3247
+ * leaving means leaving, so a queued renewal cannot put the membership back
3248
+ * afterwards, and a `subscribeToChannel` promise still waiting for its ack
3249
+ * rejects rather than resolving with a subscription to a channel this client
3250
+ * has already left.
3251
+ *
3252
+ * @group Realtime
3253
+ */
3254
+ unsubscribeFromChannel(channel: string): void;
3255
+ private sendChannelSubscribe;
3256
+ /**
3257
+ * Settle every call waiting on one channel — with the ack, or with the
3258
+ * refusal. Only that channel's waiters and only that channel's
3259
+ * registration: a client with several subscriptions must not lose the ones
3260
+ * that worked (D3184-007).
3261
+ */
3262
+ private settleChannelSubscribe;
3263
+ private handleChannelSubscribed;
3264
+ private handleChannelMessage;
3115
3265
  /**
3116
3266
  * Internal entry point for DatabasesAPI.subscribe(). Ensures the WS is
3117
3267
  * connected, registers the callback with the registry, sends the
@@ -3714,9 +3864,11 @@ export declare class JsBaoClient extends Observable<any> {
3714
3864
  ttlMs?: number;
3715
3865
  preserveOnSignOut?: boolean;
3716
3866
  }): void;
3717
- /** Create a new document. Writable locally immediately, with the server
3867
+ /** Create a new document. Writes local metadata only, with the server
3718
3868
  * commit racing in the background — unless `options.localOnly` is set, in
3719
- * which case the document never syncs.
3869
+ * which case the document never syncs. The document is NOT opened: call
3870
+ * `documents.open(documentId)` before reading or writing it, or the write
3871
+ * throws "Document `<id>` is not open".
3720
3872
  * @param options - Document creation options
3721
3873
  * @group Documents */
3722
3874
  createDocument(options: CreateDocumentOptions): Promise<{