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 +42 -105
- package/dist/JsBaoClient.d.ts +213 -61
- package/dist/JsBaoClient.js +568 -102
- package/dist/api/documentsApi.d.ts +8 -211
- package/dist/api/documentsApi.js +15 -279
- package/dist/api/functionsApi.d.ts +125 -1
- package/dist/api/functionsApi.js +181 -1
- package/dist/api/meApi.d.ts +21 -53
- package/dist/api/meApi.js +6 -12
- package/dist/browser.umd.js +769 -389
- package/dist/errors.d.ts +1 -1
- package/package.json +2 -2
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 `
|
|
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 `
|
|
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.
|
|
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 `
|
|
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 `
|
|
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.
|
|
907
|
+
const docs = await client.me.ownedDocuments();
|
|
908
908
|
|
|
909
909
|
// Include root document(s)
|
|
910
|
-
const all = await client.
|
|
910
|
+
const all = await client.me.ownedDocuments({ includeRoot: true });
|
|
911
911
|
```
|
|
912
912
|
|
|
913
913
|
## Behavior changes
|
|
914
914
|
|
|
915
|
-
- **Root documents listing**: `
|
|
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
|
|
1164
|
-
|
|
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
|
|
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
|
-
|
|
1695
|
-
|
|
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
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
"
|
|
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
|
-
//
|
|
1705
|
-
const
|
|
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
|
-
//
|
|
1711
|
-
await client.documents.
|
|
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
|
-
//
|
|
1714
|
-
|
|
1715
|
-
await client.
|
|
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
|
|
3489
|
-
documentId,
|
|
3426
|
+
const grant = await this.client.documents.updatePermissions(documentId, {
|
|
3490
3427
|
email,
|
|
3491
|
-
permission
|
|
3492
|
-
);
|
|
3493
|
-
console.log("
|
|
3494
|
-
return
|
|
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;
|
package/dist/JsBaoClient.d.ts
CHANGED
|
@@ -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,
|
|
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
|
-
*
|
|
868
|
+
* A message a server function sent to this user or this connection.
|
|
869
869
|
*
|
|
870
|
-
* Emitted over the `
|
|
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
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
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
|
|
879
|
-
type: "
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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.
|
|
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<{
|