@recursiv/sdk 0.7.52 → 0.7.54

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
@@ -93,7 +93,8 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
93
93
  ### Social
94
94
  | Resource | Key methods |
95
95
  |----------|------------|
96
- | `r.posts` | `create`, `update`, `list`, `liked`, `search`, `replies`, `react` |
96
+ | `r.posts` | `create`, `update`, `list`, `liked`, `bookmarked`, `bookmark`, `unbookmark`, `search`, `replies`, `react` |
97
+ | `r.bookmarks` | `save`, `unsave`, `posts`, `list`, `keys`, `merge` — the member's saved posts, open-network posts and articles, synced across devices |
97
98
  | `r.communities` | `create`, `join`, `search`, `members` |
98
99
  | `r.chat` | `send`, `dm`, `createGroup`, `messages` (cursor paging), `acceptRequest`, `declineRequest`, `leave`, `deleteConversation` |
99
100
  | `r.tags` | `create`, `list` |
@@ -114,7 +115,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
114
115
  | `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
115
116
  | `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
116
117
  | `r.goals` | Org goals with task rollups (group dispatcher tasks) |
117
- | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
118
+ | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts; one that has left the radar comes with its archived copy and the live story it continues as), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
118
119
 
119
120
  Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
120
121
 
@@ -648,7 +649,12 @@ per signed-in viewer and filter. Pass the app-advertised network catalog, then c
648
649
  `next()` as the reader approaches the end. Each page contains safe json-render
649
650
  module specs and native or protocol post objects. Preserve their existing source
650
651
  authority and interactive controls. `retryFailed()` explicitly retries unavailable
651
- sources; create a new session to refresh exhausted source windows.
652
+ sources; create a new session to refresh exhausted source windows. A topics network
653
+ (Bluesky) is read one topic page at a time; in a category the catalog's previews are not
654
+ judged to be in it, so a read walks into the topics at once, and walks past a topic the
655
+ provider no longer lists (`trend_unavailable`) or one with nothing in the category, up
656
+ to five topic pages a read, so a page reaches a topic that carries posts. Any other
657
+ failure marks the source failed until `retryFailed()`.
652
658
 
653
659
  `sdk.discovery.createPersonalFeed({ networks, category?, learning?, recommend? })`
654
660
  builds the same kind of session for a member's For you tab: its Minds slot reads
@@ -658,6 +664,46 @@ and says whether the member is personalized yet (`meta.personalized`). With a
658
664
  `category` (the member's first), the session also reads open-network posts from it;
659
665
  without, it is Minds only. It never carries the radar.
660
666
 
667
+ `sdk.discovery.recommendations({ kind, category?, limit?, session?, exclude? })` is
668
+ the app's one recommender for suggestion rails: `kind` is `member`, `group`, `tag`,
669
+ `network_account` or `story`, and `category` is `all` or a browse category id. It
670
+ suggests only active or warm entities that pass the server's gates (no link feeds,
671
+ bursts, mirrored `name@host` feeds, single-poster groups or unsafe names), leaves out
672
+ what the viewer follows, joined, blocked or muted, and says why a list is empty in
673
+ `meta.reason`. Call `sdk.discovery.recordImpressions({ kind, keys, event })` with
674
+ `shown` when suggestions are on screen and `engaged` on a tap, follow or join: three
675
+ showings without a tap rest a suggestion for fourteen days, and engaging clears it.
676
+ Keep one `session` token per app session so a rail does not reshuffle on every render.
677
+
678
+ A category feed never ends at an empty page. `sdk.posts.list({ category, sort: 'hot' })`
679
+ (or `score`, or the default chronological order) pages the window of the category's
680
+ newest judged posts, then continues by keyset through the whole indexed
681
+ history: every page's `meta.next` is `{ offset }`, `{ cursor }` or `null`, and when a
682
+ stage ends `meta.fallover` lists where to go, in order: `category_older` (the keyset
683
+ cursor `next` also carries), `category_network` (the same category on the open
684
+ networks), the member's next categories (`via` names the order they came from), `for_you`
685
+ and `all`. A `DiscoveryFeedSession` follows the cursor itself and reports the Minds
686
+ feed's `fallover` on its pages. `sdk.discovery.category({ category, session? })` answers a
687
+ category's surface in one call: `stories`, `network` and `clips` (each shared per app and
688
+ category, refreshed after 30 s, kept up to 300 s and warmed as the warmer reaches the category, then filtered for the viewer on every call: the cached safety verdicts of story members, network posts and clips again, the term list, the viewer's blocks and mutes over clips, and the member's own hidden sources over network posts and story members) and the
689
+ viewer's `people` and `groups`; a section that timed out, failed or is off is empty with a
690
+ `reason` for that request only (a failure is never cached, so asking again retries it), and one this
691
+ key may not read is empty with `refused` (`stories` and `network` need the member's own app
692
+ key with `users:read`; `people` and `groups` need `users:read`). In a category, a Bluesky
693
+ topic the provider no longer lists, or whose feed holds nothing in the category, is served
694
+ the network's indexed posts in it (`sourceIndex.reason` of `gone` or `empty`). While the
695
+ provider has more of the topic the page's cursor is the provider's own, so the topic's
696
+ deeper pages are still read; once it has none, or the topic is gone, the cursor is an
697
+ `idx:tf:` one that continues in the index. When the index cannot answer (busy, or its safety
698
+ cache unreadable), the page is what the provider answered: the empty page with its own
699
+ cursor, or the gone topic's 404; only an `idx:tf:` page answers 503 `unavailable`, and that
700
+ ends the index continuation, not the network. The app contract: a `trend_unavailable` 404,
701
+ an empty topic page or a failed `idx:tf:` page never disables a network for the session;
702
+ move to the next topic, keep every provider continuation, start the `idx:tf:` stream once
703
+ per network and session (every such topic answers the same index stream; after a failed
704
+ page it may start again), and pass cursors back unchanged. `DiscoveryFeedSession` does
705
+ this for you.
706
+
661
707
  `sdk.posts.categories()` answers the app's categories in the order this member
662
708
  should see them. `meta.order` is `personal`, `popular` or `static`; `meta.reason`
663
709
  says why an answer is not personal (`low_signal`, `deadline`, `requested`,
package/dist/client.d.ts CHANGED
@@ -36,8 +36,12 @@ export declare class HttpClient {
36
36
  post<T>(path: string, body?: unknown, options?: {
37
37
  retry?: boolean;
38
38
  }): Promise<T>;
39
- put<T>(path: string, body?: unknown): Promise<T>;
39
+ put<T>(path: string, body?: unknown, options?: {
40
+ retry?: boolean;
41
+ }): Promise<T>;
40
42
  patch<T>(path: string, body?: unknown): Promise<T>;
41
- delete<T>(path: string): Promise<T>;
43
+ delete<T>(path: string, options?: {
44
+ retry?: boolean;
45
+ }): Promise<T>;
42
46
  fetchRaw(path: string, options: RequestInit): Promise<Response>;
43
47
  }
package/dist/client.js CHANGED
@@ -188,16 +188,17 @@ export class HttpClient {
188
188
  const response = await (options?.retry === false ? this.fetchWithTimeout(url, request) : this.fetchWithRetry(url, request));
189
189
  return this.handleResponse(response);
190
190
  }
191
- async put(path, body) {
191
+ async put(path, body, options) {
192
192
  const url = this.buildUrl(path);
193
- const response = await this.fetchWithRetry(url, {
193
+ const request = {
194
194
  method: 'PUT',
195
195
  headers: {
196
196
  ...this.getAuthHeaders(),
197
197
  'Content-Type': 'application/json',
198
198
  },
199
199
  body: body ? JSON.stringify(body) : undefined,
200
- });
200
+ };
201
+ const response = await (options?.retry === false ? this.fetchWithTimeout(url, request) : this.fetchWithRetry(url, request));
201
202
  return this.handleResponse(response);
202
203
  }
203
204
  async patch(path, body) {
@@ -212,12 +213,10 @@ export class HttpClient {
212
213
  });
213
214
  return this.handleResponse(response);
214
215
  }
215
- async delete(path) {
216
+ async delete(path, options) {
216
217
  const url = this.buildUrl(path);
217
- const response = await this.fetchWithRetry(url, {
218
- method: 'DELETE',
219
- headers: this.getAuthHeaders(),
220
- });
218
+ const request = { method: 'DELETE', headers: this.getAuthHeaders() };
219
+ const response = await (options?.retry === false ? this.fetchWithTimeout(url, request) : this.fetchWithRetry(url, request));
221
220
  return this.handleResponse(response);
222
221
  }
223
222
  async fetchRaw(path, options) {
package/dist/index.d.ts CHANGED
@@ -59,6 +59,8 @@ import { CuratorResource } from './resources/curator.js';
59
59
  import { ReportsResource } from './resources/reports.js';
60
60
  import { ModerationResource } from './resources/moderation.js';
61
61
  import { LinkPreviewResource } from './resources/link-preview.js';
62
+ import { SearchResource } from './resources/search.js';
63
+ import { BookmarksResource } from './resources/bookmarks.js';
62
64
  import { AccountsResource } from './resources/accounts.js';
63
65
  import { RealtimeClient } from './realtime.js';
64
66
  import type { RecursivConfig } from './types.js';
@@ -124,6 +126,10 @@ export declare class Recursiv {
124
126
  readonly reports: ReportsResource;
125
127
  readonly moderation: ModerationResource;
126
128
  readonly linkPreview: LinkPreviewResource;
129
+ /** Blended search: posts, open network, people, groups, topics, stories, related and trending in one answer. */
130
+ readonly search: SearchResource;
131
+ /** Synced bookmarks: the member's saved posts, open-network posts and articles in this app, across devices. */
132
+ readonly bookmarks: BookmarksResource;
127
133
  readonly accounts: AccountsResource;
128
134
  readonly realtime: RealtimeClient;
129
135
  constructor(config?: Partial<RecursivConfig>);
@@ -141,7 +147,7 @@ export { HttpClient } from './client.js';
141
147
  export { RealtimeClient } from './realtime.js';
142
148
  export type { RealtimeConnectOptions } from './realtime.js';
143
149
  export { AuthResource } from './resources/auth.js';
144
- export type { AuthSession, SignUpInput, SignInInput, UsernameSignInInput, AppContext, ResetPasswordInput, TwoFactorConfirmation, TwoFactorEnrollment, TwoFactorChallenge, CompleteTwoFactorSignInInput, OAuthClientInfo, SendOtpInput, VerifyOtpInput, CreateApiKeyInput, ApiKeyResult, } from './resources/auth.js';
150
+ export type { AuthSession, CurrentSession, SignUpInput, SignInInput, UsernameSignInInput, AppContext, ResetPasswordInput, TwoFactorConfirmation, TwoFactorEnrollment, TwoFactorChallenge, CompleteTwoFactorSignInInput, OAuthClientInfo, SendOtpInput, VerifyOtpInput, CreateApiKeyInput, ApiKeyResult, } from './resources/auth.js';
145
151
  export { PostsResource } from './resources/posts.js';
146
152
  export type { CategoryOrder, CategoryOrderMeta, CategoryOrderReason } from './resources/posts.js';
147
153
  export { UsersResource } from './resources/users.js';
@@ -190,6 +196,10 @@ export type { ReportTargetType, ReportStatus, CreateReportInput, ReportRecord, R
190
196
  export { ModerationResource } from './resources/moderation.js';
191
197
  export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, } from './resources/moderation.js';
192
198
  export { LinkPreviewResource } from './resources/link-preview.js';
199
+ export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
200
+ export type { SearchAnswer, SearchGroup, SearchOmitReason, SearchParams, SearchPerson, SearchRelated, SearchResponse, SearchSection, SearchStory, SearchTopic } from './resources/search.js';
201
+ export { BOOKMARK_MERGE_MAX_ITEMS, BookmarksResource } from './resources/bookmarks.js';
202
+ export type { BookmarkedPost, BookmarkInput, BookmarkItem, BookmarkKey, BookmarkKeyRow, BookmarkKind, BookmarkMergeItem, BookmarkMergeResult, BookmarkProtocol, BookmarkRequestOptions, BookmarksPage, SavedBookmark, UnsavedBookmark, } from './resources/bookmarks.js';
193
203
  export { AccountsResource } from './resources/accounts.js';
194
204
  export type { SiblingAccount, SiblingAccountsResponse, SwitchAccountResult, } from './resources/accounts.js';
195
205
  export type { LinkPreview, LinkPreviewResponse, } from './resources/link-preview.js';
@@ -205,7 +215,7 @@ export { BoostsResource } from './resources/boosts.js';
205
215
  export type { BoostBalance, BoostBalanceResponse, BoostCampaign, BoostCampaignResponse, BoostEventResult, BoostLedgerEntry, BoostLedgerKind, BoostPlanCredit, BoostRejectionReason, BoostReviewStatus, BoostStatus, BoostTopupCheckout, BoostTopupConfirmation, BoostTopupLimits, } from './resources/boosts.js';
206
216
  export { ProtocolsResource } from './resources/protocols.js';
207
217
  export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, } from './resources/protocols.js';
208
- export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialRadar, SocialRadarCluster, SocialRadarMember, SocialRadarParams, SocialRadarPosts, SocialRadarStory, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendSourceIndex, SocialTrendFeed, SocialPopularProfiles, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, SearchPublicPostsParams, SocialPostSearch, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, NativeSocialProtocol, FederationIdentity, FederationIdentityView, NativeSocialAccount, NativeSocialCallbackInput, NativeReplyPrepareInput, NativePostPrepareInput, NativeMediaInput, NativeMediaItem, NativePostBatch, NativePostHistory, NativeDelegatedPostHistoryItem, NativeNostrPostHistoryItem, NativeFollowSourceInput, NativeFollowPrepareInput, NativeFollowTarget, NativeFollowReceipt, NativeFollowState, NativeFollowAccount, NativeFollowStates, NativeFollowReview, NativeLikeSourceInput, NativeRepostSourceInput, NativeLikePrepareInput, NativeRepostPrepareInput, NativeLikeTarget, NativeRepostTarget, NativeLikeReceipt, NativeRepostReceipt, NativeLikeState, NativeRepostState, NativeLikeReview, NativeRepostReview, NativeReplyReceipt, NativeReplyBatch, NativeReplyHistory, NativeDelegatedReplyHistoryItem, NativeNostrReplyResult, NativeNostrReplyHistoryItem, NativeReplyRemovalReceipt, NativeReplyRemovalPreview, SocialSourceRef, SocialSourceControl, SocialSourceControlDisplay, SocialSourceControlChange, HostedPostPreview, ConnectHostedIdentityInput, CreateHostedIdentityInput, CreatedHostedProtocolIdentity, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolIngestionReplayResult, ProtocolIngestionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, ReplayProtocolIngestionJobInput, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
218
+ export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialRadar, SocialRadarCluster, SocialRadarMember, SocialRadarParams, SocialRadarPosts, SocialRadarStory, SocialRadarStoryArchive, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendSourceIndex, SocialTrendFeed, SocialPopularProfiles, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, SearchPublicPostsParams, SocialPostSearch, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, NativeSocialProtocol, FederationIdentity, FederationIdentityView, NativeSocialAccount, NativeSocialCallbackInput, NativeReplyPrepareInput, NativePostPrepareInput, NativeMediaInput, NativeMediaItem, NativePostBatch, NativePostHistory, NativeDelegatedPostHistoryItem, NativeNostrPostHistoryItem, NativeFollowSourceInput, NativeFollowPrepareInput, NativeFollowTarget, NativeFollowReceipt, NativeFollowState, NativeFollowAccount, NativeFollowStates, NativeFollowReview, NativeLikeSourceInput, NativeRepostSourceInput, NativeLikePrepareInput, NativeRepostPrepareInput, NativeLikeTarget, NativeRepostTarget, NativeLikeReceipt, NativeRepostReceipt, NativeLikeState, NativeRepostState, NativeLikeReview, NativeRepostReview, NativeReplyReceipt, NativeReplyBatch, NativeReplyHistory, NativeDelegatedReplyHistoryItem, NativeNostrReplyResult, NativeNostrReplyHistoryItem, NativeReplyRemovalReceipt, NativeReplyRemovalPreview, SocialSourceRef, SocialSourceControl, SocialSourceControlDisplay, SocialSourceControlChange, HostedPostPreview, ConnectHostedIdentityInput, CreateHostedIdentityInput, CreatedHostedProtocolIdentity, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolIngestionReplayResult, ProtocolIngestionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, ReplayProtocolIngestionJobInput, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
209
219
  export { InboxResource } from './resources/inbox.js';
210
220
  export { NetworkResource } from './resources/network.js';
211
221
  export { SimulatorResource } from './resources/simulator.js';
package/dist/index.js CHANGED
@@ -60,6 +60,8 @@ import { CuratorResource } from './resources/curator.js';
60
60
  import { ReportsResource } from './resources/reports.js';
61
61
  import { ModerationResource } from './resources/moderation.js';
62
62
  import { LinkPreviewResource } from './resources/link-preview.js';
63
+ import { SearchResource } from './resources/search.js';
64
+ import { BookmarksResource } from './resources/bookmarks.js';
63
65
  import { AccountsResource } from './resources/accounts.js';
64
66
  import { RealtimeClient } from './realtime.js';
65
67
  export class Recursiv {
@@ -125,6 +127,10 @@ export class Recursiv {
125
127
  reports;
126
128
  moderation;
127
129
  linkPreview;
130
+ /** Blended search: posts, open network, people, groups, topics, stories, related and trending in one answer. */
131
+ search;
132
+ /** Synced bookmarks: the member's saved posts, open-network posts and articles in this app, across devices. */
133
+ bookmarks;
128
134
  accounts;
129
135
  realtime;
130
136
  constructor(config) {
@@ -181,13 +187,15 @@ export class Recursiv {
181
187
  this.webhooks = new WebhooksResource(client);
182
188
  this.jobs = new JobsResource(client);
183
189
  this.curator = new CuratorResource(client);
184
- this.discovery = new DiscoveryResource(this.posts, this.protocols, this.curator);
190
+ this.discovery = new DiscoveryResource(this.posts, this.protocols, this.curator, client);
185
191
  this.video = new VideoResource(client);
186
192
  this.appSubscriptions = new AppSubscriptionsResource(client);
187
193
  this.boosts = new BoostsResource(client);
188
194
  this.reports = new ReportsResource(client);
189
195
  this.moderation = new ModerationResource(client);
190
196
  this.linkPreview = new LinkPreviewResource(client);
197
+ this.search = new SearchResource(client);
198
+ this.bookmarks = new BookmarksResource(client);
191
199
  this.accounts = new AccountsResource(client);
192
200
  this.realtime = new RealtimeClient(client);
193
201
  this.#http = client;
@@ -243,6 +251,8 @@ export { AppSubscriptionsResource } from './resources/app-subscriptions.js';
243
251
  export { ReportsResource } from './resources/reports.js';
244
252
  export { ModerationResource } from './resources/moderation.js';
245
253
  export { LinkPreviewResource } from './resources/link-preview.js';
254
+ export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
255
+ export { BOOKMARK_MERGE_MAX_ITEMS, BookmarksResource } from './resources/bookmarks.js';
246
256
  export { AccountsResource } from './resources/accounts.js';
247
257
  export { OrganizationSettingsResource } from './resources/organization-settings.js';
248
258
  export { ProjectSettingsResource } from './resources/project-settings.js';
@@ -8,6 +8,20 @@ export interface AuthSession {
8
8
  image: string | null;
9
9
  };
10
10
  }
11
+ /**
12
+ * What `getSession` answers: the account, and which of its sessions the token
13
+ * is. `session.id` is the id `settings.listSessions()` lists that session
14
+ * under. A client that authenticates with an API key gets `is_current: false`
15
+ * on every listed row (a key belongs to no session), so it matches this id to
16
+ * mark its own device.
17
+ */
18
+ export interface CurrentSession extends AuthSession {
19
+ /** Null only when the server's answer names no session row. */
20
+ session: {
21
+ id: string;
22
+ expiresAt: string | null;
23
+ } | null;
24
+ }
11
25
  export interface SignUpInput {
12
26
  email: string;
13
27
  password: string;
@@ -275,10 +289,10 @@ export declare class AuthResource {
275
289
  success: true;
276
290
  }>;
277
291
  /**
278
- * Get current session info using a session token.
279
- * Returns null if the session is invalid or expired.
292
+ * Get current session info using a session token: the account, and the
293
+ * session's id and expiry. Returns null if the session is invalid or expired.
280
294
  */
281
- getSession(sessionToken: string): Promise<AuthSession | null>;
295
+ getSession(sessionToken: string): Promise<CurrentSession | null>;
282
296
  /**
283
297
  * Sign out (invalidate session).
284
298
  */
@@ -596,8 +596,8 @@ export class AuthResource {
596
596
  return { success: true };
597
597
  }
598
598
  /**
599
- * Get current session info using a session token.
600
- * Returns null if the session is invalid or expired.
599
+ * Get current session info using a session token: the account, and the
600
+ * session's id and expiry. Returns null if the session is invalid or expired.
601
601
  */
602
602
  async getSession(sessionToken) {
603
603
  const res = await this.fetchWithTimeout(`${this.baseOrigin}/api/auth/get-session`, {
@@ -612,6 +612,8 @@ export class AuthResource {
612
612
  const data = await res.json();
613
613
  if (!data?.user)
614
614
  return null;
615
+ const sessionId = data.session?.id;
616
+ const expiresAt = data.session?.expiresAt;
615
617
  return {
616
618
  token: sessionToken,
617
619
  user: {
@@ -620,6 +622,9 @@ export class AuthResource {
620
622
  email: data.user.email,
621
623
  image: data.user.image ?? null,
622
624
  },
625
+ session: typeof sessionId === 'string' && sessionId
626
+ ? { id: sessionId, expiresAt: typeof expiresAt === 'string' ? expiresAt : null }
627
+ : null,
623
628
  };
624
629
  }
625
630
  /**
@@ -0,0 +1,172 @@
1
+ import type { HttpClient } from '../client.js';
2
+ import type { PaginatedResponse, PaginationMeta, Post, SingleResponse } from '../types.js';
3
+ /**
4
+ * What a bookmark is stored under: `post:<post id>`, `social:<sha256>` (the hash the member's source
5
+ * controls use for that open-network post) or `article:<sha256 of the url>`. Every save, list and keys
6
+ * call returns it; pass it back to `unsave`.
7
+ */
8
+ export type BookmarkKey = string;
9
+ export type BookmarkKind = 'post' | 'social' | 'article';
10
+ export type BookmarkProtocol = 'bluesky' | 'activitypub' | 'nostr';
11
+ /** Something to save: a Minds post, an open-network post (with the snapshot to show it by) or an article. */
12
+ export type BookmarkInput = {
13
+ kind: 'post';
14
+ post_id: string;
15
+ } | {
16
+ kind: 'social';
17
+ protocol: BookmarkProtocol;
18
+ /** The network's immutable post id: an at:// URI, an ActivityPub object URL, or a Nostr event id. */
19
+ native_id: string;
20
+ /** ActivityPub only: the Mastodon-compatible status id that reopens the conversation. Dropped elsewhere. */
21
+ read_ref?: string;
22
+ author: {
23
+ display_name: string;
24
+ handle: string;
25
+ /** The author's immutable id (DID, actor URL, pubkey), so the member's blocks and mutes apply. Derived on Bluesky. */
26
+ native_id?: string;
27
+ };
28
+ /** At most 500 characters. */
29
+ snippet: string;
30
+ } | {
31
+ kind: 'article';
32
+ /** An http(s) URL without credentials, at most 2048 characters. */
33
+ url: string;
34
+ title: string;
35
+ outlet: string;
36
+ /** A World Today story or edition id. */
37
+ story?: string;
38
+ /** An https image URL. */
39
+ image?: string;
40
+ };
41
+ /** A device's saved item for `merge`: an input plus when the device saved it (held to the last five years, never the future). */
42
+ export type BookmarkMergeItem = BookmarkInput & {
43
+ saved_at?: string;
44
+ };
45
+ export interface SavedBookmark {
46
+ key: BookmarkKey;
47
+ kind: BookmarkKind;
48
+ saved: true;
49
+ /** False when it was already saved: nothing changed, not even `saved_at`. */
50
+ created: boolean;
51
+ saved_at: string | null;
52
+ /** Posts only: the post stored. A bare repost is saved as its original, so use this id for its saved state. */
53
+ post_id?: string;
54
+ }
55
+ export interface UnsavedBookmark {
56
+ key: BookmarkKey;
57
+ saved: false;
58
+ /** False when it was not saved; unsaving is idempotent. */
59
+ removed: boolean;
60
+ }
61
+ /** A saved open-network post or article, as the snapshot taken when it was saved. */
62
+ export type BookmarkItem = {
63
+ key: BookmarkKey;
64
+ kind: 'social';
65
+ saved_at: string;
66
+ social: {
67
+ protocol: BookmarkProtocol;
68
+ native_id: string;
69
+ read_ref: string | null;
70
+ author: {
71
+ display_name: string;
72
+ handle: string;
73
+ native_id: string | null;
74
+ };
75
+ snippet: string;
76
+ };
77
+ } | {
78
+ key: BookmarkKey;
79
+ kind: 'article';
80
+ saved_at: string;
81
+ article: {
82
+ url: string;
83
+ title: string;
84
+ outlet: string;
85
+ story: string | null;
86
+ image: string | null;
87
+ };
88
+ };
89
+ /** One saved key and the identity it stands for, for an app's synchronous saved-state cache. */
90
+ export interface BookmarkKeyRow {
91
+ key: BookmarkKey;
92
+ kind: BookmarkKind;
93
+ saved_at: string;
94
+ post_id?: string;
95
+ protocol?: BookmarkProtocol;
96
+ native_id?: string;
97
+ url?: string;
98
+ }
99
+ export interface BookmarkMergeResult {
100
+ saved: number;
101
+ already: number;
102
+ skipped: number;
103
+ over_limit: number;
104
+ /** What the member has saved in this app now. */
105
+ total: number;
106
+ /** The most one member keeps per app. */
107
+ limit: number;
108
+ /** One per item, in input order. */
109
+ results: Array<{
110
+ key: BookmarkKey | null;
111
+ status: 'saved' | 'already' | 'skipped' | 'over_limit';
112
+ reason?: 'invalid' | 'unavailable';
113
+ }>;
114
+ }
115
+ /** A saved Minds post, hydrated like a feed post, with when the member saved it. */
116
+ export type BookmarkedPost = Post & {
117
+ bookmarked_at: string | null;
118
+ };
119
+ export interface BookmarksPage<T> extends PaginatedResponse<T> {
120
+ /** Page on `next_cursor` until it is null: a page can come back short while it skips posts the member can no longer read. */
121
+ meta: PaginationMeta & {
122
+ next_cursor: string | null;
123
+ };
124
+ }
125
+ /** Most items one `merge` call takes. */
126
+ export declare const BOOKMARK_MERGE_MAX_ITEMS = 200;
127
+ /**
128
+ * `retry: false` answers after one attempt. Use it for the first call that finds out whether synced
129
+ * bookmarks exist: a 503 `bookmarks_unavailable` is otherwise retried with backoff like any 5xx.
130
+ */
131
+ export interface BookmarkRequestOptions {
132
+ retry?: boolean;
133
+ }
134
+ /**
135
+ * Synced bookmarks: the signed-in member's saved Minds posts, open-network posts and articles in this app,
136
+ * across every device they sign in on. Private to the member; needs the member's own app key.
137
+ *
138
+ * Until the server has synced bookmarks every call rejects with a 503 whose code is `bookmarks_unavailable`:
139
+ * keep bookmarks on the device then. Keys that are not a member's own app key get 403 `project_binding_required`.
140
+ */
141
+ export declare class BookmarksResource {
142
+ private client;
143
+ constructor(client: HttpClient);
144
+ /** Save one item. Idempotent: saving what is saved changes nothing. 409 `bookmark_limit` past the member's limit. */
145
+ save(item: BookmarkInput, options?: BookmarkRequestOptions): Promise<SingleResponse<SavedBookmark>>;
146
+ /** Unsave by the key a save, list or keys call returned, or by `{ postId }`. Idempotent. */
147
+ unsave(target: BookmarkKey | {
148
+ postId: string;
149
+ }, options?: BookmarkRequestOptions): Promise<SingleResponse<UnsavedBookmark>>;
150
+ /** Saved Minds posts, newest saved first, hydrated like the feed. Page on `meta.next_cursor`. */
151
+ posts(params?: {
152
+ limit?: number;
153
+ cursor?: string;
154
+ }, options?: BookmarkRequestOptions): Promise<BookmarksPage<BookmarkedPost>>;
155
+ /** Saved open-network posts and articles, newest saved first. Page on `meta.next_cursor`. */
156
+ list(params?: {
157
+ kind?: 'social' | 'article';
158
+ limit?: number;
159
+ cursor?: string;
160
+ }, options?: BookmarkRequestOptions): Promise<BookmarksPage<BookmarkItem>>;
161
+ /** Every saved key (hidden posts included), newest saved first, up to 500 a page. For a saved-state cache. */
162
+ keys(params?: {
163
+ limit?: number;
164
+ cursor?: string;
165
+ }, options?: BookmarkRequestOptions): Promise<BookmarksPage<BookmarkKeyRow>>;
166
+ /**
167
+ * Upload a device's saves, e.g. when its member signs in: at most 200 items a call. Idempotent (a second
168
+ * merge of the same items is a no-op); invalid items and posts the member cannot read are `skipped`, and
169
+ * items past the member's limit come back `over_limit` (the newest are kept).
170
+ */
171
+ merge(items: BookmarkMergeItem[], options?: BookmarkRequestOptions): Promise<SingleResponse<BookmarkMergeResult>>;
172
+ }
@@ -0,0 +1,44 @@
1
+ /** Most items one `merge` call takes. */
2
+ export const BOOKMARK_MERGE_MAX_ITEMS = 200;
3
+ /**
4
+ * Synced bookmarks: the signed-in member's saved Minds posts, open-network posts and articles in this app,
5
+ * across every device they sign in on. Private to the member; needs the member's own app key.
6
+ *
7
+ * Until the server has synced bookmarks every call rejects with a 503 whose code is `bookmarks_unavailable`:
8
+ * keep bookmarks on the device then. Keys that are not a member's own app key get 403 `project_binding_required`.
9
+ */
10
+ export class BookmarksResource {
11
+ client;
12
+ constructor(client) {
13
+ this.client = client;
14
+ }
15
+ /** Save one item. Idempotent: saving what is saved changes nothing. 409 `bookmark_limit` past the member's limit. */
16
+ save(item, options) {
17
+ return this.client.put('/bookmarks', item, options);
18
+ }
19
+ /** Unsave by the key a save, list or keys call returned, or by `{ postId }`. Idempotent. */
20
+ unsave(target, options) {
21
+ const key = typeof target === 'string' ? target : `post:${target.postId}`;
22
+ return this.client.delete(`/bookmarks/${encodeURIComponent(key)}`, options);
23
+ }
24
+ /** Saved Minds posts, newest saved first, hydrated like the feed. Page on `meta.next_cursor`. */
25
+ posts(params, options) {
26
+ return this.client.get('/posts', { bookmarked: true, ...params }, options);
27
+ }
28
+ /** Saved open-network posts and articles, newest saved first. Page on `meta.next_cursor`. */
29
+ list(params, options) {
30
+ return this.client.get('/bookmarks', params, options);
31
+ }
32
+ /** Every saved key (hidden posts included), newest saved first, up to 500 a page. For a saved-state cache. */
33
+ keys(params, options) {
34
+ return this.client.get('/bookmarks/keys', params, options);
35
+ }
36
+ /**
37
+ * Upload a device's saves, e.g. when its member signs in: at most 200 items a call. Idempotent (a second
38
+ * merge of the same items is a no-op); invalid items and posts the member cannot read are `skipped`, and
39
+ * items past the member's limit come back `over_limit` (the newest are kept).
40
+ */
41
+ merge(items, options) {
42
+ return this.client.post('/bookmarks/merge', { items }, options);
43
+ }
44
+ }
@@ -1,5 +1,5 @@
1
1
  import type { HttpClient } from '../client.js';
2
- import type { Community, CommunityBannedMember, CommunityCard, CommunityDetail, CommunityMember, CommunityPeopleParams, CreateCommunityInput, CreatedCommunity, JoinResult, LeaveResult, ListCommunitiesParams, PaginatedResponse, PaginationParams, RelatedCommunity, SingleResponse } from '../types.js';
2
+ import type { AnswerJoinRequestsInput, ApprovedJoinRequests, Community, CommunityBannedMember, CommunityCard, CommunityDetail, CommunityMember, CommunityPeopleParams, CreateCommunityInput, CreatedCommunity, DeclinedJoinRequests, JoinResult, LeaveResult, ListCommunitiesParams, PaginatedResponse, PaginationParams, RelatedCommunity, SingleResponse } from '../types.js';
3
3
  export declare class CommunitiesResource {
4
4
  private client;
5
5
  constructor(client: HttpClient);
@@ -52,6 +52,16 @@ export declare class CommunitiesResource {
52
52
  declineMember(id: string, userId: string): Promise<SingleResponse<{
53
53
  declined: boolean;
54
54
  }>>;
55
+ /**
56
+ * Let many pending members in at once. Admin or higher. Pass `{ user_ids }`
57
+ * (at most 100) or `{ all: true }` for the oldest 100 waiting; call again
58
+ * while `remaining` is above 0. Named people with no pending request (already
59
+ * in, already answered, banned, never asked) come back in `skipped`. A ban is
60
+ * never answered.
61
+ */
62
+ approveMembers(id: string, input: AnswerJoinRequestsInput): Promise<SingleResponse<ApprovedJoinRequests>>;
63
+ /** Turn many pending members away at once, as `approveMembers` lets them in. Admin or higher. */
64
+ declineMembers(id: string, input: AnswerJoinRequestsInput): Promise<SingleResponse<DeclinedJoinRequests>>;
55
65
  /** Remove a member. Moderator or higher, and only members below your role. */
56
66
  removeMember(id: string, userId: string): Promise<SingleResponse<{
57
67
  removed: boolean;
@@ -70,6 +70,20 @@ export class CommunitiesResource {
70
70
  declineMember(id, userId) {
71
71
  return this.client.post(`/communities/${id}/members/${userId}/decline`);
72
72
  }
73
+ /**
74
+ * Let many pending members in at once. Admin or higher. Pass `{ user_ids }`
75
+ * (at most 100) or `{ all: true }` for the oldest 100 waiting; call again
76
+ * while `remaining` is above 0. Named people with no pending request (already
77
+ * in, already answered, banned, never asked) come back in `skipped`. A ban is
78
+ * never answered.
79
+ */
80
+ approveMembers(id, input) {
81
+ return this.client.post(`/communities/${encodeURIComponent(id)}/requests/approve`, input);
82
+ }
83
+ /** Turn many pending members away at once, as `approveMembers` lets them in. Admin or higher. */
84
+ declineMembers(id, input) {
85
+ return this.client.post(`/communities/${encodeURIComponent(id)}/requests/decline`, input);
86
+ }
73
87
  /** Remove a member. Moderator or higher, and only members below your role. */
74
88
  removeMember(id, userId) {
75
89
  return this.client.delete(`/communities/${id}/members/${userId}`);
@@ -7,7 +7,7 @@ export type ForYouParams = {
7
7
  offset?: number;
8
8
  /** The previous page's `meta.next_cursor`: pages a stored ranking, so posts read meanwhile never shift the next page. */
9
9
  cursor?: string;
10
- /** Adds `meta.discovery` (layouts) and the public safety floor; limit <= 25. */
10
+ /** Adds `meta.discovery` (layouts); limit <= 25. Every page carries the promotion safety floor, with or without it. */
11
11
  discovery?: boolean;
12
12
  /** `discover` ranks for Discover: fresher, beyond the member's follows, with their category order. */
13
13
  surface?: ForYouSurface;