diff --git a/.claude/skills/docs-sync/SKILL.md b/.claude/skills/docs-sync/SKILL.md
index b1479a99..4a9699d5 100644
--- a/.claude/skills/docs-sync/SKILL.md
+++ b/.claude/skills/docs-sync/SKILL.md
@@ -1,6 +1,6 @@
---
name: docs-sync
-description: Sync the stream-node docs snippet tests (__tests__/docs-snippets) with a getstream.io docs checkout. Asks for the docs repo, first sweeps the docs for missing or mislabelled Node.js snippets (server-side code labelled JavaScript, JavaScript-only snippets the node SDK can run), then runs `yarn test:docs:sync` against it, brings the tests up to date with docs changes (docs win), adds tests for new Node.js snippets, and writes fixes for broken snippets back to the docs. Use when the user asks to sync, check or update the docs snippets or docs snippet tests.
+description: Sync the stream-node docs snippet tests (__tests__/docs-snippets) with a getstream.io docs checkout. Asks for the docs repo, first sweeps the docs for missing or mislabelled Node.js snippets (server-side code labelled JavaScript, JavaScript-only snippets the node SDK can run), then runs `yarn test:docs:sync` against it, brings the tests up to date with docs changes (docs win), adds tests for new Node.js snippets, writes fixes for broken snippets back to the docs, and backfills missing stream-chat@9 fences from existing v9 code. Use when the user asks to sync, check or update the docs snippets or docs snippet tests.
argument-hint: "[path to the getstream.io checkout or its content/docs folder]"
---
@@ -50,17 +50,17 @@ Before the sync check, make sure every server-side snippet on the tested pages h
- Pages: every page of the tested sidebars (`SIDEBAR_FILES` in `sync/parse.ts`, `$DOCS_DIR/_sidebars/`) under `PAGE_PREFIXES`, the same list as `findSidebarPages`.
- Fences: every JS fence (`js`, `javascript`, `ts`, `typescript`) labelled `JavaScript` or unlabelled, together with its tab group: the other fences in the same `` block, or the fence alone when it isn't in one.
-- Out of scope: `React` and `React Native` fences, the stream-chat-js@9 fences (`JavaScript (stream-chat-js@9)`, `Node.js (stream-chat-js@9)`), `Node.js` fences inside a `` block, fences listed in `EXCLUDED_FENCES` (`sync/parse.ts`), and comment-only fences.
+- Out of scope: `React` and `React Native` fences, the stream-chat@9 fences (`JavaScript (stream-chat@9)`, `Node.js (stream-chat@9)`), `Node.js` fences inside a `` block, fences listed in `EXCLUDED_FENCES` (`sync/parse.ts`), and comment-only fences.
List them with a throwaway script in the scratchpad (page, heading, line, the labels in the group, the code). Then classify each group by its tabs first and the prose second:
-| Group | Action |
-| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| A `JavaScript` fence with server-only code (secret, `updateAppSettings`, `createToken`, ...) | Relabel it `Node.js` and rewrite it to the node SDK call (`client.updateApp`, ...). Relabel its `JavaScript (stream-chat-js@9)` partner, if any, `Node.js (stream-chat-js@9)`, code unchanged. |
-| Only client SDKs (`JavaScript` plus Kotlin, Swift, Dart, Unity, Unreal, React, ...) | Client-side: no `Node.js` tab, even when a server equivalent exists. |
-| `JavaScript` only (or with non-SDK tabs such as `JSON`) | Add a `Node.js` tab when the node SDK can do the same thing, unless the heading or prose hints "client-side only". See below. |
-| Other server SDK tabs (Python, Ruby, PHP, Go, C#, Java) but no `Node.js` | Add a `Node.js` tab. If the others are comment-only notes ("client-side only operation"), add the same note for the Node SDK. |
-| A client group, and the page has its own server-side section for it ("Server-Side Usage", "Server Side") | No `Node.js` tab: the server-side section covers it. |
+| Group | Action |
+| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| A `JavaScript` fence with server-only code (secret, `updateAppSettings`, `createToken`, ...) | Relabel it `Node.js` and rewrite it to the node SDK call (`client.updateApp`, ...). Relabel its `JavaScript (stream-chat@9)` partner, if any, `Node.js (stream-chat@9)`, code unchanged. |
+| Only client SDKs (`JavaScript` plus Kotlin, Swift, Dart, Unity, Unreal, React, ...) | Client-side: no `Node.js` tab, even when a server equivalent exists. |
+| `JavaScript` only (or with non-SDK tabs such as `JSON`) | Add a `Node.js` tab when the node SDK can do the same thing, unless the heading or prose hints "client-side only". See below. |
+| Other server SDK tabs (Python, Ruby, PHP, Go, C#, Java) but no `Node.js` | Add a `Node.js` tab. If the others are comment-only notes ("client-side only operation"), add the same note for the Node SDK. |
+| A client group, and the page has its own server-side section for it ("Server-Side Usage", "Server Side") | No `Node.js` tab: the server-side section covers it. |
JavaScript-only groups are common: JavaScript is usually the first SDK documented, and the other SDKs follow later. Being alone is no hint that the code is client-side. Client APIs used in passing aren't either: translate them to the server side (`client.user?.language` → `user.language` of a user you have; the message from `client.chat.getMessage`). Skip the group only when:
@@ -72,8 +72,8 @@ To check what the node SDK can do, read `src/StreamClient.ts`, `src/StreamChatCl
### Writing the Node.js fence
- Mirror the `JavaScript` fence: same comments, structure and placeholder literals (`"user-1"`, `"messaging:general"`).
-- Use the node SDK's shapes and types: `client.chat.*` / `client.feeds.*`, request objects (`filter_conditions`, `preferences: [...]`), `Date` where the types say `Date` (not `.toISOString()`), and `user_id` where the client code acts as the connected user (server-side `queryChannels` with `hidden` / `muted`, drafts, devices need it).
-- Don't write a `Node.js (stream-chat-js@9)` fence for it: list the group in the report so the v9 version can be added by hand.
+- Use the node SDK's shapes and types: `client.chat.*` / `client.feeds.*`, request objects (`filter_conditions`, `preferences: [...]`), `Date` where the request types say `Date` (not `.toISOString()`), the SDK time helpers (`nsToDate`, `nsToMs`) for response timestamps (unix-ns numbers, not `Date`s), and `user_id` where the client code acts as the connected user (server-side `queryChannels` with `hidden` / `muted`, drafts, devices need it).
+- Don't write its `Node.js (stream-chat@9)` partner here: step 6 backfills it from existing v9 code.
- Put it before the group's ``. A fence that isn't in a `` block: wrap it and the new fence in ``.
- From `$DOCS_DIR`, run `npx prettier --write ` and `node scripts/tab-lint.mjs ` (it puts the tabs in the canonical order: client SDKs first, then server SDKs).
@@ -90,7 +90,7 @@ It runs without network and reports four kinds of failure. Collect them all, gro
| Failure | Meaning |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `snippets without a test region` | A `Node.js` fence on a tested sidebar page (`SIDEBAR_FILES`) has no region: a new snippet, a new page, or a fence whose key changed (see the next row). |
-| mislabelled JS fences | A JS fence is unlabelled or has a label other than `JavaScript`, `React`, `React Native`, `Node.js` or their stream-chat-js@9 versions. |
+| mislabelled JS fences | A JS fence is unlabelled or has a label other than `JavaScript`, `React`, `React Native`, `Node.js` or their stream-chat@9 versions. |
| `no such fence` | A region's key (`docs`, `heading`, `tab`, `index`) matches no fence: the fence was removed, moved, or its heading was renamed. The failure lists the page's fence keys. |
| ` differs from :` | The region's code and the fence's code differ. |
@@ -104,7 +104,7 @@ Work one docs page at a time.
### Mislabelled fence
-Label it `Node.js` for `@stream-io/node-sdk` (server-side) code, `JavaScript` for client code. Code that works on both sides (e.g. a bare filter object) gets `JavaScript`: an unlabelled `js` fence already renders like `label="JavaScript"` (the JavaScript route's own sample, a titled fallback on the Node route), so the page doesn't change. Whether it also needs a `Node.js` tab is decided by the sweep (step 3). Otherwise, `JavaScript`, `React` and `React Native` fences, the stream-chat-js@9 fences (`JavaScript (stream-chat-js@9)`, `Node.js (stream-chat-js@9)`), and `Node.js` fences inside a `` block (reference implementations without the SDK), are out of scope: never test or change them.
+Label it `Node.js` for `@stream-io/node-sdk` (server-side) code, `JavaScript` for client code. Code that works on both sides (e.g. a bare filter object) gets `JavaScript`: an unlabelled `js` fence already renders like `label="JavaScript"` (the JavaScript route's own sample, a titled fallback on the Node route), so the page doesn't change. Whether it also needs a `Node.js` tab is decided by the sweep (step 3). Otherwise, `JavaScript`, `React` and `React Native` fences, the stream-chat@9 fences (`JavaScript (stream-chat@9)`, `Node.js (stream-chat@9)`), and `Node.js` fences inside a `` block (reference implementations without the SDK), are out of scope: never test or change them. The one exception: step 6 adds missing stream-chat@9 fences.
### Region differs from its fence (docs changed)
@@ -133,7 +133,7 @@ Write a test for it in the page's test file (create the file if the page is new)
- A fence that isn't code (e.g. a bare data shape) is a docs bug: rewrite it as code that runs (e.g. reading and logging the fields), and report it. Never remove a `Node.js` fence just because it is a fragment or can't be tested: wrap it, or put it in `it.skip`.
- A snippet the test app can't run goes in `it.skip('BLOCKED: ', ...)` so it is still typechecked; report the exact API error.
- **Another SDK's code in a `Node.js` fence** (e.g. Feeds v2 `getstream` client code: `client.flagUser`, `client.user(id).flag`, `ctx.alice.feed(...)`): it isn't `@stream-io/node-sdk` code and isn't ours to update. Don't rewrite or relabel it: add the fence to `EXCLUDED_FENCES` in `sync/parse.ts` with a reason, and report it.
-- **Client-side code in a `Node.js` fence** (needs a connected user or a WebSocket, or a method the node SDK doesn't have: `connectUser`, `channel.watch()`, `client.on(...)`): never test different code from what the fence shows. If the node SDK can do the same thing, make the smallest docs fix that turns the fence into that call (`channel.watch()` → `channel.getOrCreate()`, `user_id` instead of `connectUser`). If it can't (events, WebSocket, local client state), the fence is mislabelled: label it `JavaScript`. Its `Node.js (stream-chat-js@9)` partner, if any, becomes `JavaScript (stream-chat-js@9)`, code unchanged. Use `it.skip('BLOCKED: client-side code: ...')` only while that decision is open. Report it either way. To check what a server-side endpoint accepts, read the SDK source (`src/StreamClient.ts`, `src/StreamChatClient.ts`, `src/StreamChannel.ts`, `src/gen/`) or the OpenAPI spec (`chat-serverside-api.json` in https://github.com/GetStream/protocol/tree/main/openapi/v2).
+- **Client-side code in a `Node.js` fence** (needs a connected user or a WebSocket, or a method the node SDK doesn't have: `connectUser`, `channel.watch()`, `client.on(...)`): never test different code from what the fence shows. If the node SDK can do the same thing, make the smallest docs fix that turns the fence into that call (`channel.watch()` → `channel.getOrCreate()`, `user_id` instead of `connectUser`). If it can't (events, WebSocket, local client state), the fence is mislabelled: label it `JavaScript`. Its `Node.js (stream-chat@9)` partner, if any, becomes `JavaScript (stream-chat@9)`, code unchanged. Use `it.skip('BLOCKED: client-side code: ...')` only while that decision is open. Report it either way. To check what a server-side endpoint accepts, read the SDK source (`src/StreamClient.ts`, `src/StreamChatClient.ts`, `src/StreamChannel.ts`, `src/gen/`) or the OpenAPI spec (`chat-serverside-api.json` in https://github.com/GetStream/protocol/tree/main/openapi/v2).
- **Push and webhooks**: don't test that notifications or webhook events arrive; assert the response or read the setting back (`getApp()`, `listPushProviders()`, ...). Never touch or use the test app's own push provider (see "Test app" in `AGENTS.md`): only use providers the test creates with `uniqueId` names. Snippets that need real credentials run in `describe.skipIf(!hasFirebaseCredentials)` (`helpers/push.ts`); the README's "Push credentials" section has the details.
- **App-level side effects** (app settings, built-in channel types, grants, webhook URLs, push config, ...): read the original value first and restore it with `cleanup.add(...)`, or the file fails with `DRIFT`.
@@ -155,7 +155,37 @@ Only when the docs snippet itself doesn't work (wrong method name or signature,
Docs bugs you leave unfixed (prose, non-`Node.js` fences, or waiting for a decision) go in the report (see "Report").
-## 6. Gates
+## 6. Backfill stream-chat@9 fences
+
+The chat and platform pages show every stream-chat-js snippet twice: the current version (`JavaScript`, `Node.js`) and the v9 one (`JavaScript (stream-chat@9)`, `Node.js (stream-chat@9)`). Fences added on the docs `main` branch, or rewritten in this run, often arrive without their v9 partner. Run this step after the fixes, since they can relabel or rewrite fences.
+
+### Scope
+
+- Pages: the chat and platform sidebar pages. Feeds, video and moderation pages have no stream-chat-js code: skip them.
+- Fences: every `JavaScript` and `Node.js` fence whose group has no partner with the matching v9 label. Skip comment-only fences, `` blocks, `EXCLUDED_FENCES`, and `Node.js` fences of APIs stream-chat-js never had (`client.video.*`, `client.feeds.*`, `client.moderation.*`).
+
+List them with a throwaway script in the scratchpad (page, heading, line, the group's labels).
+
+### Finding the v9 code
+
+Never write v9 code from scratch: copy it from one of these sources, in this order, and record which one you used.
+
+1. **The docs `main` branch** (`git fetch origin main`). `main` is the v9 docs: its plain `JavaScript` / `Node.js` / unlabelled `js` fence under the same heading is the v9 code. It only counts if it is v9 code (`serverClient`, `updateAppSettings`, positional arguments like `updateChannelType("messaging", {...})`, `channel.create()`). A fence identical to the branch's node SDK code (`payload: { filter_conditions }`, `client.chat.*`) means the page never had v9 code there.
+2. **The page's history on the docs branch**: the fence before the commit that rewrote it for stream-chat-js@10 (`git log -S'' -- `, then `git show ^:`).
+3. **A fence replaced in this run**: when the sweep rewrote a server-side `JavaScript` fence to a `Node.js` one, the old code (`git diff` in `$DOCS_DIR`) is the v9 partner.
+4. **A `JavaScript` fence that is v9 server code** next to a `Node.js` fence (e.g. `client.partialUpdateUser(...)` beside `client.updateUsersPartial(...)`): relabel it `Node.js (stream-chat@9)`.
+5. **The same call in both versions** (e.g. `client.blockUser("user-to-block")`): only when another fence on the docs pairs that exact code with an identical v9 copy. Copy it.
+
+No source: leave the group as it is and list it in the report ("never had v9 code"). Don't ask whether to write one.
+
+### Writing the v9 fence
+
+- Copy the code unchanged. Label it `JavaScript (stream-chat@9)` for a `JavaScript` fence, `Node.js (stream-chat@9)` for a `Node.js` fence.
+- Put it right after its partner, in the same ``. A fence that isn't in a `` block: wrap it and the new fence in ``.
+- When a group's fences match their source fences by position, copy them in that order. When they don't (some added, some removed), match them by heading and code, and leave any you can't pair.
+- From `$DOCS_DIR`, run `npx prettier --write ` and `node scripts/tab-lint.mjs `, then `DOCS_DIR=$DOCS_DIR yarn test:docs:sync`: v9 fences aren't tested, but the check must stay green.
+
+## 7. Gates
All must pass before you finish:
@@ -176,14 +206,14 @@ Run `yarn test:docs` only on the test files you changed, never two runs at once,
- Don't commit, push, stash or create branches in either repo.
- Never write the API key, secret or push credentials to any file except the repo root `.env`.
- Don't edit `src/`, `__tests__/docs-snippets/AGENTS.md`, the helpers or the configs. Propose changes in the report instead.
-- Change docs snippets as little as possible: only when they don't work, or to add or relabel `Node.js` fences in the sweep (step 3).
+- Change docs snippets as little as possible: only when they don't work, to add or relabel `Node.js` fences in the sweep (step 3), or to backfill stream-chat@9 fences (step 6).
## Report
End with:
1. **Docs folder** used, and both repos' branches.
-2. **Sweep**: `Node.js` fences added or relabelled, per page, and the groups you left without one and why (client SDKs only, a server-side section on the page, no node SDK equivalent, prose says client-side).
+2. **Sweep**: `Node.js` fences added or relabelled, per page, and the groups you left without one and why (client SDKs only, a server-side section on the page, no node SDK equivalent, prose says client-side). Then the stream-chat@9 fences backfilled, per page, each with its source (`main`, the commit, a fence replaced in this run, a relabel), and the groups left without one because they never had v9 code.
3. **Tests updated from the docs**: per page, the regions changed and why the setup/assertions changed.
4. **New and removed tests**: snippets added, regions removed with their fences, renamed markers.
5. **Docs changes**: every fence changed in `$DOCS_DIR` (fixes, labels, client-side code), each with before/after, reason and evidence. These need human review.
diff --git a/__tests__/docs-snippets/AGENTS.md b/__tests__/docs-snippets/AGENTS.md
index 2f38809b..6b39333e 100644
--- a/__tests__/docs-snippets/AGENTS.md
+++ b/__tests__/docs-snippets/AGENTS.md
@@ -23,6 +23,7 @@ New entries are added only after the user approves them (agents propose them in
- `helpers/call-types.ts`: `createCallType(client, cleanup)` creates a `uniqueId` call type, waits until it reads back, and registers it on `cleanup.callTypes`; `trackCallTypeCreates(client, cleanup)` does the same for fences that create the type themselves (spy, call before the region); `retryOnCallTypeMissing(fn)` retries calls into a new type (successful `getCallType` reads don't prove every node knows it: `getOrCreate` can still fail with code 16 "call type does not exist", wrap it in a spy inside regions). A call type with calls can't be deleted (code 4 "multiple calls of that type exist"): `Cleanup` hard-deletes `cleanup.calls` (waiting for the delete task) before `cleanup.callTypes`. `updateChannelType(client, name, patch)` (`helpers/channel-types.ts`) fills in the fields the request requires; `grantMessagingMembers` (`helpers/grants.ts`) adds `messaging` member grants and registers the restore. `helpers/server.ts` has repeated lookups: `sendServerMessage`, `getServerUser` / `getServerMember` / `getServerChannel` (throw unless exactly one result matches), `deletePollsCreatedBy`, `waitForAppSetting`, `waitForTeamIdSupport` (multi-tenancy toggles) `waitForReviewItem` (review queue item of an entity once its flags are there), `deleteFeedModerationTemplate` (the generated `v2DeleteTemplate()` can't send the name) and `saveAndRestoreEventHooks(client, cleanup)` (saves `event_hooks`, registers the restore, returns `{ original, restore }`; `waitForEventHooks`, `withoutTimestamps`). `helpers/feeds.ts`: `retryOnFeedGroupMissing`, `addActivityOnce`, `copyFeedGroup(client, cleanup, builtInId)` (a `uniqueId` group with a built-in group's settings, on `cleanup.feedGroups`), `notificationTypes` / `waitForNotificationTypes` (a user's built-in `notification` feed). `helpers/uploads.ts`: upload fixtures (`JPEG_BASE64` / `jpeg`, `MINIMAL_PDF`) and `ignoreMissingFile` (swallows the 404 of deleting a gone upload).
- One `it` per docs snippet (or per closely related group of snippets in the same tab group).
- Add basic `expect`s after each snippet to prove it did what the docs claim.
+- **Timestamps**: server-sent dates on responses and events are unix-**nanosecond** numbers (`TimestampNS`), not `Date`s; request date fields still take a `Date`. Use the SDK's time helpers (`nsToMs` for arithmetic against `Date.now()`, `nsToDate` to pass one back in a request, `convertTimestampToDate` for optional ones), assert presence with `expect.any(Number)`, and compare two server timestamps directly. Never `new Date(t)` or `.getTime()` on a response field.
### Test app
@@ -134,7 +135,7 @@ New entries are added only after the user approves them (agents propose them in
- An app setting that changes API behavior (e.g. `enforce_unique_usernames`) can break later tests in the same file: restore it at the end of the test that changes it, and keep the `cleanup.add` restore as a fallback.
- Video calls: create them with `uniqueId` ids and push the cid to `cleanup.calls` (hard deleted, "not found" ignored). Call types: `cleanup.callTypes` (see `helpers/call-types.ts`).
- Feeds v3 works on the test app (feed groups `user`, `timeline`, `notification`, ...): create feeds with `uniqueId` ids (`client.feeds.feed(group, id).getOrCreate({ user_id })`) and push the fid (`group:id`) to `cleanup.feeds` (hard deleted, "not found" / "can't be found" ignored, so a snippet may delete it first). After a soft `deleteFeedUserData`, `getActivity` on the user's activity rejects with `metadata.responseCode: 404`.
-- Feed groups are app-wide and eventually consistent: create them with a `uniqueId` id (hyphens are accepted) and push it to `cleanup.feedGroups` (deleted after `cleanup.feeds`). Calls into a new group can fail with code 16 "Feed group with ID ... not found" for a while (usually 1–2s): `retryOnFeedGroupMissing(client, ['getOrCreateFeed', 'follow', ...])` (`helpers/feeds.ts`) spies on the generated methods and retries them (`StreamFeed` methods go through `client.feeds`). `addActivity` into a new group can answer "not found" after storing the activity, so a blind retry adds it twice: in setup use `addActivityOnce(client, request)` (explicit id, retries only while `getActivity` doesn't find it). `updateFeedGroup` changes take a few seconds to reach reads: wait for several reads in a row to agree. An `updateFeedGroup` fence that leaves fields out clears them, including `aggregation` and `push_notification.push_types`. Feed group `ranking.type` accepts only `expression` and `interest` (code 4); recency is the default when `ranking` is omitted. `updateFeedGroup({ id })` with no other fields clears `custom` (updates replace every field). `deleteFeedGroup` without `hard_delete` soft-deletes (listed only with `include_soft_deleted: true`).
+- Feed groups are app-wide and eventually consistent: create them with a `uniqueId` id (hyphens are accepted) and push it to `cleanup.feedGroups` (deleted after `cleanup.feeds`). Calls into a new group can fail with code 16 "Feed group with ID ... not found" for a while (usually 1–2s): `retryOnFeedGroupMissing(client, ['getOrCreateFeed', 'follow', ...])` (`helpers/feeds.ts`) spies on the generated methods and retries them (`StreamFeed` methods go through `client.feeds`). `addActivity` into a new group can answer "not found" after storing the activity, so a blind retry adds it twice: in setup use `addActivityOnce(client, request)` (explicit id, retries only while `getActivity` doesn't find it). A fence that calls `addActivity` into a group the setup created: `waitForFeedGroup(client, id)` first (several `getFeedGroup` reads in a row). `updateFeedGroup` changes take a few seconds to reach reads: wait for several reads in a row to agree. An `updateFeedGroup` fence that leaves fields out clears them, including `aggregation` and `push_notification.push_types`. Feed group `ranking.type` accepts only `expression` and `interest` (code 4); recency is the default when `ranking` is omitted. `updateFeedGroup({ id })` with no other fields clears `custom` (updates replace every field). `deleteFeedGroup` without `hard_delete` soft-deletes (listed only with `include_soft_deleted: true`).
- Feed views are app-wide and eventually consistent: `uniqueId` ids and `cleanup.add(() => client.feeds.deleteFeedView({ id }))` (ignore "not found"). `getFeedView` and `getOrCreate({ view })` answer code 16 "... not found" for 1–3s after a create (`retryOnFeedGroupMissing` / `isFeedGroupMissing` match it), `listFeedViews` lags ~4s, `ranking.type: "recency"` is rejected, `updateFeedView({ id })` replaces every field. To read a spy that `retryOnFeedGroupMissing` installed, call `vi.spyOn(client.feeds, 'method')` again (returns the existing mock); `vi.mocked(client.feeds.method)` fails `unbound-method`.
- `updateApp({ max_aggregated_activities_length })` returns 200 but `getApp` keeps 0 and the limit isn't applied (2026-10-16); values over 100 are rejected (code 4).
- Feed ranking: ranked results settle within ~1–3s; make test popularity the reverse of recency so a passing order proves the ranking. Popularity = reactions (one per type per user: use several types) + shares × 3. Ranking expressions are validated on create (unknown variables such as `externals`, fields without defaults, non-map arguments to map functions, `min_popularity` < 1 for the popular selector). `preference_score` is 0 until the user gives `activityFeedback`. A group created without `activity_selectors` defaults to `current_feed`. Map key order in responses (`ranking.functions`) isn't stable: sort before comparing. A duplicate `createFeedGroup` id (or a built-in id like `timeline`) fails with "already exists": a docs bug.
@@ -233,7 +234,7 @@ New entries are added only after the user approves them (agents propose them in
- A new channel type starts with empty `guest` grants (no `messaging` defaults). To test removing guest grants on a `uniqueId` type, first copy the guest grants from the built-in type (read only), then `waitForChannelTypePropagation()`; with create + update waits, give that `beforeAll` a 180s timeout.
- Uploads are checked by content as well as extension: a tiny CSV (`a,b`) is read as `text/plain` and fails "File type text/plain is not supported" under `allowed_mime_types: ["text/csv"]`. Use a few rows of real CSV.
- To prove `disable_auth_checks`, send a dev token (`..devtoken`, base64url JSON) to `POST /channels?api_key=...` with `stream-auth-type: jwt`: 201 while checks are off, 401 when on. The 401 message differs per API node ("development tokens are not allowed for this application" or "Token signature is invalid", the latter for over a minute on some nodes): accept both, and `retry` until the status matches.
-- The same request proves a user token works or is revoked: `POST /channels?api_key=...` with `Authorization: ` and `stream-auth-type: jwt` returns 201 when accepted and 401 code 40 "This token has been revoked, please generate a new token" after a revocation (takes effect within seconds: `retry`). The SDK sets `iat` one second in the past by default, so a token made right after revoking with `new Date()` can still count as revoked: retry the fresh-token check, or give the tokens to revoke an `iat` well in the past. `revoke_tokens_issued_before` reads back as a `Date` that can be 1 ms off: compare with a ±1 ms tolerance.
+- The same request proves a user token works or is revoked: `POST /channels?api_key=...` with `Authorization: ` and `stream-auth-type: jwt` returns 201 when accepted and 401 code 40 "This token has been revoked, please generate a new token" after a revocation (takes effect within seconds: `retry`). The SDK sets `iat` one second in the past by default, so a token made right after revoking with `new Date()` can still count as revoked: retry the fresh-token check, or give the tokens to revoke an `iat` well in the past. `revoke_tokens_issued_before` reads back (unix ns) up to 1 ms off the `Date` sent: compare `nsToMs(...)` with a ±1 ms tolerance.
- `file_upload_config` / `image_upload_config` come back with every list present (`[]`) and `size_limit: 0`: restore the saved original and `waitForAppSetting` (deep compare) for it.
- A new custom role isn't known to every API node right away: `updateUsersPartial({ set: { role } })` just after `createRole` can fail with code 4 `role "..." provided but not defined`. Spy on `client.updateUsersPartial` with a `mockImplementation` that calls the bound original through `retry(..., { retryIf: })`. `deleteRole` right after `createRole` fails the same way with `role "..." does not exist`: retry it like that on `'does not exist'`.
- `updateChannelType({ grants: null })` resets grants to the API defaults, which differ from a new type's initial grants (they add e.g. `create-attachment`, `send-poll`, `create-mention`): assert against the update response. New and built-in types start with `guest: []` and `anonymous: []`: to prove a removal snippet works, give those roles grants first.
diff --git a/__tests__/docs-snippets/README.md b/__tests__/docs-snippets/README.md
index d4e2dcff..8e957430 100644
--- a/__tests__/docs-snippets/README.md
+++ b/__tests__/docs-snippets/README.md
@@ -10,8 +10,8 @@ these sidebars in `getstream.io/content/docs/_sidebars/` (`SIDEBAR_FILES` in `sy
- platform: `[platform][default].json`
Only fences labelled `Node.js` are in scope (a fence label, or the `label` of the enclosing `` on the video pages) (docs fences must always have a label): they are fixed when they are
-wrong. `JavaScript`, `React` and `React Native` fences (client-side code), the stream-chat-js@9 fences (`JavaScript (stream-chat-js@9)`,
-`Node.js (stream-chat-js@9)`) and `Node.js` fences inside a `` block (reference
+wrong. `JavaScript`, `React` and `React Native` fences (client-side code), the stream-chat@9 fences (`JavaScript (stream-chat@9)`,
+`Node.js (stream-chat@9)`) and `Node.js` fences inside a `` block (reference
implementations without the SDK) are out of scope and are neither tested nor changed.
```sh
@@ -91,8 +91,8 @@ await channel.getOrCreate({ data: { created_by_id: creatorId } });
- `index` is 1-based among fences with the same heading text and tab (default 1).
- **COPY**: `name="literal"` pairs, applied to the region before comparing (identifier `name` becomes the docs literal). Don't use COPY variables as object keys or shorthand properties.
- **Comparison**: both sides are formatted with the same prettier options, so quote style and line wrapping don't matter, but any code or comment change does.
-- **Coverage** (checked first): every `Node.js` fence (`js`/`javascript`/`ts`/`typescript`) on the pages of the tested sidebars (see the top of this file; `$fragment` entries are resolved from `_sidebars/fragments/`), whether or not the page has tests yet, must have a region (inside `it.skip` if it can't run). `JavaScript` / `React` / `React Native` / stream-chat-js@9 fences and comment-only fences are ignored.
-- **Labels**: every JS fence on those pages must be labelled `JavaScript`, `React`, `React Native`, `Node.js`, `JavaScript (stream-chat-js@9)` or `Node.js (stream-chat-js@9)`; unlabelled or otherwise labelled JS fences fail.
+- **Coverage** (checked first): every `Node.js` fence (`js`/`javascript`/`ts`/`typescript`) on the pages of the tested sidebars (see the top of this file; `$fragment` entries are resolved from `_sidebars/fragments/`), whether or not the page has tests yet, must have a region (inside `it.skip` if it can't run). `JavaScript` / `React` / `React Native` / stream-chat@9 fences and comment-only fences are ignored.
+- **Labels**: every JS fence on those pages must be labelled `JavaScript`, `React`, `React Native`, `Node.js`, `JavaScript (stream-chat@9)` or `Node.js (stream-chat@9)`; unlabelled or otherwise labelled JS fences fail.
- **Excluded pages and fences**: `EXCLUDED_PAGES` in `sync/parse.ts` lists sidebar pages that are deliberately not validated, with a reason. Both checks skip them. `EXCLUDED_FENCES` does the same for single `Node.js` fences (keyed by fence id) that hold another SDK's code, e.g. Feeds v2 `getstream` client code on the moderation Feeds v2 quick start: not ours to test or update.
- **`DOCS_SYNC_WRITE= yarn test:docs:sync`** writes the regions of that page into the docs fences (docs prettier style). Use it to apply a fix to the docs. `DOCS_SYNC_WRITE=1` writes all pages.
- **`DOCS_DIR`** points at another docs checkout (default: `../getstream.io/content/docs` next to this repo). The check is skipped if the directory doesn't exist.
diff --git a/__tests__/docs-snippets/global-setup.ts b/__tests__/docs-snippets/global-setup.ts
index 76de4943..5a776d21 100644
--- a/__tests__/docs-snippets/global-setup.ts
+++ b/__tests__/docs-snippets/global-setup.ts
@@ -5,6 +5,7 @@ import { createdAtFromId, DOCS_TEST_PREFIX } from './helpers/ids';
import {
describeLeftovers,
findLeftovers,
+ leftoverCreatedAt,
removeLeftovers,
} from './helpers/leaks';
import type { Leftover } from './helpers/leaks';
@@ -28,7 +29,7 @@ const createdAt = (leftover: Leftover) => {
// anything else falls back to the server timestamp.
const fromId = createdAtFromId(leftover.id.split(' ')[0]);
if (fromId) return fromId;
- return leftover.createdAt ? new Date(leftover.createdAt) : undefined;
+ return leftoverCreatedAt(leftover);
};
const loadEnv = () => {
diff --git a/__tests__/docs-snippets/helpers/feeds.ts b/__tests__/docs-snippets/helpers/feeds.ts
index e7913463..4a16b1ab 100644
--- a/__tests__/docs-snippets/helpers/feeds.ts
+++ b/__tests__/docs-snippets/helpers/feeds.ts
@@ -40,6 +40,18 @@ export const retryOnFeedGroupMissing = (
}
};
+/**
+ * Waits until a new feed group can be read back several times in a row, for setup
+ * before a region that calls `addActivity` into it (which can't be retried blindly).
+ */
+export const waitForFeedGroup = async (client: StreamClient, id: string) => {
+ for (let i = 0; i < 5; i++) {
+ await retry(() => client.feeds.getFeedGroup({ id }), {
+ retryIf: isFeedGroupMissing,
+ });
+ }
+};
+
/**
* Adds an activity (setup only) into a feed whose group may be new. `addActivity` can
* answer "Feed group ... not found" after it already stored the activity, so a blind
diff --git a/__tests__/docs-snippets/helpers/leaks.ts b/__tests__/docs-snippets/helpers/leaks.ts
index 977fb4df..8e365e96 100644
--- a/__tests__/docs-snippets/helpers/leaks.ts
+++ b/__tests__/docs-snippets/helpers/leaks.ts
@@ -1,4 +1,9 @@
-import type { StreamClient } from '../../../index';
+import { nsToDate } from '../../../index';
+import type {
+ PushProviderName,
+ StreamClient,
+ TimestampNS,
+} from '../../../index';
import { DOCS_TEST_PREFIX } from './ids';
import { deleteMembershipLevel } from './feeds';
import {
@@ -8,12 +13,23 @@ import {
import { hardDeleteUsers } from './users';
import { retry, retryOnRateLimit, waitForTask } from './wait';
+/** A leftover's `createdAt` as a `Date`, or `undefined` when it has none. */
+export const leftoverCreatedAt = ({ createdAt }: Leftover) => {
+ if (createdAt === undefined) return undefined;
+ return typeof createdAt === 'number'
+ ? nsToDate(createdAt)
+ : new Date(createdAt);
+};
+
/** Something a docs test created and didn't remove. */
export type Leftover = {
kind: string;
id: string;
- /** Used by the sweep to skip resources of runs that may still be in progress. */
- createdAt?: Date | string;
+ /**
+ * Used by the sweep to skip resources of runs that may still be in progress. A unix-ns
+ * timestamp from a response, or an RFC3339 string; read it with `leftoverCreatedAt`.
+ */
+ createdAt?: number | string;
remove: () => Promise;
/**
* Removes many leftovers of this kind in one call (e.g. users in one `deleteUsers`).
@@ -112,7 +128,7 @@ const scanners: Record = {
async channels(client, { matches, ids: channelIds = [], createdSince }) {
const found = new Map();
- const add = (channel: { cid: string; created_at: Date }) =>
+ const add = (channel: { cid: string; created_at: number }) =>
found.set(channel.cid, {
kind: 'channel',
id: channel.cid,
@@ -337,7 +353,9 @@ const scanners: Record = {
createdAt: provider.created_at,
remove: () =>
client.deletePushProvider({
- type: provider.type,
+ // The spec types `PushProviderResponse.type` as a plain string, but the
+ // delete endpoint takes a `PushProviderName`: the listed type is one of them.
+ type: provider.type as PushProviderName,
name: provider.name,
}),
}));
@@ -402,12 +420,17 @@ const scanners: Record = {
// without one it fails with code 4 "cannot specify sort and next/prev at the same
// time" (2026-10-16).
const scan = async (filter_conditions: Record) => {
- let after: Date | undefined;
+ let after: TimestampNS | undefined;
for (;;) {
const response = await retryOnRateLimit(() =>
client.video.queryCalls({
filter_conditions: after
- ? { $and: [filter_conditions, { created_at: { $gt: after } }] }
+ ? {
+ $and: [
+ filter_conditions,
+ { created_at: { $gt: nsToDate(after) } },
+ ],
+ }
: filter_conditions,
sort: [{ field: 'created_at', direction: 1 }],
limit: 100,
diff --git a/__tests__/docs-snippets/maintenance/sweep.ts b/__tests__/docs-snippets/maintenance/sweep.ts
index 7a2bc3df..f99d701a 100644
--- a/__tests__/docs-snippets/maintenance/sweep.ts
+++ b/__tests__/docs-snippets/maintenance/sweep.ts
@@ -4,6 +4,7 @@ import { createdAtFromId, DOCS_TEST_PREFIX } from '../helpers/ids';
import {
describeLeftovers,
findLeftovers,
+ leftoverCreatedAt,
removeLeftovers,
} from '../helpers/leaks';
import type { Leftover } from '../helpers/leaks';
@@ -20,9 +21,8 @@ const dryRun = process.env.DOCS_SWEEP_DRY_RUN === '1';
const minAgeMinutes = Number(process.env.DOCS_SWEEP_MIN_AGE ?? 30);
const isOldEnough = (leftover: Leftover) => {
- const createdAt = leftover.createdAt
- ? new Date(leftover.createdAt)
- : createdAtFromId(leftover.id.split(' ')[0]);
+ const createdAt =
+ leftoverCreatedAt(leftover) ?? createdAtFromId(leftover.id.split(' ')[0]);
// Unknown age: only sweep it when explicitly asked to ignore age.
if (!createdAt || Number.isNaN(createdAt.getTime()))
return minAgeMinutes === 0;
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/01-basics/01-quickstart.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/01-basics/01-quickstart.test.ts
index abbb7e49..f73aadaf 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/01-basics/01-quickstart.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/01-basics/01-quickstart.test.ts
@@ -185,7 +185,7 @@ describe(DOCS, () => {
return response;
});
expect(after.notification_status?.unread).toBe(0);
- expect(after.notification_status?.last_read_at).toBeInstanceOf(Date);
+ expect(after.notification_status?.last_read_at).toEqual(expect.any(Number));
});
it('creates a poll, attaches it to an activity and votes', async () => {
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/01-feed-groups.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/01-feed-groups.test.ts
index 24f0d59e..3c131a4f 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/01-feed-groups.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/01-feed-groups.test.ts
@@ -440,6 +440,6 @@ describe(DOCS, () => {
});
return all.groups[feedGroupId];
});
- expect(deleted?.deleted_at).toBeInstanceOf(Date);
+ expect(deleted?.deleted_at).toEqual(expect.any(Number));
});
});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/04-custom_ranking.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/04-custom_ranking.test.ts
index 1aa27b6d..c535fc2a 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/04-custom_ranking.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/04-custom_ranking.test.ts
@@ -19,7 +19,6 @@ import type {
import { getServerClient } from '../../../../helpers/clients';
import { Cleanup } from '../../../../helpers/cleanup';
import {
- addActivityOnce,
isFeedGroupMissing,
retryOnFeedGroupMissing,
} from '../../../../helpers/feeds';
@@ -53,11 +52,11 @@ const createUser = async (name: string) => {
* user feed with three activities, posted oldest first (a0, a1, a2), so
* recency alone would order them a2, a1, a0:
*
- * | activity | popularity | custom | interest_tags |
- * | -------- | -------------------- | ------------------------------------------- | ------------- |
- * | a0 | 5 (5 reactions) | stock: tesla, stocks: [] | exercise |
- * | a1 | 4 (4 reactions) | isBoosted, stock: nokia, stocks: [microsoft] | food |
- * | a2 | 3 (1 share, 3 points) | stock: apple, stocks: [apple, tesla] | travel |
+ * | activity | popularity |
+ * | -------- | --------------------- |
+ * | a0 | 5 (5 reactions) |
+ * | a1 | 4 (4 reactions) |
+ * | a2 | 3 (1 share, 3 points) |
*/
let authorId: string;
let readerId: string;
@@ -121,12 +120,7 @@ describe(DOCS, () => {
// (activities in new groups are added with `addActivityOnce`: a retried
// addActivity can add them twice).
beforeEach(() =>
- retryOnFeedGroupMissing(client, [
- 'getOrCreateFeed',
- 'follow',
- 'markActivity',
- 'activityFeedback',
- ]),
+ retryOnFeedGroupMissing(client, ['getOrCreateFeed', 'follow']),
);
afterEach(() => vi.restoreAllMocks());
afterAll(() => cleanup.run());
@@ -140,33 +134,19 @@ describe(DOCS, () => {
.feed('user', authorId)
.getOrCreate({ user_id: authorId });
await client.feeds.feed('user', fanId).getOrCreate({ user_id: fanId });
- const post = async (
- name: string,
- custom: Record,
- tag: string,
- ) => {
+ const post = async (name: string) => {
const { activity } = await client.feeds.addActivity({
type: 'post',
feeds: [`user:${authorId}`],
text: name,
- custom,
- interest_tags: [tag],
user_id: authorId,
});
names.set(activity.id, name);
return activity.id;
};
- const a0 = await post('a0', { stock: 'tesla', stocks: [] }, 'exercise');
- const a1 = await post(
- 'a1',
- { isBoosted: true, stock: 'nokia', stocks: ['microsoft'] },
- 'food',
- );
- const a2 = await post(
- 'a2',
- { stock: 'apple', stocks: ['apple', 'tesla'] },
- 'travel',
- );
+ const a0 = await post('a0');
+ const a1 = await post('a1');
+ const a2 = await post('a2');
const types = ['like', 'love', 'wow', 'haha', 'sad'];
for (const type of types) {
await client.feeds.addActivityReaction({
@@ -282,622 +262,6 @@ describe(DOCS, () => {
}
});
- it('ranks with custom data and defaults', async () => {
- const myId = newFeedGroupId('myid');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Activity data" tab="Node.js" index=1
- // COPY: myId="myid"
- await serverClient.feeds.createFeedGroup({
- id: myId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- score: 'popularity * (custom.isBoosted ? 1.5 : 1)',
- defaults: {
- custom: {
- isBoosted: false,
- },
- },
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.ranking?.defaults).toEqual({
- custom: { isBoosted: false },
- });
- // a1 (popularity 4) is boosted to 6, above a0 (5).
- const feed = await rankedFeed(myId, ['a1', 'a0', 'a2']);
- expect(byName(feed).a1.score).toBe(6);
- // Without the default, the expression is rejected.
- await expect(
- client.feeds.createFeedGroup({
- id: newFeedGroupId('no-default'),
- ranking: {
- type: 'expression',
- score: 'popularity * (custom.isBoosted ? 1.5 : 1)',
- },
- }),
- ).rejects.toThrow('expression references fields without defaults');
- });
-
- it('ranks with decay functions', async () => {
- const myId = newFeedGroupId('myid');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Parameters" tab="Node.js" index=1
- // COPY: myId="myid"
- await serverClient.feeds.createFeedGroup({
- id: myId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- functions: {
- simple_gauss: {
- base: 'decay_gauss',
- scale: '5d',
- offset: '1d',
- decay: '0.3',
- },
- popularity_gauss: {
- base: 'decay_gauss',
- scale: '100',
- offset: '5',
- decay: '0.5',
- },
- },
- score: 'simple_gauss(time)*popularity_gauss(popularity)',
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(Object.keys(feed_group.ranking?.functions ?? {}).sort()).toEqual([
- 'popularity_gauss',
- 'simple_gauss',
- ]);
- const feed = await followAuthor(myId);
- const response = await retry(async () => {
- const r = await feed.getOrCreate({ user_id: readerId });
- if (ours(r).length !== 3) throw new Error('activities missing');
- return r;
- });
- // (2026-10-16) Every activity scores 0: popularity_gauss has no
- // `origin`, which defaults to now (a UNIX timestamp), so popularity is
- // never near it.
- expect(ours(response).map((a) => a.score)).toEqual([0, 0, 0]);
- // simple_gauss alone gives activities younger than the offset a 1.
- const timeOnlyId = newFeedGroupId('time-only');
- await client.feeds.createFeedGroup({
- id: timeOnlyId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- functions: feed_group.ranking?.functions,
- score: 'simple_gauss(time)',
- },
- });
- const timeOnly = await followAuthor(timeOnlyId);
- const timeOnlyResponse = await retry(async () => {
- const r = await timeOnly.getOrCreate({ user_id: readerId });
- if (ours(r).length !== 3) throw new Error('activities missing');
- return r;
- });
- expect(ours(timeOnlyResponse).map((a) => a.score)).toEqual([1, 1, 1]);
- });
-
- it('ranks by interest weights', async () => {
- const myId = newFeedGroupId('myid');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Interest weights" tab="Node.js" index=1
- // COPY: myId="myid"
- await serverClient.feeds.createFeedGroup({
- id: myId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'interest',
- score: 'interest_score * popularity',
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.ranking?.type).toBe('interest');
-
- const timeline = await followAuthor(myId);
- // The reader has no stored interests: every interest_score is 0.
- const plain = await retry(async () => {
- const r = await timeline.getOrCreate({ user_id: readerId });
- if (ours(r).length !== 3) throw new Error('activities missing');
- return r;
- });
- expect(ours(plain).map((a) => a.score)).toEqual([0, 0, 0]);
- // Settle the ranking with the docs' weights before the region.
- await waitForOrder(timeline, ['a2', 'a1', 'a0'], {
- interest_weights: { travel: 1, food: 0.5, exercise: -1 },
- });
- // The spy retryOnFeedGroupMissing installed.
- const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
- getOrCreate.mockClear();
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="User interests" tab="Node.js" index=1
- // COPY: readerId=""
- await timeline.getOrCreate({
- interest_weights: {
- travel: 1,
- food: 0.5,
- exercise: -1,
- },
- user_id: readerId,
- });
- // #endregion snippet
-
- const response = await (getOrCreate.mock.results[0]
- .value as Promise);
- // travel (a2) > food (a1) > exercise (a0, a negative score).
- expect(order(response)).toEqual(['a2', 'a1', 'a0']);
- const got = byName(response);
- expect(got.a1.score).toBeGreaterThan(0);
- expect(got.a0.score).toBeLessThan(0);
- });
-
- it('ranks by preference score', async () => {
- const myId = newFeedGroupId('myid');
- const myIdInterest = newFeedGroupId('myid-interest');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Preference score" tab="Node.js" index=1
- // COPY: myId="myid", myIdInterest="myid_interest"
- // Using preference_score with expression type
- await serverClient.feeds.createFeedGroup({
- id: myId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- score: 'preference_score * popularity',
- },
- });
-
- // Using preference_score with interest type
- await serverClient.feeds.createFeedGroup({
- id: myIdInterest,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'interest',
- score: 'preference_score * interest_score * popularity',
- },
- });
- // #endregion snippet
-
- const groups = await createdGroups(create);
- expect(groups.map((g) => g.feed_group.ranking?.type)).toEqual([
- 'expression',
- 'interest',
- ]);
- // A user of their own: activity feedback changes their preferences.
- const userId = await createUser('preferences');
- const feed = await followAuthor(myId, userId);
- // Without activity feedback preference_score is 0 for every activity.
- const before = await retry(async () => {
- const r = await feed.getOrCreate({ user_id: userId });
- if (ours(r).length !== 3) throw new Error('activities missing');
- return r;
- });
- expect(ours(before).map((a) => a.score)).toEqual([0, 0, 0]);
- // "Show more" on a2 (travel) moves it to the top.
- await client.feeds.activityFeedback({
- activity_id: byName(before).a2.id,
- show_more: true,
- user_id: userId,
- });
- const after = await waitForOrder(feed, ['a2', 'a1', 'a0'], {}, userId);
- expect(byName(after).a2.score).toBeGreaterThan(0);
- expect(byName(after).a1.score).toBe(0);
- });
-
- it('ranks by selector source', async () => {
- const selectorTimelineId = newFeedGroupId('selector-timeline');
- const multiSelectorTimelineId = newFeedGroupId('multi-selector-timeline');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Ranking by Selector Source" tab="Node.js" index=1
- // COPY: selectorTimelineId="selector_timeline", multiSelectorTimelineId="multi_selector_timeline"
- // Example 1: Simple boost - following activities get 2x multiplier
- await serverClient.feeds.createFeedGroup({
- id: selectorTimelineId,
- activity_selectors: [
- { type: 'following' },
- { type: 'popular', min_popularity: 1 },
- ],
- ranking: {
- type: 'expression',
- score: 'selector_source == "following" ? popularity * 2 : popularity',
- defaults: {
- selector_source: '', // Required when using selector_source in expressions
- },
- },
- });
-
- // Example 2: Multiple selectors with different bonuses
- await serverClient.feeds.createFeedGroup({
- id: multiSelectorTimelineId,
- activity_selectors: [
- { type: 'following' },
- { type: 'interest' },
- { type: 'popular', min_popularity: 1 },
- ],
- ranking: {
- type: 'expression',
- score: `
- (selector_source == "following" ? 10 : 0) +
- (selector_source == "interest" ? 5 : 0) +
- popularity
- `,
- defaults: {
- selector_source: '',
- },
- },
- });
- // #endregion snippet
-
- const groups = await createdGroups(create);
- expect(groups.map((g) => g.feed_group.activity_selectors?.length)).toEqual([
- 2, 3,
- ]);
- // The author's activities come from the following selector (the first
- // one): their popularity is doubled / gets +10.
- const first = await rankedFeed(selectorTimelineId, ['a0', 'a1', 'a2']);
- for (const activity of ours(first)) {
- expect(activity.selector_source).toBe('following');
- expect(activity.score).toBe(activity.popularity * 2);
- }
- const second = await rankedFeed(multiSelectorTimelineId, [
- 'a0',
- 'a1',
- 'a2',
- ]);
- for (const activity of ours(second)) {
- expect(activity.selector_source).toBe('following');
- expect(activity.score).toBe(activity.popularity + 10);
- }
- });
-
- it('ranks read activities lower', async () => {
- const rankedTimelineId = newFeedGroupId('ranked-timeline');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Ranking by Read/Seen Status" tab="Node.js" index=1
- // COPY: rankedTimelineId="ranked_timeline"
- await serverClient.feeds.createFeedGroup({
- id: rankedTimelineId,
- activity_marks: {
- track_seen: true,
- track_read: true,
- },
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- score: 'popularity - (is_read ? 100 : 0)',
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.activity_marks).toMatchObject({ track_read: true });
- const feed = await followAuthor(rankedTimelineId);
- const unread = await waitForOrder(feed, ['a0', 'a1', 'a2']);
- const a0 = byName(unread).a0.id;
- await feed.markActivity({ mark_read: [a0], user_id: readerId });
- // The most popular activity drops to the bottom once read.
- const response = await waitForOrder(feed, ['a1', 'a2', 'a0']);
- expect(byName(response).a0).toMatchObject({ is_read: true, score: -95 });
- });
-
- it('ranks unseen activities first', async () => {
- const unseenTimelineId = newFeedGroupId('unseen-timeline');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Ranking by Read/Seen Status" tab="Node.js" index=2
- // COPY: unseenTimelineId="unseen_timeline"
- await serverClient.feeds.createFeedGroup({
- id: unseenTimelineId,
- activity_marks: {
- track_seen: true,
- track_read: true,
- },
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- score: 'is_seen ? 0 : 100',
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.ranking?.score).toBe('is_seen ? 0 : 100');
- const feed = await followAuthor(unseenTimelineId);
- const before = await retry(async () => {
- const r = await feed.getOrCreate({ user_id: readerId });
- if (ours(r).length !== 3) throw new Error('activities missing');
- return r;
- });
- expect(ours(before).map((a) => a.score)).toEqual([100, 100, 100]);
- const a1 = byName(before).a1.id;
- await feed.markActivity({ mark_seen: [a1], user_id: readerId });
- const response = await retry(async () => {
- const r = await feed.getOrCreate({ user_id: readerId });
- if (order(r)[2] !== 'a1') throw new Error('a1 not last yet');
- return r;
- });
- expect(byName(response).a1).toMatchObject({ is_seen: true, score: 0 });
- });
-
- it('ranks with external data', async () => {
- const myId = newFeedGroupId('myid');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Accessing external data in ranking expressions" tab="Node.js" index=1
- // COPY: myId="myid"
- await serverClient.feeds.createFeedGroup({
- id: myId,
- ranking: {
- type: 'expression',
- score:
- 'popularity * external.popularity_multiplier + share_count * external.share_multiplier',
- defaults: {
- external: {
- // Provide default values if external data isn't provided when reading the feed
- popularity_multiplier: 1,
- share_multiplier: 1,
- },
- },
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.ranking?.defaults).toEqual({
- external: { popularity_multiplier: 1, share_multiplier: 1 },
- });
- // No selectors: the group ranks the feed's own activities
- // (current_feed). b0 has popularity 5, b1 one share (popularity 3).
- cleanup.feeds.push(`${myId}:${readerId}`);
- const timeline = client.feeds.feed(myId, readerId);
- await retry(() => timeline.getOrCreate({ user_id: readerId }), {
- retryIf: isFeedGroupMissing,
- });
- const post = async (name: string) => {
- const activityId = await addActivityOnce(client, {
- type: 'post',
- feeds: [timeline.feed],
- text: name,
- user_id: authorId,
- });
- names.set(activityId, name);
- return activityId;
- };
- const b0 = await post('b0');
- const b1 = await post('b1');
- for (const type of ['like', 'love', 'wow', 'haha', 'sad']) {
- await client.feeds.addActivityReaction({
- activity_id: b0,
- type,
- user_id: readerId,
- });
- }
- await addActivityOnce(client, {
- type: 'post',
- feeds: [timeline.feed],
- parent_id: b1,
- text: 'shared',
- user_id: authorId,
- });
- // With the defaults: b0 5, b1 3 + 1 share.
- const plain = await waitForOrder(timeline, ['b0', 'b1']);
- expect(byName(plain).b1.score).toBe(4);
- await waitForOrder(timeline, ['b1', 'b0'], {
- external_ranking: { popularity_multiplier: 1, share_multiplier: 100 },
- });
- // The spy retryOnFeedGroupMissing installed.
- const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
- getOrCreate.mockClear();
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Providing external data when reading the feed" tab="Node.js" index=1
- // COPY: readerId=""
- await timeline.getOrCreate({
- external_ranking: {
- popularity_multiplier: 1, // normal weight for popularity
- share_multiplier: 100, // very high boost for share_count
- },
- user_id: readerId,
- });
- // #endregion snippet
-
- const response = await (getOrCreate.mock.results[0]
- .value as Promise);
- // The shared activity jumps to the top: 3 + 1 * 100.
- expect(order(response)).toEqual(['b1', 'b0']);
- expect(byName(response).b1.score).toBe(103);
- });
-
- it('ranks with map_lookup', async () => {
- const myId = newFeedGroupId('myid');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Arrays and Maps" tab="Node.js" index=1
- // COPY: myId="myid"
- await serverClient.feeds.createFeedGroup({
- id: myId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- score: 'popularity + map_lookup(stock, external.stock_boosts)',
- defaults: {
- stock: '',
- external: {
- stock_boosts: {},
- },
- },
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.ranking?.defaults).toEqual({
- stock: '',
- external: { stock_boosts: {} },
- });
- const timeline = await followAuthor(myId);
- await waitForOrder(timeline, ['a0', 'a1', 'a2']);
- // The spy retryOnFeedGroupMissing installed.
- const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
- getOrCreate.mockClear();
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Arrays and Maps" tab="Node.js" index=2
- // COPY: readerId=""
- await timeline.getOrCreate({
- external_ranking: {
- stock_boosts: {
- apple: 0.9,
- tesla: 0.5,
- microsoft: 0.3,
- },
- },
- user_id: readerId,
- });
- // #endregion snippet
-
- const response = await (getOrCreate.mock.results[0]
- .value as Promise);
- // a0 (tesla) 5 + 0.5, a1 (nokia, not in the map) 4 + 0, a2 (apple) 3 + 0.9.
- const got = byName(response);
- expect(got.a0.score).toBeCloseTo(5.5, 5);
- expect(got.a1.score).toBeCloseTo(4, 5);
- expect(got.a2.score).toBeCloseTo(3.9, 5);
- });
-
- it('ranks with sum_map_lookup', async () => {
- const myId = newFeedGroupId('myid');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Arrays and Maps" tab="Node.js" index=3
- // COPY: myId="myid"
- await serverClient.feeds.createFeedGroup({
- id: myId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- score: 'popularity + sum_map_lookup(stocks, external.stock_boosts)',
- defaults: {
- stocks: [],
- external: {
- stock_boosts: {},
- },
- },
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.ranking?.defaults).toEqual({
- stocks: [],
- external: { stock_boosts: {} },
- });
- const timeline = await followAuthor(myId);
- // a0 (no stocks) 5, a1 (microsoft) 4 + 2, a2 (apple, tesla) 3 + 15.
- await waitForOrder(timeline, ['a2', 'a1', 'a0'], {
- external_ranking: {
- stock_boosts: { apple: 10.0, tesla: 5.0, microsoft: 2.0 },
- },
- });
- // The spy retryOnFeedGroupMissing installed.
- const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
- getOrCreate.mockClear();
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Arrays and Maps" tab="Node.js" index=4
- // COPY: readerId=""
- await timeline.getOrCreate({
- external_ranking: {
- stock_boosts: {
- apple: 10.0,
- tesla: 5.0,
- microsoft: 2.0,
- },
- },
- user_id: readerId,
- });
-
- // score = popularity + 10.0 + 5.0 = popularity + 15.0 (for activity with stocks: ['apple', 'tesla'])
- // #endregion snippet
-
- const response = await (getOrCreate.mock.results[0]
- .value as Promise);
- expect(order(response)).toEqual(['a2', 'a1', 'a0']);
- const got = byName(response);
- expect(got.a2.score).toBe(18);
- expect(got.a1.score).toBe(6);
- expect(got.a0.score).toBe(5);
- });
-
- it('ranks with in_array', async () => {
- const myId = newFeedGroupId('myid');
- const create = vi.spyOn(client.feeds, 'createFeedGroup');
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Arrays and Maps" tab="Node.js" index=5
- // COPY: myId="myid"
- await serverClient.feeds.createFeedGroup({
- id: myId,
- activity_selectors: [{ type: 'following' }],
- ranking: {
- type: 'expression',
- score: 'popularity + in_array(stock, external.watchlist) * 10',
- defaults: {
- stock: '',
- external: {
- watchlist: [],
- },
- },
- },
- });
- // #endregion snippet
-
- const [{ feed_group }] = await createdGroups(create);
- expect(feed_group.ranking?.defaults).toEqual({
- stock: '',
- external: { watchlist: [] },
- });
- const timeline = await followAuthor(myId);
- // a0 (tesla) 5 + 10, a1 (nokia) 4, a2 (apple) 3 + 10.
- await waitForOrder(timeline, ['a0', 'a2', 'a1'], {
- external_ranking: { watchlist: ['apple', 'tesla', 'microsoft'] },
- });
- // The spy retryOnFeedGroupMissing installed.
- const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
- getOrCreate.mockClear();
-
- // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/04-custom_ranking.md" heading="Arrays and Maps" tab="Node.js" index=6
- // COPY: readerId=""
- await timeline.getOrCreate({
- external_ranking: {
- watchlist: ['apple', 'tesla', 'microsoft'],
- },
- user_id: readerId,
- });
-
- // activities with stocks in the watchlist get a +10 boost, others get no boost.
- // #endregion snippet
-
- const response = await (getOrCreate.mock.results[0]
- .value as Promise);
- expect(order(response)).toEqual(['a0', 'a2', 'a1']);
- const got = byName(response);
- expect(got.a0.score).toBe(15);
- expect(got.a2.score).toBe(13);
- expect(got.a1.score).toBe(4);
- });
-
it('inspects ranking variables', async () => {
// The docs' "sara": a timeline that follows the author.
const saraId = await createUser('sara');
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/05-activity_selectors.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/05-activity_selectors.test.ts
index 46637150..96f2598e 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/05-activity_selectors.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/05-activity_selectors.test.ts
@@ -9,6 +9,7 @@ import {
vi,
} from 'vitest';
import type {
+ CreateFeedViewResponse,
GetOrCreateFeedRequest,
GetOrCreateFeedResponse,
UpdateFeedGroupResponse,
@@ -44,6 +45,8 @@ const createUser = async (name: string) => {
};
const amsterdam = { lat: 52.373558, lng: 4.885261 };
+// About 17 km west of `amsterdam`.
+const haarlem = { lat: 52.3874, lng: 4.6462 };
const withinAmsterdam = {
within_bounds: {
$eq: {
@@ -359,6 +362,130 @@ describe(DOCS, () => {
);
});
+ it('reads a proximity feed around a viewer location', async () => {
+ // The docs read john's timeline; the built-in timeline group has no
+ // proximity selector, so the fence reads a uniqueId group with one.
+ const timelineId = newFeedGroupId('timeline');
+ await client.feeds.createFeedGroup({
+ id: timelineId,
+ activity_selectors: [{ type: 'proximity' }],
+ });
+ const johnId = await createUser('john');
+ cleanup.feeds.push(`${timelineId}:${johnId}`);
+ // john has no stored location: without viewer_location the read fails.
+ await expect(
+ client.feeds.getOrCreateFeed({
+ feed_group_id: timelineId,
+ feed_id: johnId,
+ user_id: johnId,
+ }),
+ ).rejects.toThrow('no near or within_bounds filter provided');
+ const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
+ getOrCreate.mockClear();
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/05-activity_selectors.md" heading="Viewer location" tab="Node.js" index=1
+ // COPY: timelineId="timeline", johnId="john"
+ await client.feeds.getOrCreateFeed({
+ feed_group_id: timelineId,
+ feed_id: johnId,
+ user_id: johnId,
+ viewer_location: { lat: 52.373558, lng: 4.885261 },
+ });
+ // #endregion snippet
+
+ const response = await (getOrCreate.mock.results[0]
+ .value as Promise);
+ expect(response.feed.feed).toBe(`${timelineId}:${johnId}`);
+ // hot and warm are posted in Amsterdam, within the default 10 km.
+ expect([...ours(response)].sort()).toEqual(['hot', 'warm']);
+ // Haarlem, ~17 km away: outside the default radius.
+ const far = await client.feeds.getOrCreateFeed({
+ feed_group_id: timelineId,
+ feed_id: johnId,
+ user_id: johnId,
+ viewer_location: haarlem,
+ });
+ expect(ours(far)).toEqual([]);
+ // Out-of-range coordinates are rejected with a 400.
+ await expect(
+ client.feeds.getOrCreateFeed({
+ feed_group_id: timelineId,
+ feed_id: johnId,
+ user_id: johnId,
+ viewer_location: { lat: 91, lng: 4.885261 },
+ }),
+ ).rejects.toMatchObject({ metadata: { responseCode: 400 } });
+ });
+
+ it('creates a feed view with a proximity radius', async () => {
+ const nearbyId = uniqueId('nearby');
+ cleanup.add(() =>
+ client.feeds.deleteFeedView({ id: nearbyId }).catch((error: unknown) => {
+ if (!String(error).includes('not found')) throw error;
+ }),
+ );
+ const create = vi.spyOn(client.feeds, 'createFeedView');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/05-activity_selectors.md" heading="Viewer location" tab="Node.js" index=2
+ // COPY: nearbyId="nearby"
+ await client.feeds.createFeedView({
+ id: nearbyId,
+ activity_selectors: [
+ {
+ type: 'proximity',
+ // Search 50 miles around the viewer's location
+ viewer_location_radius_km: 80.4672,
+ filter: { activity_type: { $in: ['post', 'event'] } },
+ },
+ ],
+ });
+ // #endregion snippet
+
+ const { feed_view } = await (create.mock.results[0]
+ .value as Promise);
+ // (2026-10-29) The API drops viewer_location_radius_km: the view is
+ // stored without it (also on getFeedView, and a radius of 300 is
+ // accepted although the docs say at most 250).
+ expect(feed_view.activity_selectors).toEqual([
+ {
+ type: 'proximity',
+ filter: { activity_type: { $in: ['post', 'event'] } },
+ },
+ ]);
+ // Read a plain group through the view around Amsterdam: hot and warm
+ // (posts). A workout in Amsterdam is left out by the activity_type filter.
+ const workoutId = await addActivityOnce(client, {
+ type: 'workout',
+ feeds: [`user:${authorId}`],
+ text: 'nearby workout',
+ location: amsterdam,
+ user_id: authorId,
+ });
+ const plainId = newFeedGroupId('plain');
+ await client.feeds.createFeedGroup({ id: plainId });
+ const feed = readerFeed(plainId);
+ // The view is new too: retry until every API node knows it.
+ const response = await retry(async () => {
+ const r = await feed.getOrCreate({
+ view: nearbyId,
+ viewer_location: amsterdam,
+ user_id: readerId,
+ });
+ if (ours(r).length !== 2) throw new Error(`got ${ours(r).join()}`);
+ return r;
+ });
+ expect([...ours(response)].sort()).toEqual(['hot', 'warm']);
+ expect(response.activities.map((a) => a.id)).not.toContain(workoutId);
+ // (2026-10-29) From Haarlem (~17 km away) the view finds nothing: the
+ // default 10 km radius applies, not the 80 km the docs set.
+ const far = await feed.getOrCreate({
+ view: nearbyId,
+ viewer_location: haarlem,
+ user_id: readerId,
+ });
+ expect(ours(far)).toEqual([]);
+ });
+
it('creates a group with the interest selector', async () => {
const myId = newFeedGroupId('myid');
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/06-notification_feeds.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/06-notification_feeds.test.ts
index 916071e1..3cfa8d81 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/06-notification_feeds.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/06-notification_feeds.test.ts
@@ -589,10 +589,10 @@ describe(DOCS, () => {
// #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/06-notification_feeds.md" heading="Legacy: Client-side computation" tab="Node.js" index=1
// Deprecated — use group.is_read / group.is_seen instead
const isRead =
- (lastReadAt && group.updated_at.getTime() < lastReadAt.getTime()) ||
+ (lastReadAt && group.updated_at < lastReadAt) ||
readActivities.includes(group.group);
const isSeen =
- (lastSeenAt && group.updated_at.getTime() < lastSeenAt.getTime()) ||
+ (lastSeenAt && group.updated_at < lastSeenAt) ||
seenActivities.includes(group.group);
// #endregion snippet
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/08-for_you_feed.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/08-for_you_feed.test.ts
index e40b4e27..0be07b44 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/08-for_you_feed.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/08-for_you_feed.test.ts
@@ -14,6 +14,7 @@ import {
copyFeedGroup,
isFeedGroupMissing,
retryOnFeedGroupMissing,
+ waitForFeedGroup,
} from '../../../../helpers/feeds';
import { uniqueId } from '../../../../helpers/ids';
import { retry } from '../../../../helpers/wait';
@@ -71,6 +72,8 @@ describe(DOCS, () => {
oneFid = await createFeed('user', oneId, oneId);
const stockGroupId = newFeedGroupId('stock');
await client.feeds.createFeedGroup({ id: stockGroupId });
+ // The fence adds an activity into this group, and addActivity can't be retried.
+ await waitForFeedGroup(client, stockGroupId);
appleFid = await createFeed(stockGroupId, uniqueId('apple'), jackId);
});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/09-stories.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/09-stories.test.ts
index 8d880782..4b3fd328 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/09-stories.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/09-stories.test.ts
@@ -1,4 +1,5 @@
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
+import { nsToMs } from '../../../../../../index';
import { getServerClient } from '../../../../helpers/clients';
import { Cleanup } from '../../../../helpers/cleanup';
import { uniqueId } from '../../../../helpers/ids';
@@ -125,7 +126,7 @@ describe(DOCS, () => {
);
// The stored time can be 1 ms off the ISO string that was sent.
expect(
- Math.abs((story.expires_at?.getTime() ?? 0) - tomorrow.getTime()),
+ Math.abs(nsToMs(story.expires_at ?? 0) - tomorrow.getTime()),
).toBeLessThanOrEqual(1);
});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/12-ranking_expressions.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/12-ranking_expressions.test.ts
new file mode 100644
index 00000000..e8859670
--- /dev/null
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/12-ranking_expressions.test.ts
@@ -0,0 +1,375 @@
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import type {
+ ActivityResponse,
+ CreateFeedGroupResponse,
+ GetOrCreateFeedRequest,
+ GetOrCreateFeedResponse,
+ StreamFeed,
+} from '../../../../../../index';
+import { getServerClient } from '../../../../helpers/clients';
+import { Cleanup } from '../../../../helpers/cleanup';
+import {
+ isFeedGroupMissing,
+ retryOnFeedGroupMissing,
+} from '../../../../helpers/feeds';
+import { uniqueId } from '../../../../helpers/ids';
+import { retry } from '../../../../helpers/wait';
+
+const DOCS =
+ 'activity-feeds/v3-latest/_default/03-feed_groups/12-ranking_expressions.md';
+
+// The docs use both names for the server client.
+const client = getServerClient();
+const serverClient = client;
+const cleanup = new Cleanup(client);
+
+// Feed groups are app-wide: every group a fence creates gets a uniqueId id.
+const newFeedGroupId = (name: string) => {
+ const id = uniqueId(name);
+ cleanup.feedGroups.push(id);
+ return id;
+};
+
+const createUser = async (name: string) => {
+ const id = uniqueId(name);
+ cleanup.users.push(id);
+ await client.upsertUsers([{ id }]);
+ return id;
+};
+
+/*
+ * Shared data: a reader whose feeds (one per ranked group) follow an author's
+ * user feed with three activities, posted oldest first (a0, a1, a2), so
+ * recency alone would order them a2, a1, a0:
+ *
+ * | activity | popularity | custom |
+ * | -------- | --------------------- | --------- |
+ * | a0 | 5 (5 reactions) | |
+ * | a1 | 4 (4 reactions) | isBoosted |
+ * | a2 | 3 (1 share, 3 points) | |
+ */
+let authorId: string;
+let readerId: string;
+const names = new Map();
+
+/** Our activities in feed order, by name. */
+const order = (response: GetOrCreateFeedResponse) =>
+ ours(response).map((a) => names.get(a.id));
+const ours = (response: GetOrCreateFeedResponse) =>
+ response.activities.filter((a) => names.has(a.id));
+const byName = (response: GetOrCreateFeedResponse) =>
+ Object.fromEntries(ours(response).map((a) => [names.get(a.id), a])) as Record<
+ string,
+ ActivityResponse
+ >;
+
+/** The reader's feed in a new group, following the author. */
+const followAuthor = async (groupId: string, userId = readerId) => {
+ cleanup.feeds.push(`${groupId}:${userId}`);
+ const feed = client.feeds.feed(groupId, userId);
+ await retry(() => feed.getOrCreate({ user_id: userId }), {
+ retryIf: isFeedGroupMissing,
+ });
+ await retry(
+ () =>
+ client.feeds.follow({ source: feed.feed, target: `user:${authorId}` }),
+ { retryIf: isFeedGroupMissing },
+ );
+ return feed;
+};
+
+/** Reads the feed until all three activities are there in `expected` order. */
+const waitForOrder = (
+ feed: StreamFeed,
+ expected: string[],
+ request: Omit = {},
+ userId = readerId,
+) =>
+ retry(async () => {
+ const response = await feed.getOrCreate({ user_id: userId, ...request });
+ const got = order(response);
+ if (got.join() !== expected.join()) {
+ throw new Error(`order is ${got.join()}, expected ${expected.join()}`);
+ }
+ return response;
+ });
+
+/** The reader's feed in `groupId`, once ranked in `expected` order. */
+const rankedFeed = async (groupId: string, expected: string[]) => {
+ const feed = await followAuthor(groupId);
+ return waitForOrder(feed, expected);
+};
+
+const createdGroups = (spy: { mock: { results: Array<{ value: unknown }> } }) =>
+ Promise.all(
+ spy.mock.results.map((r) => r.value as Promise),
+ );
+
+/**
+ * popularity_gauss from the "Parameters" fence (origin 100, direction right,
+ * scale 100, offset 5, decay 0.5) for values below the origin.
+ */
+const gauss = (popularity: number) => {
+ const distance = Math.max(0, 100 - popularity - 5);
+ const sigma2 = -(100 ** 2) / (2 * Math.log(0.5));
+ return Math.exp(-(distance ** 2) / (2 * sigma2));
+};
+
+describe(DOCS, () => {
+ // Calls into a new group can reach API nodes that don't know it yet
+ // (activities in new groups are added with `addActivityOnce`: a retried
+ // addActivity can add them twice).
+ beforeEach(() =>
+ retryOnFeedGroupMissing(client, ['getOrCreateFeed', 'follow']),
+ );
+ afterEach(() => vi.restoreAllMocks());
+ afterAll(() => cleanup.run());
+
+ beforeAll(async () => {
+ authorId = await createUser('author');
+ readerId = await createUser('reader');
+ const fanId = await createUser('fan');
+ cleanup.feeds.push(`user:${authorId}`, `user:${fanId}`);
+ await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ await client.feeds.feed('user', fanId).getOrCreate({ user_id: fanId });
+ const post = async (name: string, custom: Record = {}) => {
+ const { activity } = await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${authorId}`],
+ text: name,
+ custom,
+ user_id: authorId,
+ });
+ names.set(activity.id, name);
+ return activity.id;
+ };
+ const a0 = await post('a0');
+ const a1 = await post('a1', { isBoosted: true });
+ const a2 = await post('a2');
+ const types = ['like', 'love', 'wow', 'haha', 'sad'];
+ for (const type of types) {
+ await client.feeds.addActivityReaction({
+ activity_id: a0,
+ type,
+ user_id: fanId,
+ });
+ }
+ for (const type of types.slice(0, 4)) {
+ await client.feeds.addActivityReaction({
+ activity_id: a1,
+ type,
+ user_id: fanId,
+ });
+ }
+ // A share: an activity with a2 as parent (in the fan's own feed).
+ await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${fanId}`],
+ parent_id: a2,
+ text: 'shared',
+ user_id: fanId,
+ });
+ await retry(async () => {
+ const response = await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ const got = byName(response);
+ const popularity = ['a0', 'a1', 'a2'].map((n) => got[n]?.popularity);
+ if (popularity.join() !== '5,4,3' || got.a2.share_count !== 1) {
+ throw new Error(`popularity is ${popularity.join()}`);
+ }
+ });
+ });
+
+ it('ranks with custom data and defaults', async () => {
+ const myId = newFeedGroupId('myid');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/12-ranking_expressions.md" heading="Activity data" tab="Node.js" index=1
+ // COPY: myId="myid"
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ score: 'popularity * (custom.isBoosted ? 1.5 : 1)',
+ defaults: {
+ custom: {
+ isBoosted: false,
+ },
+ },
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.ranking?.defaults).toEqual({
+ custom: { isBoosted: false },
+ });
+ // a1 (popularity 4) is boosted to 6, above a0 (5).
+ const feed = await rankedFeed(myId, ['a1', 'a0', 'a2']);
+ expect(byName(feed).a1.score).toBe(6);
+ // Without the default, the expression is rejected.
+ await expect(
+ client.feeds.createFeedGroup({
+ id: newFeedGroupId('no-default'),
+ ranking: {
+ type: 'expression',
+ score: 'popularity * (custom.isBoosted ? 1.5 : 1)',
+ },
+ }),
+ ).rejects.toThrow('expression references fields without defaults');
+ });
+
+ it('ranks with decay functions', async () => {
+ const myId = newFeedGroupId('myid');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/12-ranking_expressions.md" heading="Parameters" tab="Node.js" index=1
+ // COPY: myId="myid"
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ functions: {
+ simple_gauss: {
+ base: 'decay_gauss',
+ scale: '5d',
+ offset: '1d',
+ decay: '0.3',
+ },
+ popularity_gauss: {
+ base: 'decay_gauss',
+ origin: '100',
+ direction: 'right',
+ scale: '100',
+ offset: '5',
+ decay: '0.5',
+ },
+ },
+ score: 'simple_gauss(time)*popularity_gauss(popularity)',
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(Object.keys(feed_group.ranking?.functions ?? {}).sort()).toEqual([
+ 'popularity_gauss',
+ 'simple_gauss',
+ ]);
+ // A second author with one activity at the origin (popularity 100:
+ // 50 comments), which should score 1.
+ const starId = await createUser('star');
+ cleanup.feeds.push(`user:${starId}`);
+ await client.feeds.feed('user', starId).getOrCreate({ user_id: starId });
+ const { activity: star } = await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${starId}`],
+ text: 'star',
+ user_id: starId,
+ });
+ await client.feeds.addCommentsBatch({
+ comments: Array.from({ length: 50 }, (_, i) => ({
+ object_id: star.id,
+ object_type: 'activity',
+ comment: `comment ${i}`,
+ user_id: starId,
+ })),
+ });
+ const feed = await followAuthor(myId);
+ await client.feeds.follow({ source: feed.feed, target: `user:${starId}` });
+ // simple_gauss(time) is 1 for activities younger than a day, so the
+ // score is popularity_gauss(popularity): 1 at the origin, lower below it.
+ const response = await waitForOrder(feed, ['a0', 'a1', 'a2']);
+ const got = byName(response);
+ expect(got.a0.score).toBeCloseTo(gauss(5), 2);
+ expect(got.a1.score).toBeCloseTo(gauss(4), 2);
+ expect(got.a2.score).toBeCloseTo(gauss(3), 2);
+ const starRead = await retry(async () => {
+ const r = await feed.getOrCreate({ user_id: readerId });
+ const found = r.activities.find((a) => a.id === star.id);
+ if (found?.popularity !== 100) throw new Error('star not ranked yet');
+ return found;
+ });
+ expect(starRead.score).toBeCloseTo(1, 5);
+ });
+
+ it('ranks by selector source', async () => {
+ const selectorTimelineId = newFeedGroupId('selector-timeline');
+ const multiSelectorTimelineId = newFeedGroupId('multi-selector-timeline');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/12-ranking_expressions.md" heading="Ranking by Selector Source" tab="Node.js" index=1
+ // COPY: selectorTimelineId="selector_timeline", multiSelectorTimelineId="multi_selector_timeline"
+ // Example 1: Simple boost - following activities get 2x multiplier
+ await serverClient.feeds.createFeedGroup({
+ id: selectorTimelineId,
+ activity_selectors: [
+ { type: 'following' },
+ { type: 'popular', min_popularity: 1 },
+ ],
+ ranking: {
+ type: 'expression',
+ score: 'selector_source == "following" ? popularity * 2 : popularity',
+ defaults: {
+ selector_source: '', // Required when using selector_source in expressions
+ },
+ },
+ });
+
+ // Example 2: Multiple selectors with different bonuses
+ await serverClient.feeds.createFeedGroup({
+ id: multiSelectorTimelineId,
+ activity_selectors: [
+ { type: 'following' },
+ { type: 'interest' },
+ { type: 'popular', min_popularity: 1 },
+ ],
+ ranking: {
+ type: 'expression',
+ score: `
+ (selector_source == "following" ? 10 : 0) +
+ (selector_source == "interest" ? 5 : 0) +
+ popularity
+ `,
+ defaults: {
+ selector_source: '',
+ },
+ },
+ });
+ // #endregion snippet
+
+ const groups = await createdGroups(create);
+ expect(groups.map((g) => g.feed_group.activity_selectors?.length)).toEqual([
+ 2, 3,
+ ]);
+ // The author's activities come from the following selector (the first
+ // one): their popularity is doubled / gets +10.
+ const first = await rankedFeed(selectorTimelineId, ['a0', 'a1', 'a2']);
+ for (const activity of ours(first)) {
+ expect(activity.selector_source).toBe('following');
+ expect(activity.score).toBe(activity.popularity * 2);
+ }
+ const second = await rankedFeed(multiSelectorTimelineId, [
+ 'a0',
+ 'a1',
+ 'a2',
+ ]);
+ for (const activity of ours(second)) {
+ expect(activity.selector_source).toBe('following');
+ expect(activity.score).toBe(activity.popularity + 10);
+ }
+ });
+});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/13-interest_ranking.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/13-interest_ranking.test.ts
new file mode 100644
index 00000000..560e6e3d
--- /dev/null
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/13-interest_ranking.test.ts
@@ -0,0 +1,292 @@
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import type {
+ ActivityResponse,
+ CreateFeedGroupResponse,
+ GetOrCreateFeedRequest,
+ GetOrCreateFeedResponse,
+ StreamFeed,
+} from '../../../../../../index';
+import { getServerClient } from '../../../../helpers/clients';
+import { Cleanup } from '../../../../helpers/cleanup';
+import {
+ isFeedGroupMissing,
+ retryOnFeedGroupMissing,
+} from '../../../../helpers/feeds';
+import { uniqueId } from '../../../../helpers/ids';
+import { retry } from '../../../../helpers/wait';
+
+const DOCS =
+ 'activity-feeds/v3-latest/_default/03-feed_groups/13-interest_ranking.md';
+
+// The docs use both names for the server client.
+const client = getServerClient();
+const serverClient = client;
+const cleanup = new Cleanup(client);
+
+// Feed groups are app-wide: every group a fence creates gets a uniqueId id.
+const newFeedGroupId = (name: string) => {
+ const id = uniqueId(name);
+ cleanup.feedGroups.push(id);
+ return id;
+};
+
+const createUser = async (name: string) => {
+ const id = uniqueId(name);
+ cleanup.users.push(id);
+ await client.upsertUsers([{ id }]);
+ return id;
+};
+
+/*
+ * Shared data: a reader whose feeds (one per ranked group) follow an author's
+ * user feed with three activities, posted oldest first (a0, a1, a2), so
+ * recency alone would order them a2, a1, a0:
+ *
+ * | activity | popularity | interest_tags |
+ * | -------- | --------------------- | ------------- |
+ * | a0 | 5 (5 reactions) | exercise |
+ * | a1 | 4 (4 reactions) | food |
+ * | a2 | 3 (1 share, 3 points) | travel |
+ */
+let authorId: string;
+let readerId: string;
+const names = new Map();
+
+/** Our activities in feed order, by name. */
+const order = (response: GetOrCreateFeedResponse) =>
+ ours(response).map((a) => names.get(a.id));
+const ours = (response: GetOrCreateFeedResponse) =>
+ response.activities.filter((a) => names.has(a.id));
+const byName = (response: GetOrCreateFeedResponse) =>
+ Object.fromEntries(ours(response).map((a) => [names.get(a.id), a])) as Record<
+ string,
+ ActivityResponse
+ >;
+
+/** The reader's feed in a new group, following the author. */
+const followAuthor = async (groupId: string, userId = readerId) => {
+ cleanup.feeds.push(`${groupId}:${userId}`);
+ const feed = client.feeds.feed(groupId, userId);
+ await retry(() => feed.getOrCreate({ user_id: userId }), {
+ retryIf: isFeedGroupMissing,
+ });
+ await retry(
+ () =>
+ client.feeds.follow({ source: feed.feed, target: `user:${authorId}` }),
+ { retryIf: isFeedGroupMissing },
+ );
+ return feed;
+};
+
+/** Reads the feed until all three activities are there in `expected` order. */
+const waitForOrder = (
+ feed: StreamFeed,
+ expected: string[],
+ request: Omit = {},
+ userId = readerId,
+) =>
+ retry(async () => {
+ const response = await feed.getOrCreate({ user_id: userId, ...request });
+ const got = order(response);
+ if (got.join() !== expected.join()) {
+ throw new Error(`order is ${got.join()}, expected ${expected.join()}`);
+ }
+ return response;
+ });
+
+const createdGroups = (spy: { mock: { results: Array<{ value: unknown }> } }) =>
+ Promise.all(
+ spy.mock.results.map((r) => r.value as Promise),
+ );
+
+describe(DOCS, () => {
+ // Calls into a new group can reach API nodes that don't know it yet
+ // (activities in new groups are added with `addActivityOnce`: a retried
+ // addActivity can add them twice).
+ beforeEach(() =>
+ retryOnFeedGroupMissing(client, [
+ 'getOrCreateFeed',
+ 'follow',
+ 'activityFeedback',
+ ]),
+ );
+ afterEach(() => vi.restoreAllMocks());
+ afterAll(() => cleanup.run());
+
+ beforeAll(async () => {
+ authorId = await createUser('author');
+ readerId = await createUser('reader');
+ const fanId = await createUser('fan');
+ cleanup.feeds.push(`user:${authorId}`, `user:${fanId}`);
+ await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ await client.feeds.feed('user', fanId).getOrCreate({ user_id: fanId });
+ const post = async (name: string, tag: string) => {
+ const { activity } = await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${authorId}`],
+ text: name,
+ interest_tags: [tag],
+ user_id: authorId,
+ });
+ names.set(activity.id, name);
+ return activity.id;
+ };
+ const a0 = await post('a0', 'exercise');
+ const a1 = await post('a1', 'food');
+ const a2 = await post('a2', 'travel');
+ const types = ['like', 'love', 'wow', 'haha', 'sad'];
+ for (const type of types) {
+ await client.feeds.addActivityReaction({
+ activity_id: a0,
+ type,
+ user_id: fanId,
+ });
+ }
+ for (const type of types.slice(0, 4)) {
+ await client.feeds.addActivityReaction({
+ activity_id: a1,
+ type,
+ user_id: fanId,
+ });
+ }
+ // A share: an activity with a2 as parent (in the fan's own feed).
+ await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${fanId}`],
+ parent_id: a2,
+ text: 'shared',
+ user_id: fanId,
+ });
+ await retry(async () => {
+ const response = await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ const got = byName(response);
+ const popularity = ['a0', 'a1', 'a2'].map((n) => got[n]?.popularity);
+ if (popularity.join() !== '5,4,3' || got.a2.share_count !== 1) {
+ throw new Error(`popularity is ${popularity.join()}`);
+ }
+ });
+ });
+
+ it('ranks by interest weights', async () => {
+ const myId = newFeedGroupId('myid');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/13-interest_ranking.md" heading="" tab="Node.js" index=1
+ // COPY: myId="myid"
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'interest',
+ score: 'interest_score * popularity',
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.ranking?.type).toBe('interest');
+
+ const timeline = await followAuthor(myId);
+ // The reader has no stored interests: every interest_score is 0.
+ const plain = await retry(async () => {
+ const r = await timeline.getOrCreate({ user_id: readerId });
+ if (ours(r).length !== 3) throw new Error('activities missing');
+ return r;
+ });
+ expect(ours(plain).map((a) => a.score)).toEqual([0, 0, 0]);
+ // Settle the ranking with the docs' weights before the region.
+ await waitForOrder(timeline, ['a2', 'a1', 'a0'], {
+ interest_weights: { travel: 1, food: 0.5, exercise: -1 },
+ });
+ // The spy retryOnFeedGroupMissing installed.
+ const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
+ getOrCreate.mockClear();
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/13-interest_ranking.md" heading="User interests" tab="Node.js" index=1
+ // COPY: readerId=""
+ await timeline.getOrCreate({
+ interest_weights: {
+ travel: 1,
+ food: 0.5,
+ exercise: -1,
+ },
+ user_id: readerId,
+ });
+ // #endregion snippet
+
+ const response = await (getOrCreate.mock.results[0]
+ .value as Promise);
+ // travel (a2) > food (a1) > exercise (a0, a negative score).
+ expect(order(response)).toEqual(['a2', 'a1', 'a0']);
+ const got = byName(response);
+ expect(got.a1.score).toBeGreaterThan(0);
+ expect(got.a0.score).toBeLessThan(0);
+ });
+
+ it('ranks by preference score', async () => {
+ const myId = newFeedGroupId('myid');
+ const myIdInterest = newFeedGroupId('myid-interest');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/13-interest_ranking.md" heading="Preference score" tab="Node.js" index=1
+ // COPY: myId="myid", myIdInterest="myid_interest"
+ // Using preference_score with expression type
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ score: 'preference_score * popularity',
+ },
+ });
+
+ // Using preference_score with interest type
+ await serverClient.feeds.createFeedGroup({
+ id: myIdInterest,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'interest',
+ score: 'preference_score * interest_score * popularity',
+ },
+ });
+ // #endregion snippet
+
+ const groups = await createdGroups(create);
+ expect(groups.map((g) => g.feed_group.ranking?.type)).toEqual([
+ 'expression',
+ 'interest',
+ ]);
+ // A user of their own: activity feedback changes their preferences.
+ const userId = await createUser('preferences');
+ const feed = await followAuthor(myId, userId);
+ // Without activity feedback preference_score is 0 for every activity.
+ const before = await retry(async () => {
+ const r = await feed.getOrCreate({ user_id: userId });
+ if (ours(r).length !== 3) throw new Error('activities missing');
+ return r;
+ });
+ expect(ours(before).map((a) => a.score)).toEqual([0, 0, 0]);
+ // "Show more" on a2 (travel) moves it to the top.
+ await client.feeds.activityFeedback({
+ activity_id: byName(before).a2.id,
+ show_more: true,
+ user_id: userId,
+ });
+ const after = await waitForOrder(feed, ['a2', 'a1', 'a0'], {}, userId);
+ expect(byName(after).a2.score).toBeGreaterThan(0);
+ expect(byName(after).a1.score).toBe(0);
+ });
+});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/14-read_seen_ranking.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/14-read_seen_ranking.test.ts
new file mode 100644
index 00000000..218f1fda
--- /dev/null
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/14-read_seen_ranking.test.ts
@@ -0,0 +1,251 @@
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import type {
+ ActivityResponse,
+ CreateFeedGroupResponse,
+ GetOrCreateFeedRequest,
+ GetOrCreateFeedResponse,
+ StreamFeed,
+} from '../../../../../../index';
+import { getServerClient } from '../../../../helpers/clients';
+import { Cleanup } from '../../../../helpers/cleanup';
+import {
+ isFeedGroupMissing,
+ retryOnFeedGroupMissing,
+} from '../../../../helpers/feeds';
+import { uniqueId } from '../../../../helpers/ids';
+import { retry } from '../../../../helpers/wait';
+
+const DOCS =
+ 'activity-feeds/v3-latest/_default/03-feed_groups/14-read_seen_ranking.md';
+
+// The docs use both names for the server client.
+const client = getServerClient();
+const serverClient = client;
+const cleanup = new Cleanup(client);
+
+// Feed groups are app-wide: every group a fence creates gets a uniqueId id.
+const newFeedGroupId = (name: string) => {
+ const id = uniqueId(name);
+ cleanup.feedGroups.push(id);
+ return id;
+};
+
+const createUser = async (name: string) => {
+ const id = uniqueId(name);
+ cleanup.users.push(id);
+ await client.upsertUsers([{ id }]);
+ return id;
+};
+
+/*
+ * Shared data: a reader whose feeds (one per ranked group) follow an author's
+ * user feed with three activities, posted oldest first (a0, a1, a2), so
+ * recency alone would order them a2, a1, a0:
+ *
+ * | activity | popularity |
+ * | -------- | --------------------- |
+ * | a0 | 5 (5 reactions) |
+ * | a1 | 4 (4 reactions) |
+ * | a2 | 3 (1 share, 3 points) |
+ */
+let authorId: string;
+let readerId: string;
+const names = new Map();
+
+/** Our activities in feed order, by name. */
+const order = (response: GetOrCreateFeedResponse) =>
+ ours(response).map((a) => names.get(a.id));
+const ours = (response: GetOrCreateFeedResponse) =>
+ response.activities.filter((a) => names.has(a.id));
+const byName = (response: GetOrCreateFeedResponse) =>
+ Object.fromEntries(ours(response).map((a) => [names.get(a.id), a])) as Record<
+ string,
+ ActivityResponse
+ >;
+
+/** The reader's feed in a new group, following the author. */
+const followAuthor = async (groupId: string, userId = readerId) => {
+ cleanup.feeds.push(`${groupId}:${userId}`);
+ const feed = client.feeds.feed(groupId, userId);
+ await retry(() => feed.getOrCreate({ user_id: userId }), {
+ retryIf: isFeedGroupMissing,
+ });
+ await retry(
+ () =>
+ client.feeds.follow({ source: feed.feed, target: `user:${authorId}` }),
+ { retryIf: isFeedGroupMissing },
+ );
+ return feed;
+};
+
+/** Reads the feed until all three activities are there in `expected` order. */
+const waitForOrder = (
+ feed: StreamFeed,
+ expected: string[],
+ request: Omit = {},
+ userId = readerId,
+) =>
+ retry(async () => {
+ const response = await feed.getOrCreate({ user_id: userId, ...request });
+ const got = order(response);
+ if (got.join() !== expected.join()) {
+ throw new Error(`order is ${got.join()}, expected ${expected.join()}`);
+ }
+ return response;
+ });
+
+const createdGroups = (spy: { mock: { results: Array<{ value: unknown }> } }) =>
+ Promise.all(
+ spy.mock.results.map((r) => r.value as Promise),
+ );
+
+describe(DOCS, () => {
+ // Calls into a new group can reach API nodes that don't know it yet
+ // (activities in new groups are added with `addActivityOnce`: a retried
+ // addActivity can add them twice).
+ beforeEach(() =>
+ retryOnFeedGroupMissing(client, [
+ 'getOrCreateFeed',
+ 'follow',
+ 'markActivity',
+ ]),
+ );
+ afterEach(() => vi.restoreAllMocks());
+ afterAll(() => cleanup.run());
+
+ beforeAll(async () => {
+ authorId = await createUser('author');
+ readerId = await createUser('reader');
+ const fanId = await createUser('fan');
+ cleanup.feeds.push(`user:${authorId}`, `user:${fanId}`);
+ await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ await client.feeds.feed('user', fanId).getOrCreate({ user_id: fanId });
+ const post = async (name: string) => {
+ const { activity } = await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${authorId}`],
+ text: name,
+ user_id: authorId,
+ });
+ names.set(activity.id, name);
+ return activity.id;
+ };
+ const a0 = await post('a0');
+ const a1 = await post('a1');
+ const a2 = await post('a2');
+ const types = ['like', 'love', 'wow', 'haha', 'sad'];
+ for (const type of types) {
+ await client.feeds.addActivityReaction({
+ activity_id: a0,
+ type,
+ user_id: fanId,
+ });
+ }
+ for (const type of types.slice(0, 4)) {
+ await client.feeds.addActivityReaction({
+ activity_id: a1,
+ type,
+ user_id: fanId,
+ });
+ }
+ // A share: an activity with a2 as parent (in the fan's own feed).
+ await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${fanId}`],
+ parent_id: a2,
+ text: 'shared',
+ user_id: fanId,
+ });
+ await retry(async () => {
+ const response = await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ const got = byName(response);
+ const popularity = ['a0', 'a1', 'a2'].map((n) => got[n]?.popularity);
+ if (popularity.join() !== '5,4,3' || got.a2.share_count !== 1) {
+ throw new Error(`popularity is ${popularity.join()}`);
+ }
+ });
+ });
+
+ it('ranks read activities lower', async () => {
+ const rankedTimelineId = newFeedGroupId('ranked-timeline');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/14-read_seen_ranking.md" heading="" tab="Node.js" index=1
+ // COPY: rankedTimelineId="ranked_timeline"
+ await serverClient.feeds.createFeedGroup({
+ id: rankedTimelineId,
+ activity_marks: {
+ track_seen: true,
+ track_read: true,
+ },
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ score: 'popularity - (is_read ? 100 : 0)',
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.activity_marks).toMatchObject({ track_read: true });
+ const feed = await followAuthor(rankedTimelineId);
+ const unread = await waitForOrder(feed, ['a0', 'a1', 'a2']);
+ const a0 = byName(unread).a0.id;
+ await feed.markActivity({ mark_read: [a0], user_id: readerId });
+ // The most popular activity drops to the bottom once read.
+ const response = await waitForOrder(feed, ['a1', 'a2', 'a0']);
+ expect(byName(response).a0).toMatchObject({ is_read: true, score: -95 });
+ });
+
+ it('ranks unseen activities first', async () => {
+ const unseenTimelineId = newFeedGroupId('unseen-timeline');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/14-read_seen_ranking.md" heading="" tab="Node.js" index=2
+ // COPY: unseenTimelineId="unseen_timeline"
+ await serverClient.feeds.createFeedGroup({
+ id: unseenTimelineId,
+ activity_marks: {
+ track_seen: true,
+ track_read: true,
+ },
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ score: 'is_seen ? 0 : 100',
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.ranking?.score).toBe('is_seen ? 0 : 100');
+ const feed = await followAuthor(unseenTimelineId);
+ const before = await retry(async () => {
+ const r = await feed.getOrCreate({ user_id: readerId });
+ if (ours(r).length !== 3) throw new Error('activities missing');
+ return r;
+ });
+ expect(ours(before).map((a) => a.score)).toEqual([100, 100, 100]);
+ const a1 = byName(before).a1.id;
+ await feed.markActivity({ mark_seen: [a1], user_id: readerId });
+ const response = await retry(async () => {
+ const r = await feed.getOrCreate({ user_id: readerId });
+ if (order(r)[2] !== 'a1') throw new Error('a1 not last yet');
+ return r;
+ });
+ expect(byName(response).a1).toMatchObject({ is_seen: true, score: 0 });
+ });
+});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/15-external_ranking_data.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/15-external_ranking_data.test.ts
new file mode 100644
index 00000000..10205f41
--- /dev/null
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/03-feed_groups/15-external_ranking_data.test.ts
@@ -0,0 +1,443 @@
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import type {
+ ActivityResponse,
+ CreateFeedGroupResponse,
+ GetOrCreateFeedRequest,
+ GetOrCreateFeedResponse,
+ StreamFeed,
+} from '../../../../../../index';
+import { getServerClient } from '../../../../helpers/clients';
+import { Cleanup } from '../../../../helpers/cleanup';
+import {
+ addActivityOnce,
+ isFeedGroupMissing,
+ retryOnFeedGroupMissing,
+} from '../../../../helpers/feeds';
+import { uniqueId } from '../../../../helpers/ids';
+import { retry } from '../../../../helpers/wait';
+
+const DOCS =
+ 'activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md';
+
+// The docs use both names for the server client.
+const client = getServerClient();
+const serverClient = client;
+const cleanup = new Cleanup(client);
+
+// Feed groups are app-wide: every group a fence creates gets a uniqueId id.
+const newFeedGroupId = (name: string) => {
+ const id = uniqueId(name);
+ cleanup.feedGroups.push(id);
+ return id;
+};
+
+const createUser = async (name: string) => {
+ const id = uniqueId(name);
+ cleanup.users.push(id);
+ await client.upsertUsers([{ id }]);
+ return id;
+};
+
+/*
+ * Shared data: a reader whose feeds (one per ranked group) follow an author's
+ * user feed with three activities, posted oldest first (a0, a1, a2), so
+ * recency alone would order them a2, a1, a0:
+ *
+ * | activity | popularity | custom |
+ * | -------- | --------------------- | ------------------------------------ |
+ * | a0 | 5 (5 reactions) | stock: tesla, stocks: [] |
+ * | a1 | 4 (4 reactions) | stock: nokia, stocks: [microsoft] |
+ * | a2 | 3 (1 share, 3 points) | stock: apple, stocks: [apple, tesla] |
+ */
+let authorId: string;
+let readerId: string;
+const names = new Map();
+
+/** Our activities in feed order, by name. */
+const order = (response: GetOrCreateFeedResponse) =>
+ ours(response).map((a) => names.get(a.id));
+const ours = (response: GetOrCreateFeedResponse) =>
+ response.activities.filter((a) => names.has(a.id));
+const byName = (response: GetOrCreateFeedResponse) =>
+ Object.fromEntries(ours(response).map((a) => [names.get(a.id), a])) as Record<
+ string,
+ ActivityResponse
+ >;
+
+/** The reader's feed in a new group, following the author. */
+const followAuthor = async (groupId: string, userId = readerId) => {
+ cleanup.feeds.push(`${groupId}:${userId}`);
+ const feed = client.feeds.feed(groupId, userId);
+ await retry(() => feed.getOrCreate({ user_id: userId }), {
+ retryIf: isFeedGroupMissing,
+ });
+ await retry(
+ () =>
+ client.feeds.follow({ source: feed.feed, target: `user:${authorId}` }),
+ { retryIf: isFeedGroupMissing },
+ );
+ return feed;
+};
+
+/** Reads the feed until all three activities are there in `expected` order. */
+const waitForOrder = (
+ feed: StreamFeed,
+ expected: string[],
+ request: Omit = {},
+ userId = readerId,
+) =>
+ retry(async () => {
+ const response = await feed.getOrCreate({ user_id: userId, ...request });
+ const got = order(response);
+ if (got.join() !== expected.join()) {
+ throw new Error(`order is ${got.join()}, expected ${expected.join()}`);
+ }
+ return response;
+ });
+
+const createdGroups = (spy: { mock: { results: Array<{ value: unknown }> } }) =>
+ Promise.all(
+ spy.mock.results.map((r) => r.value as Promise),
+ );
+
+describe(DOCS, () => {
+ // Calls into a new group can reach API nodes that don't know it yet
+ // (activities in new groups are added with `addActivityOnce`: a retried
+ // addActivity can add them twice).
+ beforeEach(() =>
+ retryOnFeedGroupMissing(client, ['getOrCreateFeed', 'follow']),
+ );
+ afterEach(() => vi.restoreAllMocks());
+ afterAll(() => cleanup.run());
+
+ beforeAll(async () => {
+ authorId = await createUser('author');
+ readerId = await createUser('reader');
+ const fanId = await createUser('fan');
+ cleanup.feeds.push(`user:${authorId}`, `user:${fanId}`);
+ await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ await client.feeds.feed('user', fanId).getOrCreate({ user_id: fanId });
+ const post = async (name: string, custom: Record) => {
+ const { activity } = await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${authorId}`],
+ text: name,
+ custom,
+ user_id: authorId,
+ });
+ names.set(activity.id, name);
+ return activity.id;
+ };
+ const a0 = await post('a0', { stock: 'tesla', stocks: [] });
+ const a1 = await post('a1', { stock: 'nokia', stocks: ['microsoft'] });
+ const a2 = await post('a2', { stock: 'apple', stocks: ['apple', 'tesla'] });
+ const types = ['like', 'love', 'wow', 'haha', 'sad'];
+ for (const type of types) {
+ await client.feeds.addActivityReaction({
+ activity_id: a0,
+ type,
+ user_id: fanId,
+ });
+ }
+ for (const type of types.slice(0, 4)) {
+ await client.feeds.addActivityReaction({
+ activity_id: a1,
+ type,
+ user_id: fanId,
+ });
+ }
+ // A share: an activity with a2 as parent (in the fan's own feed).
+ await client.feeds.addActivity({
+ type: 'post',
+ feeds: [`user:${fanId}`],
+ parent_id: a2,
+ text: 'shared',
+ user_id: fanId,
+ });
+ await retry(async () => {
+ const response = await client.feeds
+ .feed('user', authorId)
+ .getOrCreate({ user_id: authorId });
+ const got = byName(response);
+ const popularity = ['a0', 'a1', 'a2'].map((n) => got[n]?.popularity);
+ if (popularity.join() !== '5,4,3' || got.a2.share_count !== 1) {
+ throw new Error(`popularity is ${popularity.join()}`);
+ }
+ });
+ });
+
+ it('ranks with external data', async () => {
+ const myId = newFeedGroupId('myid');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Accessing external data in ranking expressions" tab="Node.js" index=1
+ // COPY: myId="myid"
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ ranking: {
+ type: 'expression',
+ score:
+ 'popularity * external.popularity_multiplier + share_count * external.share_multiplier',
+ defaults: {
+ external: {
+ // Required: used when a read doesn't provide these fields
+ popularity_multiplier: 1,
+ share_multiplier: 1,
+ },
+ },
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.ranking?.defaults).toEqual({
+ external: { popularity_multiplier: 1, share_multiplier: 1 },
+ });
+ // No selectors: the group ranks the feed's own activities
+ // (current_feed). b0 has popularity 5, b1 one share (popularity 3).
+ cleanup.feeds.push(`${myId}:${readerId}`);
+ const timeline = client.feeds.feed(myId, readerId);
+ await retry(() => timeline.getOrCreate({ user_id: readerId }), {
+ retryIf: isFeedGroupMissing,
+ });
+ const post = async (name: string) => {
+ const activityId = await addActivityOnce(client, {
+ type: 'post',
+ feeds: [timeline.feed],
+ text: name,
+ user_id: authorId,
+ });
+ names.set(activityId, name);
+ return activityId;
+ };
+ const b0 = await post('b0');
+ const b1 = await post('b1');
+ for (const type of ['like', 'love', 'wow', 'haha', 'sad']) {
+ await client.feeds.addActivityReaction({
+ activity_id: b0,
+ type,
+ user_id: readerId,
+ });
+ }
+ await addActivityOnce(client, {
+ type: 'post',
+ feeds: [timeline.feed],
+ parent_id: b1,
+ text: 'shared',
+ user_id: authorId,
+ });
+ // With the defaults: b0 5, b1 3 + 1 share.
+ const plain = await waitForOrder(timeline, ['b0', 'b1']);
+ expect(byName(plain).b1.score).toBe(4);
+ await waitForOrder(timeline, ['b1', 'b0'], {
+ external_ranking: { popularity_multiplier: 1, share_multiplier: 100 },
+ });
+ // The spy retryOnFeedGroupMissing installed.
+ const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
+ getOrCreate.mockClear();
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Providing external data when reading the feed" tab="Node.js" index=1
+ // COPY: readerId=""
+ await timeline.getOrCreate({
+ external_ranking: {
+ popularity_multiplier: 1, // normal weight for popularity
+ share_multiplier: 100, // very high boost for share_count
+ },
+ user_id: readerId,
+ });
+ // #endregion snippet
+
+ const response = await (getOrCreate.mock.results[0]
+ .value as Promise);
+ // The shared activity jumps to the top: 3 + 1 * 100.
+ expect(order(response)).toEqual(['b1', 'b0']);
+ expect(byName(response).b1.score).toBe(103);
+ });
+
+ it('ranks with map_lookup', async () => {
+ const myId = newFeedGroupId('myid');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Arrays and Maps" tab="Node.js" index=1
+ // COPY: myId="myid"
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ score: 'popularity + map_lookup(stock, external.stock_boosts)',
+ defaults: {
+ stock: '',
+ external: {
+ stock_boosts: {},
+ },
+ },
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.ranking?.defaults).toEqual({
+ stock: '',
+ external: { stock_boosts: {} },
+ });
+ const timeline = await followAuthor(myId);
+ await waitForOrder(timeline, ['a0', 'a1', 'a2']);
+ // The spy retryOnFeedGroupMissing installed.
+ const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
+ getOrCreate.mockClear();
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Arrays and Maps" tab="Node.js" index=2
+ // COPY: readerId=""
+ await timeline.getOrCreate({
+ external_ranking: {
+ stock_boosts: {
+ apple: 0.9,
+ tesla: 0.5,
+ microsoft: 0.3,
+ },
+ },
+ user_id: readerId,
+ });
+ // #endregion snippet
+
+ const response = await (getOrCreate.mock.results[0]
+ .value as Promise);
+ // a0 (tesla) 5 + 0.5, a1 (nokia, not in the map) 4 + 0, a2 (apple) 3 + 0.9.
+ const got = byName(response);
+ expect(got.a0.score).toBeCloseTo(5.5, 5);
+ expect(got.a1.score).toBeCloseTo(4, 5);
+ expect(got.a2.score).toBeCloseTo(3.9, 5);
+ });
+
+ it('ranks with sum_map_lookup', async () => {
+ const myId = newFeedGroupId('myid');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Arrays and Maps" tab="Node.js" index=3
+ // COPY: myId="myid"
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ score: 'popularity + sum_map_lookup(stocks, external.stock_boosts)',
+ defaults: {
+ stocks: [],
+ external: {
+ stock_boosts: {},
+ },
+ },
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.ranking?.defaults).toEqual({
+ stocks: [],
+ external: { stock_boosts: {} },
+ });
+ const timeline = await followAuthor(myId);
+ // a0 (no stocks) 5, a1 (microsoft) 4 + 2, a2 (apple, tesla) 3 + 15.
+ await waitForOrder(timeline, ['a2', 'a1', 'a0'], {
+ external_ranking: {
+ stock_boosts: { apple: 10.0, tesla: 5.0, microsoft: 2.0 },
+ },
+ });
+ // The spy retryOnFeedGroupMissing installed.
+ const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
+ getOrCreate.mockClear();
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Arrays and Maps" tab="Node.js" index=4
+ // COPY: readerId=""
+ await timeline.getOrCreate({
+ external_ranking: {
+ stock_boosts: {
+ apple: 10.0,
+ tesla: 5.0,
+ microsoft: 2.0,
+ },
+ },
+ user_id: readerId,
+ });
+
+ // score = popularity + 10.0 + 5.0 = popularity + 15.0 (for activity with stocks: ['apple', 'tesla'])
+ // #endregion snippet
+
+ const response = await (getOrCreate.mock.results[0]
+ .value as Promise);
+ expect(order(response)).toEqual(['a2', 'a1', 'a0']);
+ const got = byName(response);
+ expect(got.a2.score).toBe(18);
+ expect(got.a1.score).toBe(6);
+ expect(got.a0.score).toBe(5);
+ });
+
+ it('ranks with in_array', async () => {
+ const myId = newFeedGroupId('myid');
+ const create = vi.spyOn(client.feeds, 'createFeedGroup');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Arrays and Maps" tab="Node.js" index=5
+ // COPY: myId="myid"
+ await serverClient.feeds.createFeedGroup({
+ id: myId,
+ activity_selectors: [{ type: 'following' }],
+ ranking: {
+ type: 'expression',
+ score: 'popularity + in_array(stock, external.watchlist) * 10',
+ defaults: {
+ stock: '',
+ external: {
+ watchlist: [],
+ },
+ },
+ },
+ });
+ // #endregion snippet
+
+ const [{ feed_group }] = await createdGroups(create);
+ expect(feed_group.ranking?.defaults).toEqual({
+ stock: '',
+ external: { watchlist: [] },
+ });
+ const timeline = await followAuthor(myId);
+ // a0 (tesla) 5 + 10, a1 (nokia) 4, a2 (apple) 3 + 10.
+ await waitForOrder(timeline, ['a0', 'a2', 'a1'], {
+ external_ranking: { watchlist: ['apple', 'tesla', 'microsoft'] },
+ });
+ // The spy retryOnFeedGroupMissing installed.
+ const getOrCreate = vi.spyOn(client.feeds, 'getOrCreateFeed');
+ getOrCreate.mockClear();
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/03-feed_groups/15-external_ranking_data.md" heading="Arrays and Maps" tab="Node.js" index=6
+ // COPY: readerId=""
+ await timeline.getOrCreate({
+ external_ranking: {
+ watchlist: ['apple', 'tesla', 'microsoft'],
+ },
+ user_id: readerId,
+ });
+
+ // activities with stocks in the watchlist get a +10 boost, others get no boost.
+ // #endregion snippet
+
+ const response = await (getOrCreate.mock.results[0]
+ .value as Promise);
+ expect(order(response)).toEqual(['a0', 'a2', 'a1']);
+ const got = byName(response);
+ expect(got.a0.score).toBe(15);
+ expect(got.a2.score).toBe(13);
+ expect(got.a1.score).toBe(4);
+ });
+});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/04-feeds-and-follows/02-search-feeds.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/04-feeds-and-follows/02-search-feeds.test.ts
index f648642d..fc639c86 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/04-feeds-and-follows/02-search-feeds.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/04-feeds-and-follows/02-search-feeds.test.ts
@@ -95,8 +95,8 @@ describe(DOCS, () => {
expect(firstPage.next).toBeTruthy();
expect(secondPage.feeds).toHaveLength(2);
expect([...fids(firstPage), ...fids(secondPage)].sort()).toEqual(expected);
- const times = [...firstPage.feeds, ...secondPage.feeds].map((f) =>
- f.created_at.getTime(),
+ const times = [...firstPage.feeds, ...secondPage.feeds].map(
+ (f) => f.created_at,
);
expect(times).toEqual([...times].sort((a, b) => b - a));
});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/01-activities.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/01-activities.test.ts
index 81be65dc..8dd19e43 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/01-activities.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/01-activities.test.ts
@@ -290,7 +290,7 @@ describe(DOCS, () => {
expect(response.activity.custom).toEqual({ color: 'red', size: 'L' });
expect(unsetResponse.activity.text).toBe('Japan has over 6,800 islands.');
expect(unsetResponse.activity.custom).toEqual({ size: 'L' });
- expect(unsetResponse.activity.edited_at).toBeInstanceOf(Date);
+ expect(unsetResponse.activity.edited_at).toEqual(expect.any(Number));
expect(log).toHaveBeenCalledTimes(2);
});
@@ -312,7 +312,7 @@ describe(DOCS, () => {
// #endregion snippet
expect(response.activity.text).toBe('Updated text');
- expect(response.activity.edited_at).toBeInstanceOf(Date);
+ expect(response.activity.edited_at).toEqual(expect.any(Number));
expect(log).toHaveBeenCalledWith(response.activity.edited_at);
});
@@ -349,7 +349,7 @@ describe(DOCS, () => {
id,
include_soft_deleted_activities: true,
});
- expect(activity.deleted_at).toBeInstanceOf(Date);
+ expect(activity.deleted_at).toEqual(expect.any(Number));
}
});
@@ -399,7 +399,7 @@ describe(DOCS, () => {
// #endregion snippet
expect(activity.id).toBe(activityId);
- expect(activity.deleted_at).toBeInstanceOf(Date);
+ expect(activity.deleted_at).toEqual(expect.any(Number));
expect(log).toHaveBeenCalledWith(undefined, activity.deleted_at);
});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/02-querying-activities.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/02-querying-activities.test.ts
index edd27940..ae751f73 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/02-querying-activities.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/05-activities/02-querying-activities.test.ts
@@ -95,7 +95,7 @@ describe(DOCS, () => {
expect(response.activities.every((a) => a.type === 'post')).toBe(true);
// Newest first: the activity just created comes first.
expect(response.activities[0].id).toBe(activityId);
- const times = response.activities.map((a) => a.created_at.getTime());
+ const times = response.activities.map((a) => a.created_at);
expect(times).toEqual([...times].sort((a, b) => b - a));
});
@@ -218,7 +218,7 @@ describe(DOCS, () => {
// #endregion snippet
const deleted = response.activities.find((a) => a.id === deletedId);
- expect(deleted?.deleted_at).toBeInstanceOf(Date);
+ expect(deleted?.deleted_at).toEqual(expect.any(Number));
});
it('queries moderated activities', async () => {
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/03-comments.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/03-comments.test.ts
index c8f43d65..18f279d0 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/03-comments.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/03-comments.test.ts
@@ -160,7 +160,7 @@ describe(DOCS, () => {
// #endregion snippet
expect(response.comment.text).toBe('Updated comment');
- expect(response.comment.edited_at).toBeInstanceOf(Date);
+ expect(response.comment.edited_at).toEqual(expect.any(Number));
expect(log).toHaveBeenCalledWith(response.comment.edited_at);
});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/10-moderation.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/10-moderation.test.ts
index 69d3445e..0e4c6555 100644
--- a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/10-moderation.test.ts
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/10-moderation.test.ts
@@ -7,6 +7,7 @@ import {
it,
vi,
} from 'vitest';
+import { nsToMs } from '../../../../../../index';
import type {
QueryModerationConfigsResponse,
QueryReviewQueueResponse,
@@ -57,10 +58,7 @@ describe(DOCS, () => {
payload: { filter_conditions: { user_id: { $eq: userId } } },
});
expect(bans).toHaveLength(1);
- return (
- ((bans[0].expires?.getTime() ?? 0) - bans[0].created_at.getTime()) /
- 60_000
- );
+ return (nsToMs(bans[0].expires ?? 0) - nsToMs(bans[0].created_at)) / 60_000;
};
beforeAll(async () => {
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/13-translation.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/13-translation.test.ts
new file mode 100644
index 00000000..ee2189ea
--- /dev/null
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/13-translation.test.ts
@@ -0,0 +1,166 @@
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../../helpers/clients';
+import { Cleanup } from '../../../../helpers/cleanup';
+import { uniqueId } from '../../../../helpers/ids';
+import { getServerUser } from '../../../../helpers/server';
+
+const DOCS = 'activity-feeds/v3-latest/_default/06-features/13-translation.md';
+
+// Translation works on the test app with auto-translation off.
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+
+ const authorId = uniqueId('author');
+ const johnId = uniqueId('john');
+
+ /** A new user with `language: "en"` and their user feed, registered for cleanup. */
+ const createUser = async (id: string) => {
+ cleanup.users.push(id);
+ cleanup.feeds.push(`user:${id}`);
+ await client.upsertUsers([{ id, language: 'en' }]);
+ await client.feeds.feed('user', id).getOrCreate({ user_id: id });
+ };
+
+ /** A new activity by `userId` in their user feed. */
+ const createActivity = async (userId: string, text: string) =>
+ (
+ await client.feeds.addActivity({
+ feeds: [`user:${userId}`],
+ type: 'post',
+ text,
+ user_id: userId,
+ })
+ ).activity;
+
+ beforeAll(async () => {
+ await createUser(authorId);
+ await createUser(johnId);
+ // John's feed has one English activity with a stored Spanish translation.
+ const activity = await createActivity(johnId, 'Hello world');
+ await client.feeds.translateActivity({ id: activity.id, language: 'es' });
+ });
+
+ afterAll(() => cleanup.run());
+
+ it('translates an activity', async () => {
+ const activityId = (
+ await createActivity(
+ authorId,
+ 'Hello, I would like to know more about your product.',
+ )
+ ).id;
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/13-translation.md" heading="Translate an activity" tab="Node.js" index=1
+ // COPY: activityId="activity_123"
+ const response = await client.feeds.translateActivity({
+ id: activityId,
+ language: 'fr',
+ });
+
+ console.log(response.activity.i18n?.fr_text);
+ // "Bonjour, j'aimerais en savoir plus sur votre produit."
+ // #endregion snippet
+
+ expect(response.activity.id).toBe(activityId);
+ expect(response.activity.i18n?.language).toBe('en');
+ expect(typeof response.activity.i18n?.fr_text).toBe('string');
+ expect(response.activity.i18n?.fr_text).not.toBe(response.activity.text);
+ });
+
+ it('translates a comment', async () => {
+ const activity = await createActivity(authorId, 'setup');
+ const commentId = (
+ await client.feeds.addComment({
+ object_id: activity.id,
+ object_type: 'activity',
+ comment: 'Good morning, how are you?',
+ user_id: authorId,
+ })
+ ).comment.id;
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/13-translation.md" heading="Translate a comment" tab="Node.js" index=1
+ // COPY: commentId="comment_456"
+ const response = await client.feeds.translateComment({
+ id: commentId,
+ language: 'es',
+ });
+
+ console.log(response.comment.i18n?.es_text);
+ // #endregion snippet
+
+ expect(response.comment.id).toBe(commentId);
+ expect(response.comment.i18n?.language).toBe('en');
+ expect(typeof response.comment.i18n?.es_text).toBe('string');
+ expect(response.comment.i18n?.es_text).not.toBe(response.comment.text);
+ });
+
+ it('reads a feed with the default projection', async () => {
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/13-translation.md" heading="Default projection (`translate_text=false`)" tab="Node.js" index=1
+ // COPY: johnId="john"
+ const feed = client.feeds.feed('user', johnId);
+ const response = await feed.getOrCreate({
+ user_id: johnId,
+ language: 'es',
+ });
+
+ // text is still the original English
+ console.log(response.activities[0].text);
+ // "Hello world"
+
+ // projected i18n includes Spanish when translated
+ console.log(response.activities[0].i18n?.es_text);
+ // "Hola mundo"
+ // #endregion snippet
+
+ expect(response.activities).toHaveLength(1);
+ const [activity] = response.activities;
+ expect(activity.text).toBe('Hello world');
+ expect(activity.i18n?.language).toBe('en');
+ expect(typeof activity.i18n?.es_text).toBe('string');
+ expect(activity.i18n?.es_text).not.toBe('Hello world');
+ });
+
+ it('reads a feed with the translated text', async () => {
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/13-translation.md" heading="Substitute `text` (`translate_text=true`)" tab="Node.js" index=1
+ // COPY: johnId="john"
+ const feed = client.feeds.feed('user', johnId);
+ const response = await feed.getOrCreate({
+ user_id: johnId,
+ language: 'es',
+ translate_text: true,
+ });
+
+ console.log(response.activities[0].text);
+ // "Hola mundo"
+
+ console.log(response.activities[0].i18n?.language);
+ // "en"
+ // #endregion snippet
+
+ expect(response.activities).toHaveLength(1);
+ const [activity] = response.activities;
+ expect(activity.i18n?.language).toBe('en');
+ expect(activity.i18n?.en_text).toBe('Hello world');
+ expect(activity.text).not.toBe('Hello world');
+ expect(activity.text).toBe(activity.i18n?.es_text);
+ });
+
+ it('sets the user language', async () => {
+ const user123Id = uniqueId('user');
+ cleanup.users.push(user123Id);
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/13-translation.md" heading="Set user language" tab="Node.js" index=1
+ // COPY: user123Id="user_123"
+ await client.updateUsers({
+ users: {
+ user_123: { id: user123Id, language: 'en', name: 'Jane' },
+ },
+ });
+ // #endregion snippet
+
+ const user = await getServerUser(client, user123Id);
+ expect(user.language).toBe('en');
+ expect(user.name).toBe('Jane');
+ });
+});
diff --git a/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/14-data-retention.test.ts b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/14-data-retention.test.ts
new file mode 100644
index 00000000..43a499b8
--- /dev/null
+++ b/__tests__/docs-snippets/pages/activity-feeds/v3-latest/06-features/14-data-retention.test.ts
@@ -0,0 +1,226 @@
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import type {
+ FeedsRetentionPolicy,
+ SetFeedsRetentionPolicyResponse,
+ StreamClient,
+} from '../../../../../../index';
+import { getServerClient } from '../../../../helpers/clients';
+import { Cleanup } from '../../../../helpers/cleanup';
+import { uniqueId } from '../../../../helpers/ids';
+import { retry } from '../../../../helpers/wait';
+
+const DOCS =
+ 'activity-feeds/v3-latest/_default/06-features/14-data-retention.md';
+
+/** The app's feeds retention policies, without server-managed fields. */
+const policyConfigs = (policies: FeedsRetentionPolicy[]) =>
+ policies
+ .map((p) => ({ policy: p.policy, config: p.config }))
+ .sort((a, b) => a.policy.localeCompare(b.policy));
+
+/**
+ * The `enabled` flag of `feedsGetRetentionPolicy` is eventually consistent (reads can
+ * return the old value for ~6s after a change, while `policies` is up to date at once):
+ * waits until 5 reads in a row return `expected`.
+ */
+const waitForRetentionEnabled = async (
+ client: StreamClient,
+ expected: boolean,
+) => {
+ let streak = 0;
+ await retry(
+ async () => {
+ const { enabled } = await client.feeds.feedsGetRetentionPolicy();
+ streak = enabled === expected ? streak + 1 : 0;
+ if (streak < 5) {
+ throw new Error(`retention enabled is ${enabled}, want ${expected}`);
+ }
+ },
+ { interval: 500, timeout: 30000 },
+ );
+};
+
+/**
+ * Puts back the original feeds retention policies (and the enabled flag) and deletes
+ * every policy the file added. Feeds retention policies are app-level and not part of
+ * the leak / drift checks.
+ */
+const restoreFeedsRetention = async (
+ client: StreamClient,
+ original: { enabled: boolean; policies: FeedsRetentionPolicy[] },
+) => {
+ const { policies: current } = await client.feeds.feedsGetRetentionPolicy();
+ for (const p of current) {
+ if (!original.policies.some((o) => o.policy === p.policy)) {
+ await client.feeds.feedsDeleteRetentionPolicy({ policy: p.policy });
+ }
+ }
+ for (const p of original.policies) {
+ await client.feeds.feedsSetRetentionPolicy({
+ policy: p.policy,
+ max_age_hours: p.config.max_age_hours,
+ feed_groups: p.config.feed_groups,
+ enabled: original.enabled,
+ });
+ }
+};
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+
+ // The docs' own policy name ("stories"); the feed group stays the built-in `stories`.
+ const storiesPolicy = uniqueId('stories');
+
+ /** The `enabled` flag of each `feedsSetRetentionPolicy` response so far. */
+ const setResponses = async (spy: {
+ mock: { results: Array<{ value: unknown }> };
+ }) =>
+ (
+ await Promise.all(
+ spy.mock.results.map(
+ (r) => r.value as Promise,
+ ),
+ )
+ ).map((r) => r.enabled);
+
+ const getPolicy = async (name: string) =>
+ (await client.feeds.feedsGetRetentionPolicy()).policies.find(
+ (p) => p.policy === name,
+ );
+
+ beforeAll(async () => {
+ const original = await client.feeds.feedsGetRetentionPolicy();
+ cleanup.add(async () => {
+ await restoreFeedsRetention(client, original);
+ // Check that the app is back to where it started.
+ const { policies } = await client.feeds.feedsGetRetentionPolicy();
+ expect(policyConfigs(policies)).toEqual(policyConfigs(original.policies));
+ await waitForRetentionEnabled(client, original.enabled);
+ });
+ });
+
+ afterEach(() => vi.restoreAllMocks());
+ afterAll(() => cleanup.run());
+
+ it('sets the app-wide retention policy', async () => {
+ const set = vi.spyOn(client.feeds, 'feedsSetRetentionPolicy');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/14-data-retention.md" heading="Setting a retention policy" tab="Node.js" index=1
+ // Delete all activities older than one year (8,760 hours)
+ await client.feeds.feedsSetRetentionPolicy({
+ policy: 'old-activities',
+ max_age_hours: 8760,
+ });
+ // #endregion snippet
+
+ const policy = await getPolicy('old-activities');
+ expect(policy?.config.max_age_hours).toBe(8760);
+ expect(policy?.config.feed_groups ?? []).toEqual([]);
+ // Setting a policy turns retention on.
+ expect(await setResponses(set)).toEqual([true]);
+ await waitForRetentionEnabled(client, true);
+ });
+
+ it('sets a retention policy for some feed groups', async () => {
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/14-data-retention.md" heading="Setting a retention policy" tab="Node.js" index=2
+ // COPY: storiesPolicy="stories"
+ // Delete activities posted to stories feeds after 30 days (720 hours)
+ await client.feeds.feedsSetRetentionPolicy({
+ policy: storiesPolicy,
+ max_age_hours: 720,
+ feed_groups: ['stories'],
+ });
+ // #endregion snippet
+
+ const policy = await getPolicy(storiesPolicy);
+ expect(policy?.config).toEqual({
+ max_age_hours: 720,
+ feed_groups: ['stories'],
+ });
+ });
+
+ it('pauses retention', async () => {
+ await waitForRetentionEnabled(client, true);
+ const set = vi.spyOn(client.feeds, 'feedsSetRetentionPolicy');
+
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/14-data-retention.md" heading="Pausing retention" tab="Node.js" index=1
+ await client.feeds.feedsSetRetentionPolicy({
+ policy: 'old-activities',
+ max_age_hours: 8760,
+ enabled: false,
+ });
+ // #endregion snippet
+
+ expect(await setResponses(set)).toEqual([false]);
+ await waitForRetentionEnabled(client, false);
+ const response = await client.feeds.feedsGetRetentionPolicy();
+ // Pausing keeps the policies.
+ expect(response.policies.map((p) => p.policy)).toEqual(
+ expect.arrayContaining(['old-activities', storiesPolicy]),
+ );
+ });
+
+ it('retrieves retention policies', async () => {
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/14-data-retention.md" heading="Retrieving retention policies" tab="Node.js" index=1
+ const response = await client.feeds.feedsGetRetentionPolicy();
+ console.log(response.enabled);
+ console.log(response.policies);
+ // [
+ // { policy: "old-activities", config: { max_age_hours: 8760 }, enabled_at: "..." },
+ // { policy: "stories", config: { max_age_hours: 720, feed_groups: ["stories"] }, enabled_at: "..." }
+ // ]
+ // #endregion snippet
+
+ expect(response.enabled).toBe(false);
+ const stories = response.policies.find((p) => p.policy === storiesPolicy);
+ expect(stories?.config).toEqual({
+ max_age_hours: 720,
+ feed_groups: ['stories'],
+ });
+ expect(typeof stories?.enabled_at).toBe('number');
+ });
+
+ it('deletes a retention policy', async () => {
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/14-data-retention.md" heading="Deleting a retention policy" tab="Node.js" index=1
+ // COPY: storiesPolicy="stories"
+ await client.feeds.feedsDeleteRetentionPolicy({ policy: storiesPolicy });
+ // #endregion snippet
+
+ expect(await getPolicy(storiesPolicy)).toBeUndefined();
+ expect(await getPolicy('old-activities')).toBeDefined();
+ });
+
+ it('queries the cleanup run history', async () => {
+ // #region snippet docs="activity-feeds/v3-latest/_default/06-features/14-data-retention.md" heading="Viewing cleanup run history" tab="Node.js" index=1
+ // COPY: storiesPolicy="stories"
+ const response = await client.feeds.feedsGetRetentionPolicyRuns({
+ filter_conditions: { policy: { $eq: storiesPolicy } },
+ sort: [{ field: 'date', direction: -1 }],
+ limit: 10,
+ });
+
+ console.log(response.runs);
+ // [
+ // {
+ // app_pk: 12345,
+ // policy: "stories",
+ // date: "2026-10-20",
+ // stats: { activities_deleted: 15420 }
+ // }
+ // ]
+ // #endregion snippet
+
+ // A new policy's first cleanup runs after its 24-hour grace period.
+ expect(Array.isArray(response.runs)).toBe(true);
+ for (const run of response.runs) expect(run.policy).toBe(storiesPolicy);
+ });
+});
diff --git a/__tests__/docs-snippets/pages/chat/03-channels/04-query_members.test.ts b/__tests__/docs-snippets/pages/chat/03-channels/04-query_members.test.ts
index 11a4eae7..b0afe2d6 100644
--- a/__tests__/docs-snippets/pages/chat/03-channels/04-query_members.test.ts
+++ b/__tests__/docs-snippets/pages/chat/03-channels/04-query_members.test.ts
@@ -1,4 +1,5 @@
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
+import { nsToDate } from '../../../../../index';
import type { MembersResponse } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
@@ -107,7 +108,7 @@ describe(DOCS, () => {
filter_conditions: {},
sort: [{ field: 'created_at', direction: -1 }],
limit: 100,
- created_at_before: lastByCreatedAt.created_at,
+ created_at_before: nsToDate(lastByCreatedAt.created_at),
},
});
diff --git a/__tests__/docs-snippets/pages/chat/03-channels/06-channel_members.test.ts b/__tests__/docs-snippets/pages/chat/03-channels/06-channel_members.test.ts
index b370a6a5..35ef6c60 100644
--- a/__tests__/docs-snippets/pages/chat/03-channels/06-channel_members.test.ts
+++ b/__tests__/docs-snippets/pages/chat/03-channels/06-channel_members.test.ts
@@ -343,4 +343,33 @@ describe(DOCS, () => {
expect(member.channel_role).toBe('channel_moderator');
expect(member.custom).toEqual({ key1: 'new value 1', key2: 'new value 2' });
});
+
+ it('sets app-wide and channel badges', async () => {
+ const channelId = await newChannel([janeId]);
+ const cid = `messaging:${channelId}`;
+ const channel = client.chat.channel('messaging', channelId);
+
+ // #region snippet docs="chat/_default/03-channels/06-channel_members.md" heading="User Badges" tab="Node.js" index=1
+ // COPY: janeId="jane"
+ // App-wide badge, returned with the user (for example on message.user)
+ await client.updateUsersPartial({
+ users: [{ id: janeId, set: { badges: ['verified'] } }],
+ });
+
+ // Channel badge, returned with this channel's members
+ await channel.updateMemberPartial({
+ user_id: janeId,
+ set: { badges: ['host'] },
+ });
+ // #endregion snippet
+
+ const message = await sendServerMessage(client, cid, {
+ text: 'hi',
+ user_id: janeId,
+ });
+ expect(message.user?.custom).toMatchObject({ badges: ['verified'] });
+ const member = await getServerMember(client, cid, janeId);
+ expect(member.custom).toEqual({ badges: ['host'] });
+ expect(member.user?.custom).toMatchObject({ badges: ['verified'] });
+ });
});
diff --git a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/10-muting.test.ts b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/10-muting.test.ts
index dd09d2a3..fb6e6d5d 100644
--- a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/10-muting.test.ts
+++ b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/10-muting.test.ts
@@ -1,4 +1,5 @@
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
+import { nsToMs } from '../../../../../../index';
import type {
MuteChannelResponse,
QueryChannelsResponse,
@@ -82,14 +83,12 @@ describe(DOCS, () => {
const now = Date.now();
const twoWeeksExpires = muteOf(twoWeeks)?.expires;
expect(twoWeeksExpires).toBeDefined();
- expect(new Date(twoWeeksExpires ?? 0).getTime()).toBeGreaterThan(
+ expect(nsToMs(twoWeeksExpires ?? 0)).toBeGreaterThan(
now + 13 * 24 * 60 * 60 * 1000,
);
const tenSecondsExpires = muteOf(tenSeconds)?.expires;
expect(tenSecondsExpires).toBeDefined();
- expect(new Date(tenSecondsExpires ?? 0).getTime()).toBeLessThan(
- now + 60 * 1000,
- );
+ expect(nsToMs(tenSecondsExpires ?? 0)).toBeLessThan(now + 60 * 1000);
expect(await mutedCids()).toEqual([cid]);
});
diff --git a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/13-deleting.test.ts b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/13-deleting.test.ts
index 2166fa5f..e39093f9 100644
--- a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/13-deleting.test.ts
+++ b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/13-deleting.test.ts
@@ -102,7 +102,7 @@ describe(DOCS, () => {
deleteSpy.mockRestore();
expect(soft.channel?.cid).toBe(cid);
- expect(soft.channel?.deleted_at).toBeInstanceOf(Date);
+ expect(soft.channel?.deleted_at).toEqual(expect.any(Number));
expect(hard.channel?.cid).toBe(otherCid);
expect(await countChannels([cid, otherCid])).toBe(0);
});
diff --git a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/14-freezing.test.ts b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/14-freezing.test.ts
index 5fd3ffef..18e77f88 100644
--- a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/14-freezing.test.ts
+++ b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/14-freezing.test.ts
@@ -121,13 +121,8 @@ describe(DOCS, () => {
const adminGrants = [...channelType.grants.admin, 'use-frozen-channel'];
- // automod, automod_behavior and max_message_length are required on update and
- // are not preserved when omitted, so read them back from the current type.
await client.chat.updateChannelType({
name: 'messaging',
- automod: channelType.automod,
- automod_behavior: channelType.automod_behavior,
- max_message_length: channelType.max_message_length,
grants: { admin: adminGrants },
});
// #endregion snippet
diff --git a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/15-truncating.test.ts b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/15-truncating.test.ts
index 6bc5bff4..2ea92705 100644
--- a/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/15-truncating.test.ts
+++ b/__tests__/docs-snippets/pages/chat/03-channels/07-channel_management/15-truncating.test.ts
@@ -7,6 +7,7 @@ import {
it,
vi,
} from 'vitest';
+import { nsToMs } from '../../../../../../index';
import type { TruncateChannelResponse } from '../../../../../../index';
import { getServerClient } from '../../../../helpers/clients';
import { Cleanup } from '../../../../helpers/cleanup';
@@ -136,11 +137,9 @@ describe(DOCS, () => {
expect(visibleAfter[2]).toEqual([]);
// `truncated_at` an hour ago keeps the messages sent since then.
const truncatedAt = responses[3].channel?.truncated_at;
- expect(truncatedAt).toBeInstanceOf(Date);
+ expect(truncatedAt).toEqual(expect.any(Number));
if (truncatedAt) {
- expect(Date.now() - truncatedAt.getTime()).toBeGreaterThan(
- 59 * 60 * 1000,
- );
+ expect(Date.now() - nsToMs(truncatedAt)).toBeGreaterThan(59 * 60 * 1000);
}
expect(visibleAfter[3]).toEqual(['recent', 'before-4']);
});
diff --git a/__tests__/docs-snippets/pages/chat/04-messages/05-pinned_messages.test.ts b/__tests__/docs-snippets/pages/chat/04-messages/05-pinned_messages.test.ts
index 5f95840c..7fcb6844 100644
--- a/__tests__/docs-snippets/pages/chat/04-messages/05-pinned_messages.test.ts
+++ b/__tests__/docs-snippets/pages/chat/04-messages/05-pinned_messages.test.ts
@@ -1,4 +1,5 @@
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
+import { convertTimestampToDate, nsToMs } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
@@ -83,10 +84,12 @@ describe(DOCS, () => {
expect(message.pinned).toBe(true);
expect(message.pinned_by?.id).toBe(john);
- expect(message.pin_expires?.toISOString()).toBe('2077-01-01T00:00:00.000Z');
+ expect(convertTimestampToDate(message.pin_expires)?.toISOString()).toBe(
+ '2077-01-01T00:00:00.000Z',
+ );
expect(timed.message?.pinned).toBe(true);
- const timedExpiry = timed.message?.pin_expires?.getTime() ?? 0;
+ const timedExpiry = nsToMs(timed.message?.pin_expires ?? 0);
expect(timedExpiry).toBeGreaterThanOrEqual(before + 110 * 1000);
expect(timedExpiry).toBeLessThan(Date.now() + 130 * 1000);
diff --git a/__tests__/docs-snippets/pages/chat/04-messages/08-unread_reminders.test.ts b/__tests__/docs-snippets/pages/chat/04-messages/08-unread_reminders.test.ts
index 66f3cf73..0da4a8b4 100644
--- a/__tests__/docs-snippets/pages/chat/04-messages/08-unread_reminders.test.ts
+++ b/__tests__/docs-snippets/pages/chat/04-messages/08-unread_reminders.test.ts
@@ -41,16 +41,9 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/04-messages/08-unread_reminders.md" heading="Enabling Unread Reminders" tab="Node.js" index=1
// COPY: channelType="messaging"
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
-
// Enable reminders for the messaging channel type
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
reminders: true,
});
@@ -64,8 +57,8 @@ describe(DOCS, () => {
return response;
});
expect(updated.reminders).toBe(true);
- expect(updated.automod).toBe(current.automod);
- expect(updated.max_message_length).toBe(current.max_message_length);
+ expect(updated.automod).toBe(before.automod);
+ expect(updated.max_message_length).toBe(before.max_message_length);
await waitForAppSetting(client, 'reminders_interval', 3600);
const { app } = await client.getApp();
diff --git a/__tests__/docs-snippets/pages/chat/04-messages/09-message_reminders.test.ts b/__tests__/docs-snippets/pages/chat/04-messages/09-message_reminders.test.ts
index 22be9d31..a16798ca 100644
--- a/__tests__/docs-snippets/pages/chat/04-messages/09-message_reminders.test.ts
+++ b/__tests__/docs-snippets/pages/chat/04-messages/09-message_reminders.test.ts
@@ -1,4 +1,5 @@
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
+import { nsToMs } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
@@ -104,22 +105,16 @@ describe(DOCS, () => {
});
// Enabling it for a channel type.
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
const update = await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
user_message_reminders: true,
});
// #endregion snippet
await waitForChannelTypePropagation();
expect(update.user_message_reminders).toBe(true);
- expect(update.automod).toBe(current.automod);
- expect(update.max_message_length).toBe(current.max_message_length);
+ expect(update.automod).toBe(before.automod);
+ expect(update.max_message_length).toBe(before.max_message_length);
const updated = await client.chat.getChannelType({ name: channelType });
expect(updated.user_message_reminders).toBe(true);
@@ -168,8 +163,10 @@ describe(DOCS, () => {
[messageId, anotherMessageId].sort(),
);
const timed = byMessage.get(messageId)?.remind_at;
- expect(timed).toBeInstanceOf(Date);
- expect(timed?.getTime()).toBeGreaterThanOrEqual(startedAt + 3600000 - 1000);
+ expect(timed).toEqual(expect.any(Number));
+ expect(nsToMs(timed ?? 0)).toBeGreaterThanOrEqual(
+ startedAt + 3600000 - 1000,
+ );
expect(byMessage.get(anotherMessageId)?.remind_at ?? null).toBeNull();
});
@@ -203,8 +200,8 @@ describe(DOCS, () => {
// #endregion snippet
const newDueDate = updated.reminder.remind_at;
- expect(newDueDate).toBeInstanceOf(Date);
- expect(newDueDate?.getTime()).toBeGreaterThanOrEqual(
+ expect(newDueDate).toEqual(expect.any(Number));
+ expect(nsToMs(newDueDate ?? 0)).toBeGreaterThanOrEqual(
startedAt + 7200000 - 1000,
);
expect(log.mock.calls[0]).toEqual([newDueDate]);
diff --git a/__tests__/docs-snippets/pages/chat/04-messages/10-message_receipts.test.ts b/__tests__/docs-snippets/pages/chat/04-messages/10-message_receipts.test.ts
index e9e68f2f..a7b6702c 100644
--- a/__tests__/docs-snippets/pages/chat/04-messages/10-message_receipts.test.ts
+++ b/__tests__/docs-snippets/pages/chat/04-messages/10-message_receipts.test.ts
@@ -46,16 +46,8 @@ describe(DOCS, () => {
});
// When updating an existing channel type.
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({
- name: targetChannelType,
- });
await client.chat.updateChannelType({
name: targetChannelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
delivery_events: true,
});
// #endregion snippet
@@ -67,14 +59,13 @@ describe(DOCS, () => {
),
);
expect(created.delivery_events).toBe(true);
- expect(current.delivery_events).toBe(true);
- expect(current.automod).toBe('disabled');
const [updated] = await Promise.all(
updateSpy.mock.results.map(
(r) => r.value as ReturnType,
),
);
expect(updated.delivery_events).toBe(true);
+ expect(updated.automod).toBe('disabled');
const after = await client.chat.getChannelType({ name: targetChannelType });
expect(after.delivery_events).toBe(true);
expect(after.max_message_length).toBe(5000);
@@ -120,16 +111,8 @@ describe(DOCS, () => {
});
// When updating an existing channel type.
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({
- name: targetChannelType,
- });
await client.chat.updateChannelType({
name: targetChannelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
read_events: true,
});
// #endregion snippet
@@ -141,13 +124,13 @@ describe(DOCS, () => {
),
);
expect(created.read_events).toBe(true);
- expect(current.read_events).toBe(true);
const [updated] = await Promise.all(
updateSpy.mock.results.map(
(r) => r.value as ReturnType,
),
);
expect(updated.read_events).toBe(true);
+ expect(updated.automod).toBe('disabled');
const after = await client.chat.getChannelType({ name: targetChannelType });
expect(after.read_events).toBe(true);
expect(after.max_message_length).toBe(5000);
diff --git a/__tests__/docs-snippets/pages/chat/05-features/03-unread.test.ts b/__tests__/docs-snippets/pages/chat/05-features/03-unread.test.ts
index f4343064..cbb6d0d2 100644
--- a/__tests__/docs-snippets/pages/chat/05-features/03-unread.test.ts
+++ b/__tests__/docs-snippets/pages/chat/05-features/03-unread.test.ts
@@ -228,7 +228,7 @@ describe(DOCS, () => {
);
expect(unreadByUser).toEqual({ [sender]: 0, [userID]: 1, [userID2]: 0 });
expect(log).toHaveBeenCalledTimes(3);
- expect(log).toHaveBeenCalledWith(userID, expect.any(Date), 1);
+ expect(log).toHaveBeenCalledWith(userID, expect.any(Number), 1);
});
it('reads the unread messages per channel', async () => {
diff --git a/__tests__/docs-snippets/pages/chat/05-features/06-campaign_api.test.ts b/__tests__/docs-snippets/pages/chat/05-features/06-campaign_api.test.ts
index 022d11c4..dbd3c513 100644
--- a/__tests__/docs-snippets/pages/chat/05-features/06-campaign_api.test.ts
+++ b/__tests__/docs-snippets/pages/chat/05-features/06-campaign_api.test.ts
@@ -7,6 +7,7 @@ import {
it,
vi,
} from 'vitest';
+import { nsToMs } from '../../../../../index';
import { StreamError } from '../../../../../index';
import type {
CampaignResponse,
@@ -137,7 +138,7 @@ describe(DOCS, () => {
expect(['scheduled', 'in_progress', 'completed']).toContain(
campaign?.status,
);
- expect(campaign?.scheduled_for?.getTime() ?? 0).toBeLessThan(
+ expect(nsToMs(campaign?.scheduled_for ?? 0)).toBeLessThan(
Date.now() + 60000,
);
};
@@ -306,8 +307,8 @@ describe(DOCS, () => {
id: anotherCampaignId,
});
expect(scheduled?.status).toBe('scheduled');
- expect(scheduled?.scheduled_for).toBeInstanceOf(Date);
- expect(scheduled?.stop_at).toBeInstanceOf(Date);
+ expect(scheduled?.scheduled_for).toEqual(expect.any(Number));
+ expect(scheduled?.stop_at).toEqual(expect.any(Number));
});
it('updates, starts, stops, deletes and queries a campaign', async () => {
@@ -837,7 +838,7 @@ describe(DOCS, () => {
expect(typeof stats?.progress).toBe('number');
expect(typeof stats?.stats_messages_sent).toBe('number');
expect(typeof stats?.stats_users_sent).toBe('number');
- expect(stats?.stats_started_at).toBeInstanceOf(Date);
+ expect(stats?.stats_started_at).toEqual(expect.any(Number));
});
});
diff --git a/__tests__/docs-snippets/pages/chat/05-features/08-location_sharing.test.ts b/__tests__/docs-snippets/pages/chat/05-features/08-location_sharing.test.ts
index f72f065e..25668756 100644
--- a/__tests__/docs-snippets/pages/chat/05-features/08-location_sharing.test.ts
+++ b/__tests__/docs-snippets/pages/chat/05-features/08-location_sharing.test.ts
@@ -8,6 +8,7 @@ import {
vi,
type MockInstance,
} from 'vitest';
+import { nsToMs } from '../../../../../index';
import type { StreamClient } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
@@ -145,15 +146,8 @@ describe(DOCS, () => {
});
// Enabling it for a channel type.
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
shared_locations: true,
});
// #endregion snippet
@@ -246,7 +240,7 @@ describe(DOCS, () => {
longitude: 10,
message_id: message.id,
});
- expect(message.shared_location?.end_at?.getTime()).toBeGreaterThan(
+ expect(nsToMs(message.shared_location?.end_at ?? 0)).toBeGreaterThan(
Date.now(),
);
expect(await activeLiveLocationIds()).toContain(message.id);
@@ -274,10 +268,8 @@ describe(DOCS, () => {
StreamClient['updateLiveLocation']
>);
expect(stopped.message_id).toBe(messageID);
- expect(stopped.end_at?.getTime()).toBeLessThanOrEqual(Date.now());
- expect((await storedLocation(messageID))?.end_at?.getTime()).toBe(
- stopped.end_at?.getTime(),
- );
+ expect(nsToMs(stopped.end_at ?? 0)).toBeLessThanOrEqual(Date.now());
+ expect((await storedLocation(messageID))?.end_at).toBe(stopped.end_at);
expect(await activeLiveLocationIds()).not.toContain(messageID);
});
diff --git a/__tests__/docs-snippets/pages/chat/05-features/10-advanced/15-pending_messages.test.ts b/__tests__/docs-snippets/pages/chat/05-features/10-advanced/15-pending_messages.test.ts
index 62ee1e07..059b075d 100644
--- a/__tests__/docs-snippets/pages/chat/05-features/10-advanced/15-pending_messages.test.ts
+++ b/__tests__/docs-snippets/pages/chat/05-features/10-advanced/15-pending_messages.test.ts
@@ -83,15 +83,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/05-features/10-advanced/15-pending_messages.md" heading="Sending Pending Messages" tab="Node.js" index=1
// COPY: channelType="messaging"
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
-
const response = await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
mark_messages_pending: true,
});
// #endregion snippet
diff --git a/__tests__/docs-snippets/pages/chat/05-features/10-advanced/16-dynamic_partitioning.test.ts b/__tests__/docs-snippets/pages/chat/05-features/10-advanced/16-dynamic_partitioning.test.ts
index ca72376d..614b3299 100644
--- a/__tests__/docs-snippets/pages/chat/05-features/10-advanced/16-dynamic_partitioning.test.ts
+++ b/__tests__/docs-snippets/pages/chat/05-features/10-advanced/16-dynamic_partitioning.test.ts
@@ -103,19 +103,13 @@ describe(DOCS, () => {
});
it('updates the partition size', async () => {
+ const before = await client.chat.getChannelType({ name: channelType });
const updateSpy = vi.spyOn(client.chat, 'updateChannelType');
// #region snippet docs="chat/_default/05-features/10-advanced/16-dynamic_partitioning.md" heading="Updating dynamic partitioning" tab="Node.js" index=1
// COPY: channelType="example"
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
partition_size: 200,
});
// #endregion snippet
@@ -123,8 +117,8 @@ describe(DOCS, () => {
const updated = await (updateSpy.mock.results[0]
.value as Promise);
expect(updated.partition_size).toBe(200);
- expect(updated.automod).toBe(current.automod);
- expect(updated.max_message_length).toBe(current.max_message_length);
+ expect(updated.automod).toBe(before.automod);
+ expect(updated.max_message_length).toBe(before.max_message_length);
await waitForChannelTypePropagation();
const type = await waitForChannelType((r) => r.partition_size === 200);
@@ -148,13 +142,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/05-features/10-advanced/16-dynamic_partitioning.md" heading="Disabling dynamic partitioning" tab="Node.js" index=1
// COPY: channelType="example"
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
partition_size: 0,
});
// #endregion snippet
@@ -188,13 +177,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/05-features/10-advanced/16-dynamic_partitioning.md" heading="Partition TTL" tab="Node.js" index=1
// COPY: channelType="example"
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
partition_ttl: '24h',
});
// #endregion snippet
diff --git a/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/01-channel_types.test.ts b/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/01-channel_types.test.ts
index 9bd9281a..673da120 100644
--- a/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/01-channel_types.test.ts
+++ b/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/01-channel_types.test.ts
@@ -94,15 +94,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/06-app_and_channel_settings/01-channel_types.md" heading="Restricting guest access" tab="Node.js" index=1
// COPY: agentType="agent"
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: agentType });
-
await client.chat.updateChannelType({
name: agentType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
grants: {
guest: [], // guests can no longer create agent channels or act as owners
},
@@ -146,8 +139,6 @@ describe(DOCS, () => {
// Update an existing channel type
await client.chat.updateChannelType({
name: myChannelType,
- automod: 'disabled',
- automod_behavior: 'flag',
max_message_length: 1000,
reactions: false,
});
@@ -214,15 +205,11 @@ describe(DOCS, () => {
});
it('edits commands and replies', async () => {
+ const before = await client.chat.getChannelType({ name: publicType });
// #region snippet docs="chat/_default/06-app_and_channel_settings/01-channel_types.md" heading="Edit a Channel Type" tab="Node.js" index=1
// COPY: publicType="public"
- const current = await client.chat.getChannelType({ name: publicType });
-
const update = await client.chat.updateChannelType({
name: publicType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
replies: false,
commands: ['all'],
});
@@ -233,7 +220,7 @@ describe(DOCS, () => {
expect(update.commands).toEqual(
expect.arrayContaining(['ban', 'giphy', 'mute', 'unban', 'unmute']),
);
- expect(update.max_message_length).toBe(current.max_message_length);
+ expect(update.max_message_length).toBe(before.max_message_length);
await waitForChannelTypePropagation();
const type = await waitForChannelType(publicType, (r) => !r.replies);
@@ -245,13 +232,8 @@ describe(DOCS, () => {
it('updates feature flags', async () => {
// #region snippet docs="chat/_default/06-app_and_channel_settings/01-channel_types.md" heading="Edit a Channel Type" tab="Node.js" index=2
// COPY: publicType="public"
- const current = await client.chat.getChannelType({ name: publicType });
-
const update = await client.chat.updateChannelType({
name: publicType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
typing_events: false,
read_events: true,
connect_events: true,
@@ -283,12 +265,8 @@ describe(DOCS, () => {
it('updates settings with new values', async () => {
// #region snippet docs="chat/_default/06-app_and_channel_settings/01-channel_types.md" heading="Edit a Channel Type" tab="Node.js" index=3
// COPY: publicType="public"
- const current = await client.chat.getChannelType({ name: publicType });
-
const update = await client.chat.updateChannelType({
name: publicType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
max_message_length: 140, // the value this example is changing
commands: ['giphy', 'ban'],
});
diff --git a/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/03-chat_permission_policies.test.ts b/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/03-chat_permission_policies.test.ts
index 915ad1c3..275a65e3 100644
--- a/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/03-chat_permission_policies.test.ts
+++ b/__tests__/docs-snippets/pages/chat/06-app_and_channel_settings/03-chat_permission_policies.test.ts
@@ -249,9 +249,6 @@ describe(DOCS, () => {
// update "channel_member" role grants in "messaging" scope
await client.chat.updateChannelType({
name: channelType,
- automod: 'disabled',
- automod_behavior: 'flag',
- max_message_length: 5000,
grants: {
channel_member: [
'read-channel', // allow access to the channel
@@ -287,13 +284,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/06-app_and_channel_settings/03-chat_permission_policies.md" heading="Granting permissions" tab="Node.js" index=3
// COPY: channelType="messaging"
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
grants: {
guest: [], // removes all grants of "guest" role
anonymous: [], // removes all grants of "anonymous" role
@@ -320,15 +312,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/06-app_and_channel_settings/03-chat_permission_policies.md" heading="Granting permissions" tab="Node.js" index=4
// COPY: channelType="messaging"
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
grants: null, // resets the whole scope to default settings
});
// #endregion snippet
diff --git a/__tests__/docs-snippets/pages/chat/12-best_practices/02-moderation.test.ts b/__tests__/docs-snippets/pages/chat/12-best_practices/02-moderation.test.ts
index 235908f8..2bb27ffc 100644
--- a/__tests__/docs-snippets/pages/chat/12-best_practices/02-moderation.test.ts
+++ b/__tests__/docs-snippets/pages/chat/12-best_practices/02-moderation.test.ts
@@ -103,16 +103,9 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/12-best_practices/02-moderation.md" heading="Disabling the permission to post links or add attachments" tab="Node.js" index=1
// COPY: channelType="messaging"
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
-
// Remove link and attachment permissions for channel_member role
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
grants: {
channel_member: [
'read-channel',
@@ -320,16 +313,9 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/12-best_practices/02-moderation.md" heading="Slash commands for banning" tab="Node.js" index=1
// COPY: channelType="messaging"
- // automod, automod_behavior and max_message_length are required here, so read
- // the current values and carry them through.
- const current = await client.chat.getChannelType({ name: channelType });
-
// Enable ban/unban commands for a channel type
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
commands: ['giphy', 'ban', 'unban', 'mute', 'unmute', 'flag'],
});
// #endregion snippet
@@ -490,6 +476,68 @@ describe(DOCS, () => {
expect(log.mock.calls.at(-1)).toEqual([response.next]);
});
+ it('turns on two-way blocking', async () => {
+ const { app } = await client.getApp();
+ const original = app.two_way_block_effect_enabled;
+ expect(original).toBe(false);
+ const restore = async () => {
+ await client.updateApp({ two_way_block_effect_enabled: original });
+ await waitForAppSetting(client, 'two_way_block_effect_enabled', original);
+ };
+ cleanup.add(restore);
+
+ // A blocks B, and both are in a 1-on-1 channel and a group channel.
+ const blockerId = await createUser('blocker');
+ const blockedId = await createUser('blocked');
+ const otherId = await createUser('other');
+ await client.blockUsers({ blocked_user_id: blockedId, user_id: blockerId });
+ const newChannel = async (members: string[]) => {
+ const id = uniqueId('channel');
+ cleanup.channels.push(`messaging:${id}`);
+ await client.chat.channel('messaging', id).getOrCreate({
+ data: {
+ created_by_id: blockerId,
+ members: members.map((user_id) => ({ user_id })),
+ },
+ });
+ return `messaging:${id}`;
+ };
+ const oneOnOneCid = await newChannel([blockerId, blockedId]);
+ const groupCid = await newChannel([blockerId, blockedId, otherId]);
+ const unreadFor = async (cid: string, userId: string) => {
+ const channel = await getServerChannel(client, cid, { user_id: userId });
+ return channel.read?.find((r) => r.user.id === userId)?.unread_messages;
+ };
+
+ // #region snippet docs="chat/_default/12-best_practices/02-moderation.md" heading="Two-way blocking" tab="Node.js" index=1
+ await client.updateApp({
+ two_way_block_effect_enabled: true,
+ });
+ // #endregion snippet
+
+ await waitForAppSetting(client, 'two_way_block_effect_enabled', true, {
+ agreeing: 5,
+ });
+
+ // A's messages no longer count towards B's unread counts in their 1-on-1
+ // channel; group channels are unaffected (the positive control).
+ await sendServerMessage(client, oneOnOneCid, {
+ text: 'hi',
+ user_id: blockerId,
+ });
+ await sendServerMessage(client, groupCid, {
+ text: 'hi all',
+ user_id: blockerId,
+ });
+ await retry(async () => {
+ expect(await unreadFor(groupCid, blockedId)).toBe(1);
+ });
+ expect(await unreadFor(oneOnOneCid, blockedId)).toBe(0);
+
+ // Restore right away: the setting changes chat behavior app-wide.
+ await restore();
+ });
+
it('blocks, lists and unblocks a user', async () => {
const user1Id = await createUser('user1');
const user2Id = await createUser('user2');
diff --git a/__tests__/docs-snippets/pages/chat/12-best_practices/05-marketplace_best_practices.test.ts b/__tests__/docs-snippets/pages/chat/12-best_practices/05-marketplace_best_practices.test.ts
index 1a3ad23e..3da60163 100644
--- a/__tests__/docs-snippets/pages/chat/12-best_practices/05-marketplace_best_practices.test.ts
+++ b/__tests__/docs-snippets/pages/chat/12-best_practices/05-marketplace_best_practices.test.ts
@@ -78,16 +78,9 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/12-best_practices/05-marketplace_best_practices.md" heading="Unread Message Reminders" tab="Node.js" index=1
// COPY: channelType="messaging"
- // Enable reminders for the messaging channel type. automod, automod_behavior and
- // max_message_length are required on this endpoint, so carry the current values
- // through to avoid overwriting them.
- const current = await client.chat.getChannelType({ name: channelType });
-
+ // Enable reminders for the messaging channel type
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
reminders: true,
});
@@ -98,7 +91,7 @@ describe(DOCS, () => {
await waitForChannelTypePropagation();
const updated = await client.chat.getChannelType({ name: featuresType });
expect(updated.reminders).toBe(true);
- expect(updated.max_message_length).toBe(current.max_message_length);
+ expect(updated.max_message_length).toBe(before.max_message_length);
await waitForAppSetting(client, 'reminders_interval', 3600);
});
@@ -151,15 +144,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/12-best_practices/05-marketplace_best_practices.md" heading="Pending Messages" tab="Node.js" index=1
// COPY: channelType="messaging"
- // automod, automod_behavior and max_message_length are required on this endpoint,
- // so carry the current values through to avoid overwriting them.
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
mark_messages_pending: true,
});
// #endregion snippet
@@ -185,15 +171,8 @@ describe(DOCS, () => {
// #region snippet docs="chat/_default/12-best_practices/05-marketplace_best_practices.md" heading="Recommended Settings" tab="Node.js" index=1
// COPY: channelType="messaging"
- // automod, automod_behavior and max_message_length are required on this endpoint,
- // so carry the current values through to avoid overwriting them.
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
typing_events: true, // Show when users are typing
read_events: true, // Track read status for messages
reactions: true, // Allow reactions for quick responses
diff --git a/__tests__/docs-snippets/pages/moderation/02-quick-start/03-quick-start-feeds-moderation.test.ts b/__tests__/docs-snippets/pages/moderation/02-quick-start/03-quick-start-feeds-moderation.test.ts
index 129c53e6..6a554027 100644
--- a/__tests__/docs-snippets/pages/moderation/02-quick-start/03-quick-start-feeds-moderation.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/02-quick-start/03-quick-start-feeds-moderation.test.ts
@@ -3,6 +3,7 @@ import type { QueryFeedModerationTemplatesResponse } from '../../../../../index'
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
+import { waitForReviewItem } from '../../../helpers/server';
const DOCS =
'moderation/_default/02-quick-start/03-quick-start-feeds-moderation.md';
@@ -82,6 +83,7 @@ describe(DOCS, () => {
// arguments (TS2554) and sends no name, so it fails with code 4 "name is
// a required field". Cleanup deletes the template instead.
it.skip('BLOCKED: SDK issue: v2DeleteTemplate() cannot send the template name', async () => {
+ // @ts-expect-error SDK issue: generated v2DeleteTemplate() takes no arguments, so the template name is not sent
// #region snippet docs="moderation/_default/02-quick-start/03-quick-start-feeds-moderation.md" heading="Delete Template" tab="Node.js" index=1
// COPY: templateName="mod_tmpl_1"
await client.moderation.v2DeleteTemplate({ name: templateName });
@@ -89,4 +91,39 @@ describe(DOCS, () => {
expect(await findTemplate(templateName)).toBeUndefined();
});
+
+ // The node SDK can't create Feeds v2 activities (the test app has no Feeds v2 data).
+ // Don't run this against the shared app: the failing flag still stores a review
+ // queue item, and every read that includes it (getReviewQueueItem, a
+ // queryReviewQueue page with it, submitAction on it) fails with the same empty
+ // code -1 error (2026-10-29).
+ it.skip('BLOCKED: flagging a Feeds v2 activity id that does not exist fails with code -1 (500), message: V2Flag failed with error: ""', async () => {
+ // The docs' undefined app values: a Feeds v2 activity (the moderation API takes
+ // any entity id) and its users.
+ const activityId = uniqueId('activity');
+ const actorId = uniqueId('actor');
+ const reporterId = uniqueId('reporter');
+ cleanup.users.push(actorId, reporterId);
+ await client.upsertUsers([{ id: actorId }, { id: reporterId }]);
+
+ // #region snippet docs="moderation/_default/02-quick-start/03-quick-start-feeds-moderation.md" heading="User-Driven Actions" tab="Node.js" index=1
+ await client.moderation.flag({
+ entity_type: 'stream:feeds:v2:activity',
+ entity_id: activityId,
+ entity_creator_id: actorId,
+ reason: 'inappropriate_content',
+ user_id: reporterId,
+ });
+ // #endregion snippet
+
+ const item = await waitForReviewItem(client, activityId);
+ expect(item).toMatchObject({
+ entity_type: 'stream:feeds:v2:activity',
+ entity_creator_id: actorId,
+ });
+ expect(item.flags[0]).toMatchObject({
+ reason: 'inappropriate_content',
+ user_id: reporterId,
+ });
+ });
});
diff --git a/__tests__/docs-snippets/pages/moderation/02-quick-start/04-custom-content-moderation.test.ts b/__tests__/docs-snippets/pages/moderation/02-quick-start/04-custom-content-moderation.test.ts
new file mode 100644
index 00000000..c7bf65ff
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/02-quick-start/04-custom-content-moderation.test.ts
@@ -0,0 +1,111 @@
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+import { waitForReviewItem } from '../../../helpers/server';
+import { retry } from '../../../helpers/wait';
+
+const DOCS =
+ 'moderation/_default/02-quick-start/04-custom-content-moderation.md';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' "custom:my-content-platform-1" policy, test-owned: a blocklist that
+ // flags a word of the docs text.
+ const configKey = `custom:${uniqueId('my-content-platform')}`;
+ const blocklist = uniqueId('custom-content-words');
+ // The docs' "entity_type" placeholder, test-owned, so the review queue query of the
+ // next snippet only matches this file's items.
+ const entityType = uniqueId('entity-type');
+ const entityId = uniqueId('entity');
+ const creatorId = uniqueId('entity-creator');
+
+ beforeAll(async () => {
+ cleanup.users.push(creatorId);
+ await client.upsertUsers([{ id: creatorId }]);
+ await client.createBlockList({ name: blocklist, words: ['bullshit'] });
+ cleanup.add(() => client.deleteBlockList({ name: blocklist }));
+ // A new blocklist isn't known to the moderation API right away ("Blocklist not found").
+ await retry(
+ () =>
+ client.moderation.upsertConfig({
+ key: configKey,
+ block_list_config: { rules: [{ name: blocklist, action: 'flag' }] },
+ }),
+ { retryIf: (error) => String(error).includes('Blocklist not found') },
+ );
+ cleanup.add(() => client.moderation.deleteConfig({ key: configKey }));
+ // A new policy can take a moment to apply.
+ await retry(async () => {
+ const { recommended_action } = await client.moderation.check({
+ entity_type: entityType,
+ entity_id: uniqueId('warmup'),
+ entity_creator_id: creatorId,
+ config_key: configKey,
+ moderation_payload: { texts: ['bullshit'] },
+ });
+ if (recommended_action !== 'flag') {
+ throw new Error(`recommended_action: ${recommended_action}`);
+ }
+ });
+ });
+
+ afterEach(() => vi.restoreAllMocks());
+ afterAll(() => cleanup.run());
+
+ it('submits custom content to the Check API', async () => {
+ const log = vi.spyOn(console, 'log').mockImplementation(() => undefined);
+
+ // #region snippet docs="moderation/_default/02-quick-start/04-custom-content-moderation.md" heading="Submit custom content to the moderation Check API" tab="Node.js" index=1
+ // COPY: entityType="entity_type", entityId="entity_id", creatorId="entity_creator_id", configKey="custom:my-content-platform-1"
+ const response = await client.moderation.check({
+ entity_type: entityType,
+ entity_id: entityId,
+ entity_creator_id: creatorId,
+ moderation_payload: {
+ texts: ['this is bullshit', 'f*ck you as***le'],
+ images: ['example.com/test.jpg'],
+ },
+ config_key: configKey,
+ });
+
+ console.log(response.recommended_action); // keep | flag | remove | shadow
+ // #endregion snippet
+
+ expect(response.recommended_action).toBe('flag');
+ expect(log.mock.calls[0]).toEqual(['flag']);
+ expect(['complete', 'partial']).toContain(response.status);
+ expect(response.item).toMatchObject({
+ entity_id: entityId,
+ entity_type: entityType,
+ });
+ });
+
+ it('queries flagged content', async () => {
+ // The flag of the previous snippet, once it reached the review queue.
+ const flagged = await waitForReviewItem(client, entityId);
+
+ // #region snippet docs="moderation/_default/02-quick-start/04-custom-content-moderation.md" heading="Query Flagged Content" tab="Node.js" index=1
+ // COPY: entityType="entity_type"
+ const response = await client.moderation.queryReviewQueue({
+ filter: { entity_type: entityType },
+ sort: [{ field: 'created_at', direction: -1 }],
+ });
+ // #endregion snippet
+
+ // The newest item is the checked entity; the warm-up checks come after it.
+ expect(response.items[0].id).toBe(flagged.id);
+ for (const item of response.items) {
+ expect(item.entity_type).toBe(entityType);
+ }
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/02-quick-start/07-quick-start-call-moderation.test.ts b/__tests__/docs-snippets/pages/moderation/02-quick-start/07-quick-start-call-moderation.test.ts
index f71a9d45..dc809a89 100644
--- a/__tests__/docs-snippets/pages/moderation/02-quick-start/07-quick-start-call-moderation.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/02-quick-start/07-quick-start-call-moderation.test.ts
@@ -7,37 +7,9 @@ import { uniqueId } from '../../../helpers/ids';
const DOCS =
'moderation/_default/02-quick-start/07-quick-start-call-moderation.md';
-// Values the module-level region below uses (it must come after them: TDZ).
-const apiKey = process.env.STREAM_API_KEY as string;
-const secret = process.env.STREAM_SECRET as string;
-// The docs' "my-room-12345" entity and "user-abc" creator, test-owned.
-const roomEntityId = uniqueId('my-room');
-const creatorId = uniqueId('user-abc');
-// A real image: an unreachable one makes the check return `status: "partial"`.
-const keyframeUrl = 'https://getstream.io/images/og/OG_Home.png';
-await getServerClient().upsertUsers([{ id: creatorId }]);
-
-// #region snippet docs="moderation/_default/02-quick-start/07-quick-start-call-moderation.md" heading="Sending Keyframes" tab="Node.js" index=1
-// COPY: roomEntityId="my-room-12345", creatorId="user-abc", keyframeUrl="https://your-cdn.com/keyframe-001.jpg"
-import { StreamClient } from '@stream-io/node-sdk';
-
-const client = new StreamClient(apiKey, secret);
-
-const response = await client.moderation.check({
- entity_type: 'external:call',
- entity_id: roomEntityId,
- entity_creator_id: creatorId,
- content_published_at: new Date(),
- moderation_payload: {
- images: [keyframeUrl],
- },
-});
-// #endregion snippet
-
describe(DOCS, () => {
- const serverClient = getServerClient();
- const cleanup = new Cleanup(serverClient);
- cleanup.users.push(creatorId);
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
/** Deletes the rules `upsertModerationRule` created in a region, by the ids it returned. */
const deleteUpsertedRules = (spy: {
@@ -49,7 +21,7 @@ describe(DOCS, () => {
await (r.value as Promise);
if (!rule) throw new Error('upsertModerationRule returned no rule');
cleanup.add(() =>
- serverClient.moderation.deleteModerationRule({ id: rule.id }),
+ client.moderation.deleteModerationRule({ id: rule.id }),
);
return rule;
}),
@@ -63,7 +35,7 @@ describe(DOCS, () => {
const callRuleName = uniqueId('inappropriate-content-detection');
const upsertRule = vi.spyOn(client.moderation, 'upsertModerationRule');
- // #region snippet docs="moderation/_default/02-quick-start/07-quick-start-call-moderation.md" heading="Configure Via API" tab="Node.js" index=1
+ // #region snippet docs="moderation/_default/02-quick-start/07-quick-start-call-moderation.md" heading="Configure via API" tab="Node.js" index=1
// COPY: callRuleName="Inappropriate Content Detection"
await client.moderation.upsertModerationRule({
name: callRuleName,
@@ -143,7 +115,7 @@ describe(DOCS, () => {
const userRuleName = uniqueId('ban-repeat-call-offenders');
const upsertRule = vi.spyOn(client.moderation, 'upsertModerationRule');
- // #region snippet docs="moderation/_default/02-quick-start/07-quick-start-call-moderation.md" heading="Configure Via API" tab="Node.js" index=2
+ // #region snippet docs="moderation/_default/02-quick-start/07-quick-start-call-moderation.md" heading="Configure via API" tab="Node.js" index=2
// COPY: userRuleName="Ban Repeat Call Offenders"
await client.moderation.upsertModerationRule({
name: userRuleName,
@@ -174,76 +146,4 @@ describe(DOCS, () => {
],
});
});
-
- it('sends keyframes', () => {
- // The check ran at module level (the fence imports the SDK).
- expect(client).toBeInstanceOf(StreamClient);
- expect(response.status).toBe('complete');
- expect(response.recommended_action).toBe('keep');
- });
-
- it('sends closed captions', async () => {
- // #region snippet docs="moderation/_default/02-quick-start/07-quick-start-call-moderation.md" heading="Sending Closed Captions" tab="Node.js" index=1
- // COPY: roomEntityId="my-room-12345", creatorId="user-abc"
- const response = await client.moderation.check({
- entity_type: 'external:call',
- entity_id: roomEntityId,
- entity_creator_id: creatorId,
- content_published_at: new Date(),
- moderation_payload: {
- texts: ['transcribed audio segment from the call'],
- },
- });
- // #endregion snippet
-
- expect(response.status).toBe('complete');
- expect(response.recommended_action).toBe('keep');
- });
-
- it('runs a continuous moderation loop', async () => {
- const roomId = uniqueId('room');
-
- // #region snippet docs="moderation/_default/02-quick-start/07-quick-start-call-moderation.md" heading="Example: Continuous Moderation Loop" tab="Node.js" index=1
- const ENTITY_ID = `room-${roomId}`;
- const INTERVAL_MS = 5000;
-
- async function moderateKeyframe(imageUrl: string, userId: string) {
- return client.moderation.check({
- entity_type: 'external:call',
- entity_id: ENTITY_ID,
- entity_creator_id: userId,
- content_published_at: new Date(),
- moderation_payload: {
- images: [imageUrl],
- },
- });
- }
-
- async function moderateCaption(text: string, userId: string) {
- return client.moderation.check({
- entity_type: 'external:call',
- entity_id: ENTITY_ID,
- entity_creator_id: userId,
- content_published_at: new Date(),
- moderation_payload: {
- texts: [text],
- },
- });
- }
- // #endregion snippet
-
- expect(INTERVAL_MS).toBe(5000);
- const check = vi.spyOn(client.moderation, 'check');
- const keyframe = await moderateKeyframe(keyframeUrl, creatorId);
- const caption = await moderateCaption('hello everyone', creatorId);
- // With the page's call rule (keyframe condition) in place, the image is analysed
- // asynchronously: `status: "partial"` plus a `task_id`.
- expect(['complete', 'partial']).toContain(keyframe.status);
- expect(keyframe.recommended_action).toBe('keep');
- expect(caption.status).toBe('complete');
- expect(check.mock.calls.map(([request]) => request.entity_id)).toEqual([
- ENTITY_ID,
- ENTITY_ID,
- ]);
- });
});
diff --git a/__tests__/docs-snippets/pages/moderation/02-quick-start/09-your-own-video.test.ts b/__tests__/docs-snippets/pages/moderation/02-quick-start/09-your-own-video.test.ts
new file mode 100644
index 00000000..14a24153
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/02-quick-start/09-your-own-video.test.ts
@@ -0,0 +1,62 @@
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+
+const DOCS = 'moderation/_default/02-quick-start/09-your-own-video.md';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' "my-room-12345" entity and "user-abc" creator, test-owned.
+ const roomEntityId = uniqueId('my-room');
+ const creatorId = uniqueId('user-abc');
+
+ beforeAll(async () => {
+ cleanup.users.push(creatorId);
+ await client.upsertUsers([{ id: creatorId }]);
+ });
+
+ afterAll(() => cleanup.run());
+
+ it('sends a keyframe', async () => {
+ // A real image: an unreachable one makes the check return `status: "partial"`.
+ const keyframeUrl = 'https://getstream.io/images/og/OG_Home.png';
+
+ // #region snippet docs="moderation/_default/02-quick-start/09-your-own-video.md" heading="Send a keyframe" tab="Node.js" index=1
+ // COPY: roomEntityId="my-room-12345", creatorId="user-abc", keyframeUrl="https://your-cdn.com/keyframe-001.jpg"
+ const response = await client.moderation.check({
+ entity_type: 'external:call',
+ entity_id: roomEntityId,
+ entity_creator_id: creatorId,
+ content_published_at: new Date(),
+ moderation_payload: {
+ images: [keyframeUrl],
+ },
+ });
+ // #endregion snippet
+
+ // With a call rule (keyframe condition) on the app, images are analysed
+ // asynchronously: `status: "partial"` plus a `task_id`.
+ expect(['complete', 'partial']).toContain(response.status);
+ expect(response.recommended_action).toBe('keep');
+ });
+
+ it('sends a caption', async () => {
+ // #region snippet docs="moderation/_default/02-quick-start/09-your-own-video.md" heading="Send a caption" tab="Node.js" index=1
+ // COPY: roomEntityId="my-room-12345", creatorId="user-abc"
+ const response = await client.moderation.check({
+ entity_type: 'external:call',
+ entity_id: roomEntityId,
+ entity_creator_id: creatorId,
+ content_published_at: new Date(),
+ moderation_payload: {
+ texts: ['transcribed audio segment'],
+ },
+ });
+ // #endregion snippet
+
+ expect(response.status).toBe('complete');
+ expect(response.recommended_action).toBe('keep');
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/03-moderation-engines/01-ai-llm-text-harm-detection-engine.test.ts b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/01-ai-llm-text-harm-detection-engine.test.ts
index 89fdbf76..f5f437c5 100644
--- a/__tests__/docs-snippets/pages/moderation/03-moderation-engines/01-ai-llm-text-harm-detection-engine.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/01-ai-llm-text-harm-detection-engine.test.ts
@@ -121,4 +121,60 @@ describe(DOCS, () => {
expect(response.status).toBe('complete');
expect(['keep', 'flag', 'remove']).toContain(response.recommended_action);
});
+
+ it('configures the LLM engine', async () => {
+ // The docs' app-wide "chat:messaging" policy, test-owned.
+ const policyKey = uniqueId('llm-policy');
+ cleanup.add(() => client.moderation.deleteConfig({ key: policyKey }));
+
+ // #region snippet docs="moderation/_default/03-moderation-engines/01-ai-llm-text-harm-detection-engine.md" heading="Configuration" tab="Node.js" index=1
+ // COPY: policyKey="chat:messaging"
+ await client.moderation.upsertConfig({
+ key: policyKey,
+ llm_config: {
+ app_context:
+ 'A social chat app for adults. Casual tone is fine. Threats and scams are not.',
+ rules: [
+ {
+ label: 'SCAM',
+ description:
+ 'Fraud, phishing, or requests for payment credentials.',
+ action: 'remove',
+ },
+ {
+ label: 'HATE_SPEECH',
+ description: 'Hatred, discrimination, or violence against a group.',
+ action: 'flag',
+ },
+ ],
+ severity_descriptions: {
+ low: 'Rude but not targeting a person.',
+ high: 'A direct attack or a credible threat.',
+ },
+ },
+ });
+ // #endregion snippet
+
+ const { config }: GetConfigResponse = await client.moderation.getConfig({
+ key: policyKey,
+ });
+ expect(config?.llm_config).toMatchObject({
+ enabled: true,
+ app_context:
+ 'A social chat app for adults. Casual tone is fine. Threats and scams are not.',
+ severity_descriptions: {
+ low: 'Rude but not targeting a person.',
+ high: 'A direct attack or a credible threat.',
+ },
+ });
+ expect(
+ config?.llm_config?.rules?.map(({ label, action }) => ({
+ label,
+ action,
+ })),
+ ).toEqual([
+ { label: 'SCAM', action: 'remove' },
+ { label: 'HATE_SPEECH', action: 'flag' },
+ ]);
+ });
});
diff --git a/__tests__/docs-snippets/pages/moderation/03-moderation-engines/02-ai-text-harm-detection-engine.test.ts b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/02-ai-text-harm-detection-engine.test.ts
new file mode 100644
index 00000000..58d4b4ff
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/02-ai-text-harm-detection-engine.test.ts
@@ -0,0 +1,62 @@
+import { afterAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+
+const DOCS =
+ 'moderation/_default/03-moderation-engines/02-ai-text-harm-detection-engine.md';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' app-wide "chat:messaging" policy, test-owned.
+ const policyKey = uniqueId('ai-text-policy');
+
+ afterAll(() => cleanup.run());
+
+ it('configures the NLP engine with actions and severity rules', async () => {
+ cleanup.add(() => client.moderation.deleteConfig({ key: policyKey }));
+
+ // #region snippet docs="moderation/_default/03-moderation-engines/02-ai-text-harm-detection-engine.md" heading="Configuration" tab="Node.js" index=1
+ // COPY: policyKey="chat:messaging"
+ await client.moderation.upsertConfig({
+ key: policyKey,
+ ai_text_config: {
+ enabled: true,
+ rules: [
+ // One action for every match
+ { label: 'INSULT', action: 'flag' },
+ // Labels without severity can only take one action
+ { label: 'PLATFORM_BYPASS', action: 'flag' },
+ // An action per severity
+ {
+ label: 'HATRED',
+ severity_rules: [
+ { severity: 'low', action: 'flag' },
+ { severity: 'medium', action: 'flag' },
+ { severity: 'high', action: 'remove' },
+ { severity: 'critical', action: 'remove' },
+ ],
+ },
+ ],
+ },
+ });
+ // #endregion snippet
+
+ const { config } = await client.moderation.getConfig({ key: policyKey });
+ expect(config?.ai_text_config?.enabled).toBe(true);
+ expect(config?.ai_text_config?.rules).toMatchObject([
+ { label: 'INSULT', action: 'flag' },
+ { label: 'PLATFORM_BYPASS', action: 'flag' },
+ {
+ label: 'HATRED',
+ severity_rules: [
+ { severity: 'low', action: 'flag' },
+ { severity: 'medium', action: 'flag' },
+ { severity: 'high', action: 'remove' },
+ { severity: 'critical', action: 'remove' },
+ ],
+ },
+ ]);
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/03-moderation-engines/05-image-moderation.test.ts b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/05-image-moderation.test.ts
new file mode 100644
index 00000000..d907c597
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/05-image-moderation.test.ts
@@ -0,0 +1,117 @@
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+
+const DOCS = 'moderation/_default/03-moderation-engines/05-image-moderation.md';
+
+// A small image that Stream's servers can download.
+const REACHABLE_IMAGE =
+ 'https://theme.zdassets.com/theme_assets/9442057/efc3820e436f9150bc8cf34267fff4df052a1f9c.png';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' app-wide "chat:messaging" and "marketplace" policies, test-owned.
+ const policyKey = uniqueId('image-policy');
+ const marketplaceKey = uniqueId('marketplace');
+ const creatorId = uniqueId('user-42');
+
+ beforeAll(async () => {
+ cleanup.users.push(creatorId);
+ await client.upsertUsers([{ id: creatorId }]);
+ cleanup.add(() => client.moderation.deleteConfig({ key: policyKey }));
+ cleanup.add(() => client.moderation.deleteConfig({ key: marketplaceKey }));
+ // The "marketplace" policy the "Send images" check uses: async image moderation.
+ await client.moderation.upsertConfig({
+ key: marketplaceKey,
+ ai_image_config: {
+ rules: [{ label: 'Explicit', action: 'remove', min_confidence: 70 }],
+ },
+ });
+ });
+
+ afterAll(() => cleanup.run());
+
+ it('configures image moderation', async () => {
+ // #region snippet docs="moderation/_default/03-moderation-engines/05-image-moderation.md" heading="Configuration" tab="Node.js" index=1
+ // COPY: policyKey="chat:messaging"
+ await client.moderation.upsertConfig({
+ key: policyKey,
+ ai_image_config: {
+ rules: [
+ { label: 'Explicit', action: 'remove', min_confidence: 70 },
+ { label: 'Violence', action: 'flag', min_confidence: 50 },
+ ],
+ ocr_rules: [{ label: 'PII', action: 'flag' }],
+ },
+ });
+ // #endregion snippet
+
+ const { config } = await client.moderation.getConfig({ key: policyKey });
+ expect(config?.ai_image_config).toMatchObject({
+ enabled: true,
+ rules: [
+ { label: 'Explicit', action: 'remove', min_confidence: 70 },
+ { label: 'Violence', action: 'flag', min_confidence: 50 },
+ ],
+ ocr_rules: [{ label: 'PII', action: 'flag' }],
+ });
+ });
+
+ it('sends images to the Check API', async () => {
+ const listingId = uniqueId('listing-123');
+
+ // #region snippet docs="moderation/_default/03-moderation-engines/05-image-moderation.md" heading="Send images" tab="Node.js" index=1
+ // COPY: listingId="listing-123", creatorId="user-42", marketplaceKey="marketplace"
+ const response = await client.moderation.check({
+ entity_type: 'listing',
+ entity_id: listingId,
+ entity_creator_id: creatorId,
+ config_key: marketplaceKey,
+ moderation_payload: {
+ images: ['https://cdn.example.com/listings/123/photo-1.jpg'],
+ },
+ });
+ // #endregion snippet
+
+ // Async image moderation: the check returns before the analysis, with a task id.
+ // The docs say `status: "pending"`; the test app answers `"partial"`.
+ expect(['pending', 'partial']).toContain(response.status);
+ expect(typeof response.task_id).toBe('string');
+ expect(response.recommended_action).toBe('keep');
+ });
+
+ it('turns on synchronous image moderation', async () => {
+ // #region snippet docs="moderation/_default/03-moderation-engines/05-image-moderation.md" heading="Synchronous moderation" tab="Node.js" index=1
+ // COPY: marketplaceKey="marketplace"
+ await client.moderation.upsertConfig({
+ key: marketplaceKey,
+ ai_image_config: {
+ async: false,
+ rules: [{ label: 'Explicit', action: 'remove', min_confidence: 50 }],
+ },
+ });
+ // #endregion snippet
+
+ const { config } = await client.moderation.getConfig({
+ key: marketplaceKey,
+ });
+ expect(config?.ai_image_config).toMatchObject({
+ async: false,
+ rules: [{ label: 'Explicit', action: 'remove', min_confidence: 50 }],
+ });
+
+ // Check now returns the image verdict in its response: no task id.
+ const check = await client.moderation.check({
+ entity_type: 'listing',
+ entity_id: uniqueId('listing'),
+ entity_creator_id: creatorId,
+ config_key: marketplaceKey,
+ moderation_payload: { images: [REACHABLE_IMAGE] },
+ });
+ expect(check.status).toBe('complete');
+ expect(check.task_id).toBeUndefined();
+ expect(check.recommended_action).toBe('keep');
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/03-moderation-engines/06-ai-video-file-moderation.test.ts b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/06-ai-video-file-moderation.test.ts
new file mode 100644
index 00000000..75fc56ee
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/06-ai-video-file-moderation.test.ts
@@ -0,0 +1,82 @@
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+
+const DOCS =
+ 'moderation/_default/03-moderation-engines/06-ai-video-file-moderation.md';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' app-wide "chat:messaging" and "marketplace" policies, test-owned.
+ const policyKey = uniqueId('video-policy');
+ const marketplaceKey = uniqueId('marketplace');
+ const creatorId = uniqueId('user-42');
+
+ beforeAll(async () => {
+ cleanup.users.push(creatorId);
+ await client.upsertUsers([{ id: creatorId }]);
+ cleanup.add(() => client.moderation.deleteConfig({ key: policyKey }));
+ cleanup.add(() => client.moderation.deleteConfig({ key: marketplaceKey }));
+ // The "marketplace" policy the check uses, with video moderation on.
+ await client.moderation.upsertConfig({
+ key: marketplaceKey,
+ ai_video_config: {
+ rules: [{ label: 'Explicit', action: 'remove', min_confidence: 70 }],
+ },
+ });
+ });
+
+ afterAll(() => cleanup.run());
+
+ it('configures video moderation', async () => {
+ // #region snippet docs="moderation/_default/03-moderation-engines/06-ai-video-file-moderation.md" heading="Configuration" tab="Node.js" index=1
+ // COPY: policyKey="chat:messaging"
+ await client.moderation.upsertConfig({
+ key: policyKey,
+ ai_video_config: {
+ rules: [
+ { label: 'Explicit', action: 'remove', min_confidence: 70 },
+ { label: 'Violence', action: 'flag', min_confidence: 50 },
+ ],
+ },
+ });
+ // #endregion snippet
+
+ const { config } = await client.moderation.getConfig({ key: policyKey });
+ expect(config?.ai_video_config).toMatchObject({
+ enabled: true,
+ rules: [
+ { label: 'Explicit', action: 'remove', min_confidence: 70 },
+ { label: 'Violence', action: 'flag', min_confidence: 50 },
+ ],
+ });
+ // Video and image are set separately.
+ expect(config?.ai_image_config).toBeNull();
+ });
+
+ it('sends videos to the Check API', async () => {
+ const listingId = uniqueId('listing-123');
+
+ // #region snippet docs="moderation/_default/03-moderation-engines/06-ai-video-file-moderation.md" heading="Configuration" tab="Node.js" index=2
+ // COPY: listingId="listing-123", creatorId="user-42", marketplaceKey="marketplace"
+ const response = await client.moderation.check({
+ entity_type: 'listing',
+ entity_id: listingId,
+ entity_creator_id: creatorId,
+ config_key: marketplaceKey,
+ moderation_payload: {
+ videos: ['https://cdn.example.com/listings/123/tour.mp4'],
+ },
+ });
+ // response.status is "pending" until the video is analyzed
+ // #endregion snippet
+
+ // Video analysis is always async: the check returns at once with a task id.
+ // The docs say `"pending"`; the test app answers `"partial"`.
+ expect(['pending', 'partial']).toContain(response.status);
+ expect(typeof response.task_id).toBe('string');
+ expect(response.recommended_action).toBe('keep');
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/03-moderation-engines/07-ai-audio-file-moderation.test.ts b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/07-ai-audio-file-moderation.test.ts
new file mode 100644
index 00000000..6145ea69
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/07-ai-audio-file-moderation.test.ts
@@ -0,0 +1,70 @@
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+
+const DOCS =
+ 'moderation/_default/03-moderation-engines/07-ai-audio-file-moderation.md';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' app-wide "chat:messaging" policy, test-owned (both fences use it).
+ const policyKey = uniqueId('audio-policy');
+ const creatorId = uniqueId('user-42');
+
+ beforeAll(async () => {
+ cleanup.users.push(creatorId);
+ await client.upsertUsers([{ id: creatorId }]);
+ cleanup.add(() => client.moderation.deleteConfig({ key: policyKey }));
+ });
+
+ afterAll(() => cleanup.run());
+
+ it('configures audio moderation', async () => {
+ // #region snippet docs="moderation/_default/03-moderation-engines/07-ai-audio-file-moderation.md" heading="Configuration" tab="Node.js" index=1
+ // COPY: policyKey="chat:messaging"
+ await client.moderation.upsertConfig({
+ key: policyKey,
+ ai_audio_config: {
+ ai_text_config: {
+ rules: [{ label: 'HATRED', action: 'flag' }],
+ },
+ },
+ });
+ // #endregion snippet
+
+ const { config } = await client.moderation.getConfig({ key: policyKey });
+ // A rule with an action turns audio moderation on.
+ expect(config?.ai_audio_config?.enabled).toBe(true);
+ expect(config?.ai_audio_config?.ai_text_config?.rules).toMatchObject([
+ { label: 'HATRED', action: 'flag' },
+ ]);
+ // Text policy rules don't apply to transcripts: the policy has no text config.
+ expect(config?.ai_text_config).toBeNull();
+ });
+
+ it('sends audio to the Check API', async () => {
+ const voiceNoteId = uniqueId('voice-note-123');
+
+ // #region snippet docs="moderation/_default/03-moderation-engines/07-ai-audio-file-moderation.md" heading="Send audio" tab="Node.js" index=1
+ // COPY: voiceNoteId="voice-note-123", creatorId="user-42", policyKey="chat:messaging"
+ const response = await client.moderation.check({
+ entity_type: 'voice_note',
+ entity_id: voiceNoteId,
+ entity_creator_id: creatorId,
+ config_key: policyKey,
+ moderation_payload: {
+ audios: ['https://cdn.example.com/voice/123.m4a'],
+ },
+ });
+ // response.status is "pending" until transcription and classification finish
+ // #endregion snippet
+
+ // Audio is always async: the check returns at once with a task id.
+ // The docs say `"pending"`; the test app answers `"partial"`.
+ expect(['pending', 'partial']).toContain(response.status);
+ expect(typeof response.task_id).toBe('string');
+ expect(response.recommended_action).toBe('keep');
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/03-moderation-engines/08-usernames-and-images.test.ts b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/08-usernames-and-images.test.ts
new file mode 100644
index 00000000..285377eb
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/03-moderation-engines/08-usernames-and-images.test.ts
@@ -0,0 +1,100 @@
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+
+const DOCS =
+ 'moderation/_default/03-moderation-engines/08-usernames-and-images.md';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' app-wide "username:default" and "user_profile:default" policies,
+ // test-owned. `labels` fails with code 4 "configuration not found" without the policy.
+ const usernamePolicy = `username:${uniqueId('usernames')}`;
+ const profilePolicy = `user_profile:${uniqueId('profiles')}`;
+ const userId = uniqueId('user');
+
+ beforeAll(async () => {
+ cleanup.users.push(userId);
+ await client.upsertUsers([{ id: userId }]);
+ cleanup.add(() => client.moderation.deleteConfig({ key: usernamePolicy }));
+ cleanup.add(() => client.moderation.deleteConfig({ key: profilePolicy }));
+ await client.moderation.upsertConfig({
+ key: usernamePolicy,
+ ai_text_config: { rules: [{ label: 'INSULT', action: 'flag' }] },
+ });
+ await client.moderation.upsertConfig({
+ key: profilePolicy,
+ ai_text_config: { rules: [{ label: 'INSULT', action: 'flag' }] },
+ ai_image_config: {
+ rules: [{ label: 'Explicit', action: 'remove', min_confidence: 50 }],
+ },
+ });
+ });
+
+ afterAll(() => cleanup.run());
+
+ it('checks a username with the labels fast path', async () => {
+ // #region snippet docs="moderation/_default/03-moderation-engines/08-usernames-and-images.md" heading="Check Username" tab="Node.js" index=1
+ // COPY: usernamePolicy="username:default"
+ const response = await client.moderation.labels({
+ content: 'username_to_check',
+ content_type: 'username',
+ policy: usernamePolicy,
+ });
+
+ if (response.recommended_action === 'keep') {
+ // Username is acceptable
+ }
+ // #endregion snippet
+
+ expect(response.recommended_action).toBe('keep');
+ });
+
+ it('checks a username against a user profile policy', async () => {
+ // #region snippet docs="moderation/_default/03-moderation-engines/08-usernames-and-images.md" heading="Check Username" tab="Node.js" index=2
+ // COPY: profilePolicy="user_profile:default"
+ const response = await client.moderation.check({
+ entity_type: 'stream:v1:user_profile',
+ entity_id: userId,
+ entity_creator_id: userId,
+ moderation_payload: { texts: ['username_to_check'] },
+ config_key: profilePolicy,
+ });
+
+ // Response will indicate if the username is acceptable
+ if (response.recommended_action === 'keep') {
+ // Username is acceptable, proceed with user creation/update
+ } else {
+ // Username violates moderation rules, ask user to choose a different name
+ }
+ // #endregion snippet
+
+ expect(response.status).toBe('complete');
+ expect(response.recommended_action).toBe('keep');
+ });
+
+ it('checks a profile image', async () => {
+ // #region snippet docs="moderation/_default/03-moderation-engines/08-usernames-and-images.md" heading="Check Profile Images" tab="Node.js" index=1
+ // COPY: profilePolicy="user_profile:default"
+ const response = await client.moderation.check({
+ entity_type: 'stream:v1:user_profile',
+ entity_id: userId,
+ entity_creator_id: userId,
+ moderation_payload: { images: ['https://example.com/profile.jpg'] },
+ config_key: profilePolicy,
+ });
+
+ // Handle the moderation result
+ if (response.recommended_action === 'keep') {
+ // Image is safe to use as profile picture
+ } else {
+ // Image violates moderation rules
+ }
+ // #endregion snippet
+
+ expect(['complete', 'partial']).toContain(response.status);
+ expect(response.recommended_action).toBe('keep');
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/04-configuration/01-policies.test.ts b/__tests__/docs-snippets/pages/moderation/04-configuration/01-policies.test.ts
index 72e70de7..0bb79df0 100644
--- a/__tests__/docs-snippets/pages/moderation/04-configuration/01-policies.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/04-configuration/01-policies.test.ts
@@ -150,7 +150,7 @@ describe(DOCS, () => {
expect(configs.length).toBeGreaterThan(0);
expect(configs.length).toBeLessThanOrEqual(10);
// Newest first: the config this file just created is among them.
- const createdAt = configs.map(({ created_at }) => created_at.getTime());
+ const createdAt = configs.map(({ created_at }) => created_at);
expect(createdAt).toEqual([...createdAt].sort((a, b) => b - a));
expect(configs.map(({ key }) => key)).toContain(configKey);
});
diff --git a/__tests__/docs-snippets/pages/moderation/04-configuration/05-evaluations.test.ts b/__tests__/docs-snippets/pages/moderation/04-configuration/05-evaluations.test.ts
index 0d0753c0..a9849565 100644
--- a/__tests__/docs-snippets/pages/moderation/04-configuration/05-evaluations.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/04-configuration/05-evaluations.test.ts
@@ -151,6 +151,7 @@ describe(DOCS, () => {
// The generated `listPolicyTestSets()` takes no arguments (TS2554), so
// `limit` isn't sent: the API returns its default page size.
it('lists evaluation sets', async () => {
+ // @ts-expect-error SDK issue: generated listPolicyTestSets() takes no arguments, so limit is not sent
// #region snippet docs="moderation/_default/04-configuration/05-evaluations.md" heading="List evaluation sets" tab="Node.js" index=1
const response = await client.moderation.listPolicyTestSets({ limit: 50 });
// #endregion snippet
diff --git a/__tests__/docs-snippets/pages/moderation/05-content-moderation/02-flag-mute-ban.test.ts b/__tests__/docs-snippets/pages/moderation/05-content-moderation/02-flag-mute-ban.test.ts
index a7c009ff..047ec03c 100644
--- a/__tests__/docs-snippets/pages/moderation/05-content-moderation/02-flag-mute-ban.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/05-content-moderation/02-flag-mute-ban.test.ts
@@ -1,5 +1,5 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
-import type { StreamClient, UserResponse } from '../../../../../index';
+import { nsToMs } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
@@ -144,9 +144,9 @@ describe(DOCS, () => {
const { mutes } = await getServerUser(client, mutingUserId);
expect(mutes.map((m) => m.target?.id)).toEqual([mutedUser]);
// 60 minutes
- expect(
- (mutes[0].expires?.getTime() ?? 0) - mutes[0].created_at.getTime(),
- ).toBe(60 * 60 * 1000);
+ expect(nsToMs(mutes[0].expires ?? 0) - nsToMs(mutes[0].created_at)).toBe(
+ 60 * 60 * 1000,
+ );
});
it('unmutes a user', async () => {
@@ -194,9 +194,9 @@ describe(DOCS, () => {
expect(bans[0].banned_by?.id).toBe(moderatorId);
expect(bans[0].channel).toBeUndefined();
// 1440 minutes
- expect(
- (bans[0].expires?.getTime() ?? 0) - bans[0].created_at.getTime(),
- ).toBe(1440 * 60 * 1000);
+ expect(nsToMs(bans[0].expires ?? 0) - nsToMs(bans[0].created_at)).toBe(
+ 1440 * 60 * 1000,
+ );
});
it('bans a user from one channel', async () => {
@@ -314,7 +314,9 @@ describe(DOCS, () => {
await retry(
async () => {
const user = await getServerUser(client, expiredBanned);
- if ((user.ban_expires?.getTime() ?? Infinity) > Date.now()) {
+ if (
+ (user.ban_expires ? nsToMs(user.ban_expires) : Infinity) > Date.now()
+ ) {
throw new Error('ban not expired yet');
}
},
@@ -398,26 +400,4 @@ describe(DOCS, () => {
true,
);
});
-
- it.skip('BLOCKED: SDK issue: the node SDK has no getUserModerationReport (GET /api/v2/moderation/user_report is left out of the spec)', async () => {
- const reportUser = await createUser('report-user');
- // The node SDK has no getUserModerationReport: type the missing method (from the
- // API's response) so the skipped region typechecks and lints. It isn't there at runtime.
- const client = getServerClient() as StreamClient & {
- moderation: {
- getUserModerationReport: (request: {
- user_id: string;
- }) => Promise<{ user: UserResponse }>;
- };
- };
-
- // #region snippet docs="moderation/_default/05-content-moderation/02-flag-mute-ban.md" heading="User Moderation Report" tab="Node.js" index=1
- // COPY: reportUser="user_123"
- const response = await client.moderation.getUserModerationReport({
- user_id: reportUser,
- });
- // #endregion snippet
-
- expect(response.user.id).toBe(reportUser);
- });
});
diff --git a/__tests__/docs-snippets/pages/moderation/06-review-queue/01-review-queue.test.ts b/__tests__/docs-snippets/pages/moderation/06-review-queue/01-review-queue.test.ts
index 9417fa5a..c0e0facb 100644
--- a/__tests__/docs-snippets/pages/moderation/06-review-queue/01-review-queue.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/06-review-queue/01-review-queue.test.ts
@@ -1,8 +1,18 @@
-import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import type { SubmitModerationFeedbackResponse } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
import { sendServerMessage, waitForReviewItem } from '../../../helpers/server';
+import { retry } from '../../../helpers/wait';
const DOCS = 'moderation/_default/06-review-queue/01-review-queue.md';
@@ -42,8 +52,94 @@ describe(DOCS, () => {
cleanup.channels.push(cid);
});
+ afterEach(() => vi.restoreAllMocks());
afterAll(() => cleanup.run());
+ it('queries flagged items that are not reviewed yet', async () => {
+ // Manual flags have `recommended_action: "keep"`: flag messages through a
+ // channel-level blocklist policy (action `flag`) of a new channel instead.
+ const policyChannelId = uniqueId('policy-channel');
+ const policyCid = `messaging:${policyChannelId}`;
+ const configKey = `chat:${policyCid}`;
+ const blocklist = uniqueId('review-words');
+ await client.chat
+ .channel('messaging', policyChannelId)
+ .getOrCreate({ data: { created_by_id: moderatorId } });
+ cleanup.channels.push(policyCid);
+ await client.createBlockList({ name: blocklist, words: ['frobnicate'] });
+ cleanup.add(() => client.deleteBlockList({ name: blocklist }));
+ // A new blocklist isn't known to the moderation API right away.
+ await retry(
+ () =>
+ client.moderation.upsertConfig({
+ key: configKey,
+ block_list_config: { rules: [{ name: blocklist, action: 'flag' }] },
+ }),
+ { retryIf: (error) => String(error).includes('Blocklist not found') },
+ );
+ cleanup.add(() => client.moderation.deleteConfig({ key: configKey }));
+ // A new policy can take a moment to apply.
+ await retry(async () => {
+ const { recommended_action } = await client.moderation.check({
+ entity_type: 'stream:chat:v1:message',
+ entity_id: uniqueId('warmup'),
+ entity_creator_id: creatorId,
+ config_key: configKey,
+ moderation_payload: { texts: ['frobnicate'] },
+ });
+ expect(recommended_action).toBe('flag');
+ });
+ /** Sends a message the policy flags, and returns its review queue item. */
+ // The channel's policy can apply to messages a while after `check` sees it:
+ // send again until a message reaches the review queue.
+ const policyFlaggedMessage = () =>
+ retry(async () => {
+ const message = await sendServerMessage(
+ client,
+ policyCid,
+ { text: 'please frobnicate now', user_id: creatorId },
+ { force_moderation: true },
+ );
+ return retry(
+ async () => {
+ const { items } = await client.moderation.queryReviewQueue({
+ filter: { entity_id: message.id },
+ });
+ expect(items).toHaveLength(1);
+ return items[0];
+ },
+ { timeout: 5000 },
+ );
+ });
+ // The filter matches every unreviewed flagged item of the app: flag a fresh
+ // message just before, so it is the newest one. A newer item marked as reviewed
+ // must not show up.
+ const flagged = await policyFlaggedMessage();
+ const reviewed = await policyFlaggedMessage();
+ await client.moderation.submitAction({
+ action_type: 'mark_reviewed',
+ item_id: reviewed.id,
+ user_id: moderatorId,
+ });
+
+ // #region snippet docs="moderation/_default/06-review-queue/01-review-queue.md" heading="Query Review Queue" tab="Node.js" index=1
+ const response = await client.moderation.queryReviewQueue({
+ filter: { recommended_action: 'flag', reviewed: false },
+ sort: [{ field: 'created_at', direction: -1 }],
+ });
+ // #endregion snippet
+
+ expect(response.items[0].id).toBe(flagged.id);
+ expect(response.items[0].recommended_action).toBe('flag');
+ // The bare `recommended_action: "flag"` equality doesn't narrow the results on
+ // the test app (items with "keep" come back too; `{ $in: ["flag"] }` does narrow
+ // them, 2026-10-29), so only the `reviewed` part is asserted for every item.
+ for (const item of response.items) {
+ expect(item.reviewed_at).toBeFalsy();
+ }
+ expect(response.items.map((item) => item.id)).not.toContain(reviewed.id);
+ });
+
it('queries the review queue', async () => {
// The filter matches every unreviewed message item of the app: flag a fresh
// message just before, so it is the newest one. A newer item marked as reviewed
@@ -56,7 +152,7 @@ describe(DOCS, () => {
user_id: moderatorId,
});
- // #region snippet docs="moderation/_default/06-review-queue/01-review-queue.md" heading="Query Review Queue" tab="Node.js" index=1
+ // #region snippet docs="moderation/_default/06-review-queue/01-review-queue.md" heading="Query Review Queue" tab="Node.js" index=2
const response = await client.moderation.queryReviewQueue({
filter: {
entity_type: 'stream:chat:v1:message',
@@ -97,4 +193,30 @@ describe(DOCS, () => {
user_id: moderatorId,
});
});
+
+ it('submits moderator feedback', async () => {
+ const submitFeedback = vi.spyOn(
+ client.moderation,
+ 'submitModerationFeedback',
+ );
+
+ // #region snippet docs="moderation/_default/06-review-queue/01-review-queue.md" heading="Moderator feedback" tab="Node.js" index=1
+ await client.moderation.submitModerationFeedback({
+ message: 'the moderated text',
+ published_at: '2026-10-20T13:13:38Z',
+ reference: 'provider-reference',
+ current_labels: ['HATRED'],
+ current_recommended_action: 'remove',
+ expected_labels: ['INSULT'],
+ expected_recommended_action: 'flag',
+ });
+ // #endregion snippet
+
+ const [response] = await Promise.all(
+ submitFeedback.mock.results.map(
+ (r) => r.value as Promise,
+ ),
+ );
+ expect(response.duration).toBeTruthy();
+ });
});
diff --git a/__tests__/docs-snippets/pages/moderation/06-review-queue/02-actions.test.ts b/__tests__/docs-snippets/pages/moderation/06-review-queue/02-actions.test.ts
index cfc1de72..ecfbf0f5 100644
--- a/__tests__/docs-snippets/pages/moderation/06-review-queue/02-actions.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/06-review-queue/02-actions.test.ts
@@ -1,9 +1,19 @@
-import { afterAll, beforeAll, describe, expect, it } from 'vitest';
-import type { StreamClient } from '../../../../../index';
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ describe,
+ expect,
+ it,
+ vi,
+} from 'vitest';
+import { nsToMs } from '../../../../../index';
+import type { UpsertActionConfigResponse } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
import { sendServerMessage, waitForReviewItem } from '../../../helpers/server';
+import { retry } from '../../../helpers/wait';
const DOCS = 'moderation/_default/06-review-queue/02-actions.md';
@@ -13,6 +23,7 @@ describe(DOCS, () => {
const moderatorId = uniqueId('moderator');
const channelId = uniqueId('actions-channel');
const cid = `messaging:${channelId}`;
+ const upsertConfig = client.moderation.upsertConfig.bind(client.moderation);
/**
* Flags a fresh message of a new member (so bans of one test don't affect the
@@ -70,6 +81,7 @@ describe(DOCS, () => {
cleanup.channels.push(cid);
});
+ afterEach(() => vi.restoreAllMocks());
afterAll(() => cleanup.run());
it('marks an item as reviewed', async () => {
@@ -134,10 +146,9 @@ describe(DOCS, () => {
expect(bans[0].channel?.cid).toBe(cid);
expect(bans[0].reason).toBe('Repeated harassment');
expect(bans[0].expires).toBeDefined();
- expect(
- new Date(bans[0].expires ?? 0).getTime() -
- new Date(bans[0].created_at).getTime(),
- ).toBe(1440 * 60 * 1000);
+ expect(nsToMs(bans[0].expires ?? 0) - nsToMs(bans[0].created_at)).toBe(
+ 1440 * 60 * 1000,
+ );
const { users } = await client.queryUsers({
payload: { filter_conditions: { id: creatorId } },
});
@@ -230,55 +241,120 @@ describe(DOCS, () => {
);
});
- it.skip("BLOCKED: SDK issue: the node SDK has no bulkSubmitAction (POST /api/v2/moderation/bulk_submit_action is `Ignore: true` in the spec), and the API rejects the `actions` list: code 4 V2BulkSubmitAction failed with error: \"action is a required field, filters 'BulkSubmitActionRequest.filters' Error:Field validation for 'filters' failed on the 'required_without' tag, review_queue_item_ids 'BulkSubmitActionRequest.review_queue_item_ids' Error:Field validation for 'review_queue_item_ids' failed on the 'required_without' tag\"", async () => {
- const [first, second, third] = await Promise.all([
- flagMessage(),
- flagMessage(),
- flagMessage(),
- ]);
- const itemId1 = first.itemId;
- const itemId2 = second.itemId;
- const itemId3 = third.itemId;
- // The node SDK has no bulkSubmitAction: type the missing method (with the request
- // shape the docs use) so the skipped region typechecks and lints. It isn't there
- // at runtime.
- const client = getServerClient() as StreamClient & {
- moderation: {
- bulkSubmitAction: (request: {
- actions: Array<{
- action_type: string;
- item_id: string;
- user_id: string;
- }>;
- }) => Promise;
- };
- };
-
- // #region snippet docs="moderation/_default/06-review-queue/02-actions.md" heading="Bulk Submit Action" tab="Node.js" index=1
- // COPY: itemId1="item_id_1", itemId2="item_id_2", itemId3="item_id_3"
- await client.moderation.bulkSubmitAction({
- actions: [
- {
- action_type: 'mark_reviewed',
- item_id: itemId1,
- user_id: moderatorId,
- },
- {
- action_type: 'delete_message',
- item_id: itemId2,
- user_id: moderatorId,
- },
- {
- action_type: 'mark_reviewed',
- item_id: itemId3,
- user_id: moderatorId,
- },
- ],
+ it('customizes a review queue button', async () => {
+ const entityType = 'stream:chat:v1:message';
+ /** The app's own (non-default) buttons for chat messages. */
+ const listButtons = async () =>
+ (
+ await client.moderation.getActionConfig({
+ entity_type: entityType,
+ exclude_defaults: true,
+ })
+ ).action_config[entityType] ?? [];
+ const original = await listButtons();
+ const upsertActionConfig = vi.spyOn(
+ client.moderation,
+ 'upsertActionConfig',
+ );
+
+ // #region snippet docs="moderation/_default/06-review-queue/02-actions.md" heading="Customize review queue buttons" tab="Node.js" index=1
+ await client.moderation.upsertActionConfig({
+ entity_type: 'stream:chat:v1:message',
+ order: 1,
+ action: 'delete_message',
+ description: 'Delete message',
});
// #endregion snippet
- expect((await getItem(itemId1))?.latest_moderator_action).toBe(
- 'mark_reviewed',
+ const [{ action_config: created }] = await Promise.all(
+ upsertActionConfig.mock.results.map(
+ (r) => r.value as Promise,
+ ),
+ );
+ const createdId = created?.id;
+ if (!createdId) throw new Error('upsertActionConfig returned no id');
+ // Without an `id` the request creates a new button (the app's own buttons stay).
+ expect(original.map(({ id }) => id)).not.toContain(createdId);
+ cleanup.add(async () => {
+ await client.moderation.deleteActionConfig({ id: createdId });
+ expect((await listButtons()).map(({ id }) => id)).toEqual(
+ original.map(({ id }) => id),
+ );
+ });
+ expect(created).toMatchObject({
+ entity_type: 'stream:chat:v1:message',
+ order: 1,
+ action: 'delete_message',
+ description: 'Delete message',
+ });
+ expect(
+ (await listButtons()).find(({ id }) => id === createdId),
+ ).toMatchObject({
+ action: 'delete_message',
+ description: 'Delete message',
+ });
+ });
+
+ it('masks blocklist matches with mask_flag', async () => {
+ // The docs' app-wide "chat" policy, test-owned: a channel-level policy of a new
+ // channel, so only that channel is moderated by it.
+ const maskChannelId = uniqueId('mask-channel');
+ const maskCid = `messaging:${maskChannelId}`;
+ const chatConfigKey = `chat:${maskCid}`;
+ const blocklistName = uniqueId('phone-numbers');
+ await client.chat
+ .channel('messaging', maskChannelId)
+ .getOrCreate({ data: { created_by_id: moderatorId } });
+ cleanup.channels.push(maskCid);
+ await client.createBlockList({
+ name: blocklistName,
+ words: ['frobnicate'],
+ });
+ cleanup.add(() => client.deleteBlockList({ name: blocklistName }));
+ cleanup.add(() => client.moderation.deleteConfig({ key: chatConfigKey }));
+ // A new blocklist isn't known to the moderation API right away.
+ vi.spyOn(client.moderation, 'upsertConfig').mockImplementation((request) =>
+ retry(() => upsertConfig(request), {
+ retryIf: (error) => String(error).includes('Blocklist not found'),
+ }),
);
+
+ // #region snippet docs="moderation/_default/06-review-queue/02-actions.md" heading="Mask" tab="Node.js" index=1
+ // COPY: chatConfigKey="chat"
+ await client.moderation.upsertConfig({
+ key: chatConfigKey,
+ block_list_config: {
+ enabled: true,
+ rules: [{ name: blocklistName, action: 'mask_flag', team: '' }],
+ },
+ });
+ // #endregion snippet
+
+ const { config } = await client.moderation.getConfig({
+ key: chatConfigKey,
+ });
+ expect(config?.block_list_config?.rules).toMatchObject([
+ { name: blocklistName, action: 'mask_flag' },
+ ]);
+ // A matching message is published with the word masked, and flagged.
+ const senderId = uniqueId('sender');
+ cleanup.users.push(senderId);
+ await client.upsertUsers([{ id: senderId }]);
+ const message = await retry(async () => {
+ const sent = await sendServerMessage(
+ client,
+ maskCid,
+ { text: 'please frobnicate now', user_id: senderId },
+ { force_moderation: true },
+ );
+ // The matched word is replaced (on the test app with four U+26B9 characters,
+ // whatever the word's length, 2026-10-29).
+ expect(sent.text).not.toContain('frobnicate');
+ expect(sent.text).toMatch(/^please \S+ now$/);
+ return sent;
+ });
+ expect(message.type).toBe('regular');
+ const item = await waitForReviewItem(client, message.id);
+ expect(item.entity_id).toBe(message.id);
});
});
diff --git a/__tests__/docs-snippets/pages/moderation/06-review-queue/03-appeals.test.ts b/__tests__/docs-snippets/pages/moderation/06-review-queue/03-appeals.test.ts
index 9ca49631..780fedd9 100644
--- a/__tests__/docs-snippets/pages/moderation/06-review-queue/03-appeals.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/06-review-queue/03-appeals.test.ts
@@ -388,4 +388,42 @@ describe(DOCS, () => {
expect(submitted.items.map((item) => item.id)).not.toContain(withoutAppeal);
expect(byAppeal.items.map((item) => item.id)).toEqual([itemId]);
});
+
+ it('restores content for several appeals at once', async () => {
+ const first = await deletedMessage();
+ const second = await deletedMessage();
+ const appealIds = await Promise.all(
+ [first, second].map(async ({ creatorId, messageId }) => {
+ const { appeal_id } = await client.moderation.appeal({
+ appeal_reason: 'Not spam',
+ entity_id: messageId,
+ entity_type: 'stream:chat:v1:message',
+ user_id: creatorId,
+ });
+ return appeal_id;
+ }),
+ );
+ const [appealId1, appealId2] = appealIds;
+
+ // #region snippet docs="moderation/_default/06-review-queue/03-appeals.md" heading="Bulk action" tab="Node.js" index=1
+ // COPY: appealId1="appeal_1", appealId2="appeal_2"
+ await client.moderation.bulkActionAppeals({
+ action_type: 'restore',
+ appeal_ids: [appealId1, appealId2],
+ restore: { decision_reason: 'Restored after review' },
+ user_id: moderatorId,
+ });
+ // #endregion snippet
+
+ for (const appealId of appealIds) {
+ expect(await getAppeal(appealId)).toMatchObject({
+ status: 'accepted',
+ decision_reason: 'Restored after review',
+ });
+ }
+ for (const { messageId } of [first, second]) {
+ const { message } = await client.chat.getMessage({ id: messageId });
+ expect(message.deleted_at).toBeUndefined();
+ }
+ });
});
diff --git a/__tests__/docs-snippets/pages/moderation/11-guides/09-rule-triggered-webhooks.test.ts b/__tests__/docs-snippets/pages/moderation/08-webhooks/01-webhooks.test.ts
similarity index 91%
rename from __tests__/docs-snippets/pages/moderation/11-guides/09-rule-triggered-webhooks.test.ts
rename to __tests__/docs-snippets/pages/moderation/08-webhooks/01-webhooks.test.ts
index 3f233628..66c63bbc 100644
--- a/__tests__/docs-snippets/pages/moderation/11-guides/09-rule-triggered-webhooks.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/08-webhooks/01-webhooks.test.ts
@@ -4,7 +4,7 @@ import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
-const DOCS = 'moderation/_default/11-guides/09-rule-triggered-webhooks.md';
+const DOCS = 'moderation/_default/08-webhooks/01-webhooks.md';
describe(DOCS, () => {
const client = getServerClient();
@@ -22,7 +22,7 @@ describe(DOCS, () => {
const ruleName = uniqueId('high-profile-harms');
const upsertRule = vi.spyOn(client.moderation, 'upsertModerationRule');
- // #region snippet docs="moderation/_default/11-guides/09-rule-triggered-webhooks.md" heading="Content rule: notify on a harm label" tab="Node.js" index=1
+ // #region snippet docs="moderation/_default/08-webhooks/01-webhooks.md" heading="Content rule: notify on a harm label" tab="Node.js" index=1
// COPY: configKey="chat:messaging", livestreamConfigKey="chat:livestream", ruleName="High-profile harms"
await client.moderation.upsertModerationRule({
name: ruleName,
diff --git a/__tests__/docs-snippets/pages/moderation/09-logs-and-analytics/01-logs-and-analytics.test.ts b/__tests__/docs-snippets/pages/moderation/09-logs-and-analytics/01-logs-and-analytics.test.ts
index b43a97e9..35a12026 100644
--- a/__tests__/docs-snippets/pages/moderation/09-logs-and-analytics/01-logs-and-analytics.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/09-logs-and-analytics/01-logs-and-analytics.test.ts
@@ -1,5 +1,4 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
-import type { StreamClient, UserResponse } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
@@ -82,95 +81,6 @@ describe(DOCS, () => {
);
});
- it.skip('BLOCKED: SDK issue: the node SDK has no exportModerationLogs (POST /api/v2/moderation/logs/export is `Ignore: true` in the spec)', async () => {
- // The node SDK has no exportModerationLogs: type the missing method (with the
- // request shape the docs use) so the skipped region typechecks and lints. It isn't
- // there at runtime.
- const client = getServerClient() as StreamClient & {
- moderation: {
- exportModerationLogs: (request: {
- filter: Record;
- }) => Promise<{ task_id: string }>;
- };
- };
-
- // #region snippet docs="moderation/_default/09-logs-and-analytics/01-logs-and-analytics.md" heading="Export Moderation Logs" tab="Node.js" index=1
- const response = await client.moderation.exportModerationLogs({
- filter: { created_at: { $gte: '2024-01-01T00:00:00Z' } },
- });
- // #endregion snippet
-
- expect(response.task_id).toBeTruthy();
- });
-
- it.skip('BLOCKED: SDK issue: the node SDK has no getModerationAnalytics (POST /api/v2/moderation/analytics is `Ignore: true` in the spec)', async () => {
- // The node SDK has no getModerationAnalytics: type the missing method (with the
- // request shape the docs use) so the skipped region typechecks and lints. It isn't
- // there at runtime.
- const client = getServerClient() as StreamClient & {
- moderation: {
- getModerationAnalytics: (request: {
- from: string;
- to: string;
- }) => Promise<{ analytics: Record }>;
- };
- };
-
- // #region snippet docs="moderation/_default/09-logs-and-analytics/01-logs-and-analytics.md" heading="Get Moderation Analytics" tab="Node.js" index=1
- const response = await client.moderation.getModerationAnalytics({
- from: '2024-01-01T00:00:00Z',
- to: '2024-01-31T23:59:59Z',
- });
- // #endregion snippet
-
- expect(response.analytics).toBeDefined();
- });
-
- it.skip('BLOCKED: SDK issue: the node SDK has no queryUsageStats (POST /api/v2/moderation/usage_stats is `Ignore: true` in the spec)', async () => {
- // The node SDK has no queryUsageStats: type the missing method (with the request
- // shape the docs use) so the skipped region typechecks and lints. It isn't there
- // at runtime.
- const client = getServerClient() as StreamClient & {
- moderation: {
- queryUsageStats: (request: {
- from: string;
- to: string;
- }) => Promise<{ items: unknown[] }>;
- };
- };
-
- // #region snippet docs="moderation/_default/09-logs-and-analytics/01-logs-and-analytics.md" heading="Query Usage Stats" tab="Node.js" index=1
- const response = await client.moderation.queryUsageStats({
- from: '2024-01-01T00:00:00Z',
- to: '2024-01-31T23:59:59Z',
- });
- // #endregion snippet
-
- expect(Array.isArray(response.items)).toBe(true);
- });
-
- it.skip('BLOCKED: SDK issue: the node SDK has no getUserModerationReport (GET /api/v2/moderation/user_report is left out of the spec)', async () => {
- const reportUser = await createMember('report-user');
- // The node SDK has no getUserModerationReport: type the missing method (from the
- // API's response) so the skipped region typechecks and lints. It isn't there at runtime.
- const client = getServerClient() as StreamClient & {
- moderation: {
- getUserModerationReport: (request: {
- user_id: string;
- }) => Promise<{ user: UserResponse }>;
- };
- };
-
- // #region snippet docs="moderation/_default/09-logs-and-analytics/01-logs-and-analytics.md" heading="Get User Moderation Report" tab="Node.js" index=1
- // COPY: reportUser="user_123"
- const response = await client.moderation.getUserModerationReport({
- user_id: reportUser,
- });
- // #endregion snippet
-
- expect(response.user.id).toBe(reportUser);
- });
-
it('gets the flag count of a user', async () => {
const flaggedUser = await createMember('flagged');
await flagMessage(flaggedUser);
diff --git a/__tests__/docs-snippets/pages/moderation/11-guides/06-bypass-user-from-moderation.test.ts b/__tests__/docs-snippets/pages/moderation/11-guides/06-bypass-user-from-moderation.test.ts
new file mode 100644
index 00000000..cc295e70
--- /dev/null
+++ b/__tests__/docs-snippets/pages/moderation/11-guides/06-bypass-user-from-moderation.test.ts
@@ -0,0 +1,37 @@
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { getServerClient } from '../../../helpers/clients';
+import { Cleanup } from '../../../helpers/cleanup';
+import { uniqueId } from '../../../helpers/ids';
+import { getServerUser } from '../../../helpers/server';
+
+const DOCS = 'moderation/_default/11-guides/06-bypass-user-from-moderation.md';
+
+describe(DOCS, () => {
+ const client = getServerClient();
+ const cleanup = new Cleanup(client);
+ // The docs' "user-123", test-owned.
+ const userId = uniqueId('user');
+
+ beforeAll(async () => {
+ cleanup.users.push(userId);
+ await client.upsertUsers([{ id: userId }]);
+ });
+
+ afterAll(() => cleanup.run());
+
+ it('enables moderation bypass for a user', async () => {
+ expect(
+ (await getServerUser(client, userId)).bypass_moderation ?? false,
+ ).toBe(false);
+
+ // #region snippet docs="moderation/_default/11-guides/06-bypass-user-from-moderation.md" heading="Enable or Disable Bypass" tab="Node.js" index=1
+ // COPY: userId="user-123"
+ await client.moderation.bypass({
+ target_user_id: userId,
+ enabled: true,
+ });
+ // #endregion snippet
+
+ expect((await getServerUser(client, userId)).bypass_moderation).toBe(true);
+ });
+});
diff --git a/__tests__/docs-snippets/pages/moderation/node/01-getting-started/01-getting-started.test.ts b/__tests__/docs-snippets/pages/moderation/node/01-getting-started/01-getting-started.test.ts
index 5b442fe4..7d578e4b 100644
--- a/__tests__/docs-snippets/pages/moderation/node/01-getting-started/01-getting-started.test.ts
+++ b/__tests__/docs-snippets/pages/moderation/node/01-getting-started/01-getting-started.test.ts
@@ -112,22 +112,22 @@ describe(DOCS, () => {
});
it('checks content', async () => {
- const messageId = uniqueId('message');
+ const postId = uniqueId('post');
const log = vi.spyOn(console, 'log').mockImplementation(() => undefined);
// #region snippet docs="moderation/node/01-getting-started/01-getting-started.md" heading="Check content" tab="Node.js" index=1
- // COPY: messageId="message-123", userId="user-456", myConfig="my_config"
+ // COPY: postId="post-123", userId="user-456", myConfig="my_config"
const response = await client.moderation.check({
- entity_type: 'stream:chat:v1:message',
- entity_id: messageId,
+ entity_type: 'post',
+ entity_id: postId,
entity_creator_id: userId,
moderation_payload: {
- texts: ['Hello, this is a test message'],
+ texts: ['Selling a bike, pickup in the city'],
},
config_key: myConfig,
});
- console.log(response.recommended_action); // "keep", "flag", or "remove"
+ console.log(response.recommended_action); // "keep", "flag", "remove", "shadow", or "bounce"
// #endregion snippet
expect(response.status).toBe('complete');
@@ -155,19 +155,25 @@ describe(DOCS, () => {
// #region snippet docs="moderation/node/01-getting-started/01-getting-started.md" heading="Handle the response" tab="Node.js" index=1
if (response.recommended_action === 'keep') {
- // Content is safe, no action needed
+ // Publish the content
} else if (response.recommended_action === 'flag') {
- // Content is suspicious; Stream has queued it for review
+ // Publish it, and review the queued item
console.log('Flagged for review:', response.item);
- } else if (response.recommended_action === 'remove') {
- // Content violates policies, remove it
- console.log('Content removed:', response.item);
+ } else if (response.recommended_action === 'shadow') {
+ // Show it only to the author
+ } else if (
+ response.recommended_action === 'remove' ||
+ response.recommended_action === 'bounce' ||
+ response.recommended_action === 'bounce_remove'
+ ) {
+ // Do not publish it. Removed items stay in the review queue.
+ console.log('Not published:', response.item);
}
// #endregion snippet
expect(response.recommended_action).toBe('remove');
expect(log.mock.calls).toHaveLength(1);
- expect(log.mock.calls[0][0]).toBe('Content removed:');
+ expect(log.mock.calls[0][0]).toBe('Not published:');
expect(log.mock.calls[0][1]).toBe(response.item);
expect(typeof response.item?.id).toBe('string');
});
diff --git a/__tests__/docs-snippets/pages/platform/02-identity-and-access/01-authentication.test.ts b/__tests__/docs-snippets/pages/platform/02-identity-and-access/01-authentication.test.ts
index e3f93b0f..0e428d2c 100644
--- a/__tests__/docs-snippets/pages/platform/02-identity-and-access/01-authentication.test.ts
+++ b/__tests__/docs-snippets/pages/platform/02-identity-and-access/01-authentication.test.ts
@@ -9,6 +9,7 @@ import {
it,
vi,
} from 'vitest';
+import { nsToMs } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
@@ -198,8 +199,7 @@ describe(DOCS, () => {
// Stored with sub-millisecond rounding: compare to the millisecond.
expect(
Math.abs(
- (user.revoke_tokens_issued_before?.getTime() ?? 0) -
- revokeDate.getTime(),
+ nsToMs(user.revoke_tokens_issued_before ?? 0) - revokeDate.getTime(),
),
).toBeLessThanOrEqual(1);
}
diff --git a/__tests__/docs-snippets/pages/platform/02-identity-and-access/02-users.test.ts b/__tests__/docs-snippets/pages/platform/02-identity-and-access/02-users.test.ts
index 6ac44594..081a3526 100644
--- a/__tests__/docs-snippets/pages/platform/02-identity-and-access/02-users.test.ts
+++ b/__tests__/docs-snippets/pages/platform/02-identity-and-access/02-users.test.ts
@@ -317,7 +317,7 @@ describe(DOCS, () => {
expect(response.users.length).toBeGreaterThan(0);
expect(response.users.length).toBeLessThanOrEqual(10);
expect(response.users.every((u) => u.role === 'admin')).toBe(true);
- const created = response.users.map((u) => u.created_at.getTime());
+ const created = response.users.map((u) => u.created_at);
expect(created).toEqual([...created].sort((a, b) => b - a));
// The newest admin is the one created just now.
expect(response.users.map((u) => u.id)).toContain(adminId);
@@ -451,7 +451,7 @@ describe(DOCS, () => {
(r) => r.value as Promise,
),
);
- expect(deactivated.user?.deactivated_at).toBeInstanceOf(Date);
+ expect(deactivated.user?.deactivated_at).toEqual(expect.any(Number));
// Bulk reactivation is asynchronous too.
const [reactivated] = await Promise.all(
reactivateUsers.mock.results.map(
@@ -462,7 +462,7 @@ describe(DOCS, () => {
expect((await findUser(userId))?.deactivated_at).toBeUndefined();
await waitForTask(client, deactivateResponse.task_id);
for (const id of [bulkUser1, bulkUser2]) {
- expect((await findUser(id))?.deactivated_at).toBeInstanceOf(Date);
+ expect((await findUser(id))?.deactivated_at).toEqual(expect.any(Number));
}
});
@@ -489,7 +489,7 @@ describe(DOCS, () => {
const { message: stored } = await client.chat.getMessage({
id: message.id,
});
- expect(stored.deleted_at).toBeInstanceOf(Date);
+ expect(stored.deleted_at).toEqual(expect.any(Number));
});
return result;
});
@@ -511,7 +511,7 @@ describe(DOCS, () => {
// #endregion snippet
expect(deactivate.user).toMatchObject({ id: userID });
- expect(deactivate.user?.deactivated_at).toBeInstanceOf(Date);
+ expect(deactivate.user?.deactivated_at).toEqual(expect.any(Number));
expect(reactivate.user).toMatchObject({ id: userID, name: 'I am back' });
expect(reactivate.user?.deactivated_at).toBeUndefined();
await retry(async () => {
diff --git a/__tests__/docs-snippets/pages/platform/03-events-and-notifications/04-push-devices.test.ts b/__tests__/docs-snippets/pages/platform/03-events-and-notifications/04-push-devices.test.ts
index e427bb83..99f80ea9 100644
--- a/__tests__/docs-snippets/pages/platform/03-events-and-notifications/04-push-devices.test.ts
+++ b/__tests__/docs-snippets/pages/platform/03-events-and-notifications/04-push-devices.test.ts
@@ -1,5 +1,5 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
-import type { DeviceResponse } from '../../../../../index';
+import type { DeviceResponse, TimestampNS } from '../../../../../index';
import { getServerClient } from '../../../helpers/clients';
import { Cleanup } from '../../../helpers/cleanup';
import { uniqueId } from '../../../helpers/ids';
@@ -128,9 +128,9 @@ describe(DOCS, () => {
it('device object (Listing from the feeds client SDKs)', () => {
// #region snippet docs="platform/03-events-and-notifications/04-push-devices.md" heading="Listing from the feeds client SDKs" tab="Node.js" index=2
type Device = {
- created_at: Date;
+ created_at: TimestampNS; // unix nanoseconds, nsToDate() converts it
id: string;
- push_provider: string;
+ push_provider: 'firebase' | 'apn' | 'huawei' | 'xiaomi';
user_id: string;
disabled?: boolean;
disabled_reason?: string;
diff --git a/__tests__/docs-snippets/pages/platform/03-events-and-notifications/05-push-preferences.test.ts b/__tests__/docs-snippets/pages/platform/03-events-and-notifications/05-push-preferences.test.ts
index 2fa17b89..450326fe 100644
--- a/__tests__/docs-snippets/pages/platform/03-events-and-notifications/05-push-preferences.test.ts
+++ b/__tests__/docs-snippets/pages/platform/03-events-and-notifications/05-push-preferences.test.ts
@@ -78,15 +78,8 @@ describe(DOCS, () => {
// #region snippet docs="platform/03-events-and-notifications/05-push-preferences.md" heading="Channel Configuration Push Level" tab="Node.js" index=1
// COPY: channelType="messaging", channelId="announcements"
// Set push_level on a channel type
- // automod, automod_behavior and max_message_length are required on this endpoint,
- // so carry the current values through to avoid overwriting them.
- const current = await client.chat.getChannelType({ name: channelType });
-
await client.chat.updateChannelType({
name: channelType,
- automod: current.automod,
- automod_behavior: current.automod_behavior,
- max_message_length: current.max_message_length,
push_level: 'all_mentions',
});
@@ -106,8 +99,8 @@ describe(DOCS, () => {
return response;
});
expect(updated.push_level).toBe('all_mentions');
- expect(updated.automod).toBe(current.automod);
- expect(updated.max_message_length).toBe(current.max_message_length);
+ expect(updated.automod).toBe(before.automod);
+ expect(updated.max_message_length).toBe(before.max_message_length);
// The override wins over the type's `all_mentions` in the merged channel config.
const { channel: response } = await channel.getOrCreate({});
@@ -273,34 +266,4 @@ describe(DOCS, () => {
},
);
});
-
- it('Setting Call Push Preferences', async () => {
- const upsert = vi.spyOn(client, 'updatePushNotificationPreferences');
-
- // #region snippet docs="platform/03-events-and-notifications/05-push-preferences.md" heading="Setting Call Push Preferences" tab="Node.js" index=1
- // COPY: userId1="user-1"
- // Set call-level preferences with temporary disabling
- const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
-
- await client.updatePushNotificationPreferences({
- preferences: [
- {
- user_id: userId1,
- call_level: 'all',
- disabled_until: oneHourFromNow,
- },
- ],
- });
- // #endregion snippet
-
- const [response] = await upsertResults(upsert);
- const preferences = response.user_preferences[userId1];
- expect(preferences?.call_level).toBe('all');
- expect(
- Math.abs(
- (preferences?.disabled_until?.getTime() ?? 0) -
- oneHourFromNow.getTime(),
- ),
- ).toBeLessThan(1000);
- });
});
diff --git a/__tests__/docs-snippets/pages/video/api/01-basics/03-calls.test.ts b/__tests__/docs-snippets/pages/video/api/01-basics/03-calls.test.ts
index 608f5d62..d651c6af 100644
--- a/__tests__/docs-snippets/pages/video/api/01-basics/03-calls.test.ts
+++ b/__tests__/docs-snippets/pages/video/api/01-basics/03-calls.test.ts
@@ -7,6 +7,7 @@ import {
it,
vi,
} from 'vitest';
+import { nsToMs } from '../../../../../../index';
import { OwnCapability } from '../../../../../../index';
import { getServerClient } from '../../../../helpers/clients';
import { Cleanup } from '../../../../helpers/cleanup';
@@ -315,7 +316,7 @@ describe(DOCS, () => {
// #endregion snippet
const { call: stored } = await call.get();
- expect(stored.ended_at).toBeInstanceOf(Date);
+ expect(stored.ended_at).toEqual(expect.any(Number));
});
it('queries calls with sorting and pagination', async () => {
@@ -403,7 +404,9 @@ describe(DOCS, () => {
>);
expect(calls.length).toBeGreaterThan(0);
for (const { call } of calls) {
- expect(call.starts_at?.getTime()).toBeGreaterThan(inNext30mins.getTime());
+ expect(nsToMs(call.starts_at ?? 0)).toBeGreaterThan(
+ inNext30mins.getTime(),
+ );
}
});
diff --git a/__tests__/docs-snippets/pages/video/api/01-basics/04-ring-calls.test.ts b/__tests__/docs-snippets/pages/video/api/01-basics/04-ring-calls.test.ts
index bc6c5473..d6cfb2da 100644
--- a/__tests__/docs-snippets/pages/video/api/01-basics/04-ring-calls.test.ts
+++ b/__tests__/docs-snippets/pages/video/api/01-basics/04-ring-calls.test.ts
@@ -153,6 +153,57 @@ describe(DOCS, () => {
expect(stored.settings.ring).toMatchObject(expected);
});
+ it('sets the lifecycle mode on a call type', async () => {
+ const callTypeName = await createCallType(client, cleanup);
+ expect(
+ (await client.video.getCallType({ name: callTypeName })).settings
+ .lifecycle.mode,
+ ).toBe('reusable');
+
+ // #region snippet docs="video/api/01-basics/04-ring-calls.md" heading="Setting the mode" tab="Node.js" index=1
+ await client.video.updateCallType({
+ name: callTypeName,
+ settings: {
+ lifecycle: { mode: 'single_use' },
+ },
+ });
+ // #endregion snippet
+
+ const callType = await client.video.getCallType({ name: callTypeName });
+ expect(callType.settings.lifecycle.mode).toBe('single_use');
+ });
+
+ it('sets the lifecycle mode on one call', async () => {
+ const getOrCreate = vi.spyOn(client.video, 'getOrCreateCall');
+
+ // #region snippet docs="video/api/01-basics/04-ring-calls.md" heading="Setting the mode" tab="Node.js" index=2
+ // COPY: myselfId="myself", friendId="my friend"
+ const call = client.video.call('default', crypto.randomUUID());
+ await call.getOrCreate({
+ ring: true,
+ data: {
+ created_by_id: myselfId,
+ members: [{ user_id: myselfId }, { user_id: friendId }],
+ settings_override: {
+ lifecycle: { mode: 'single_use' },
+ },
+ },
+ });
+ // #endregion snippet
+ cleanup.calls.push(call.cid);
+
+ const response = await (getOrCreate.mock.results[0].value as ReturnType<
+ typeof client.video.getOrCreateCall
+ >);
+ expect(response.created).toBe(true);
+ expect(response.call.settings.lifecycle.mode).toBe('single_use');
+ // The built-in `default` type itself stays reusable.
+ expect(
+ (await client.video.getCallType({ name: 'default' })).settings.lifecycle
+ .mode,
+ ).toBe('reusable');
+ });
+
it('configures push notifications on a call type', async () => {
const pushCallType = uniqueId('call-type');
const create = trackCallTypeCreates(client, cleanup);
diff --git a/__tests__/docs-snippets/pages/video/api/03-streaming/02-backstage.test.ts b/__tests__/docs-snippets/pages/video/api/03-streaming/02-backstage.test.ts
index 8141f4aa..b46ceba3 100644
--- a/__tests__/docs-snippets/pages/video/api/03-streaming/02-backstage.test.ts
+++ b/__tests__/docs-snippets/pages/video/api/03-streaming/02-backstage.test.ts
@@ -7,6 +7,7 @@ import {
it,
vi,
} from 'vitest';
+import { nsToMs } from '../../../../../../index';
import { OwnCapability } from '../../../../../../index';
import { createCallType } from '../../../../helpers/call-types';
import { getServerClient } from '../../../../helpers/clients';
@@ -69,7 +70,7 @@ describe(DOCS, () => {
>);
expect(response.created).toBe(true);
expect(response.call.backstage).toBe(true);
- expect(response.call.starts_at?.getTime()).toBe(startsAt.getTime());
+ expect(nsToMs(response.call.starts_at ?? 0)).toBe(startsAt.getTime());
expect(response.call.join_ahead_time_seconds).toBe(300);
expect(response.call.settings.backstage).toMatchObject({
enabled: true,
diff --git a/__tests__/docs-snippets/pages/video/api/04-quality/03-stats.test.ts b/__tests__/docs-snippets/pages/video/api/04-quality/03-stats.test.ts
index d82e5b5e..846e237d 100644
--- a/__tests__/docs-snippets/pages/video/api/04-quality/03-stats.test.ts
+++ b/__tests__/docs-snippets/pages/video/api/04-quality/03-stats.test.ts
@@ -21,11 +21,9 @@ describe(DOCS, () => {
// #endregion snippet
// The status covers the last minute.
- expect(response.start_time).toBeInstanceOf(Date);
- expect(response.end_time).toBeInstanceOf(Date);
- expect(
- response.end_time.getTime() - response.start_time.getTime(),
- ).toBeGreaterThan(0);
+ expect(response.start_time).toEqual(expect.any(Number));
+ expect(response.end_time).toEqual(expect.any(Number));
+ expect(response.end_time - response.start_time).toBeGreaterThan(0);
// The test app usually has no running calls, so the numbers can be 0.
expect(typeof response.summary?.active_calls).toBe('number');
expect(typeof response.summary?.active_publishers).toBe('number');
diff --git a/__tests__/docs-snippets/pages/video/api/06-moderation/01-overview.test.ts b/__tests__/docs-snippets/pages/video/api/06-moderation/01-overview.test.ts
index bf0d832d..f58e92a0 100644
--- a/__tests__/docs-snippets/pages/video/api/06-moderation/01-overview.test.ts
+++ b/__tests__/docs-snippets/pages/video/api/06-moderation/01-overview.test.ts
@@ -14,6 +14,7 @@ import type {
GetBlockedUsersResponse,
ModerationBanResponse,
MuteUsersResponse,
+ TimestampNS,
UpdateUserPermissionsResponse,
} from '../../../../../../index';
import { getServerClient } from '../../../../helpers/clients';
@@ -217,7 +218,10 @@ describe(DOCS, () => {
const originalBan = client.moderation.ban.bind(client.moderation);
const ban = vi.spyOn(client.moderation, 'ban');
const unban = vi.spyOn(client.moderation, 'unban');
- const bansAfterEachCall: Array<{ reason?: string; expires?: Date }> = [];
+ const bansAfterEachCall: Array<{
+ reason?: string;
+ expires?: TimestampNS;
+ }> = [];
ban.mockImplementation(async (request) => {
const response = await originalBan(request);
const { bans } = await client.queryBannedUsers({
@@ -267,7 +271,7 @@ describe(DOCS, () => {
expect(bansAfterEachCall).toHaveLength(3);
expect(bansAfterEachCall[0]).toMatchObject({ reason: banReason });
expect(bansAfterEachCall[0].expires).toBeUndefined();
- expect(bansAfterEachCall[1].expires).toBeInstanceOf(Date);
+ expect(bansAfterEachCall[1].expires).toEqual(expect.any(Number));
const { bans } = await client.queryBannedUsers({
payload: { filter_conditions: { user_id: badUserId } },
});
@@ -318,7 +322,7 @@ describe(DOCS, () => {
expect((await findUser(userId))?.deactivated_at).toBeUndefined();
await waitForTask(client, deactivateResponse.task_id);
for (const id of [bulkUser1, bulkUser2]) {
- expect((await findUser(id))?.deactivated_at).toBeInstanceOf(Date);
+ expect((await findUser(id))?.deactivated_at).toEqual(expect.any(Number));
}
});
diff --git a/__tests__/docs-snippets/sync/docs-sync.test.ts b/__tests__/docs-snippets/sync/docs-sync.test.ts
index ec2050f8..c070d5b4 100644
--- a/__tests__/docs-snippets/sync/docs-sync.test.ts
+++ b/__tests__/docs-snippets/sync/docs-sync.test.ts
@@ -22,8 +22,8 @@ import type { DocsFence, TestRegion } from './parse';
// `yarn test:docs:sync`, in this order:
// 1. Every `Node.js` fence (except comment-only fences) on the pages of the tested
// sidebars (`SIDEBAR_FILES` in parse.ts) has a test region. `JavaScript`, `React` and
-// `React Native` (client-side) fences, the stream-chat-js@9 fences
-// (`JavaScript (stream-chat-js@9)`, `Node.js (stream-chat-js@9)`), and fences inside a
+// `React Native` (client-side) fences, the stream-chat@9 fences
+// (`JavaScript (stream-chat@9)`, `Node.js (stream-chat@9)`), and fences inside a
// `` block, are out of scope.
// 2. Every JS fence on those pages is labelled with one of `ALLOWED_JS_LABELS`.
// 3. Every marked test region points at an existing fence and matches it (after
@@ -100,7 +100,7 @@ describe.skipIf(!hasDocs)(`docs sync (${DOCS_DIR})`, () => {
);
expect(
mislabelled,
- `JS fences need label="${ALLOWED_JS_LABELS.join('" or label="')}" (client-side code, @stream-io/node-sdk code, or their stream-chat-js@9 versions)`,
+ `JS fences need label="${ALLOWED_JS_LABELS.join('" or label="')}" (client-side code, @stream-io/node-sdk code, or their stream-chat@9 versions)`,
).toEqual([]);
});
diff --git a/__tests__/docs-snippets/sync/parse.ts b/__tests__/docs-snippets/sync/parse.ts
index a7d8ed06..9b282823 100644
--- a/__tests__/docs-snippets/sync/parse.ts
+++ b/__tests__/docs-snippets/sync/parse.ts
@@ -56,14 +56,14 @@ export const JS_TAB = 'JavaScript';
/** The tested tab (`@stream-io/node-sdk` code). */
export const NODE_TAB = 'Node.js';
/**
- * Out of scope too: client-side React / React Native SDK code, and the stream-chat-js@9
+ * Out of scope too: client-side React / React Native SDK code, and the stream-chat@9
* versions shown next to `JavaScript` (client-side) and `Node.js` (server-side).
*/
const OUT_OF_SCOPE_TABS = [
'React',
'React Native',
- 'JavaScript (stream-chat-js@9)',
- 'Node.js (stream-chat-js@9)',
+ 'JavaScript (stream-chat@9)',
+ 'Node.js (stream-chat@9)',
];
/**
* Opens a block of reference implementations that don't use the SDK (platform pages).
@@ -99,7 +99,10 @@ const PAGE_PREFIXES = [
* Sidebar pages that are deliberately not validated (page path relative to
* `content/docs`, reason).
*/
-export const EXCLUDED_PAGES: Record = {};
+export const EXCLUDED_PAGES: Record = {
+ 'activity-feeds/v3-latest/_default/13-importing_data/03_v2_to_v3_code_migration.md':
+ 'Feeds v2 to v3 porting guide: before/after code in fences titled "Feeds v2" / "Feeds v3", not SDK tabs (a `label` would replace the visible title)',
+};
const FEEDS_V2_CLIENT =
'Feeds v2 code for the `getstream` package (a different SDK), not @stream-io/node-sdk';
diff --git a/__tests__/integration/agent.test.ts b/__tests__/integration/agent.test.ts
index fe9c8591..2a8802ed 100644
--- a/__tests__/integration/agent.test.ts
+++ b/__tests__/integration/agent.test.ts
@@ -67,7 +67,7 @@ describe.skip('AI agent integration', () => {
const [, agent] = await createTestStreamAndRealtimeClients();
let errorEvent: any = null;
- agent.on('realtime.event', ({ event }) => {
+ agent.on('realtime.event', ({ event }: { event: any }) => {
if (event.type === 'error') {
errorEvent = event;
}
@@ -147,7 +147,7 @@ describe.skip('AI agent integration', () => {
required: ['lat', 'lng', 'location'],
},
},
- async ({ lat, lng }) => {
+ async ({ lat, lng }: { lat: number; lng: number }) => {
const result = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}¤t=temperature_2m,wind_speed_10m`,
);
diff --git a/__tests__/integration/date-transform.test.ts b/__tests__/integration/date-transform.test.ts
index 0eb59d8a..392e587b 100644
--- a/__tests__/integration/date-transform.test.ts
+++ b/__tests__/integration/date-transform.test.ts
@@ -3,8 +3,9 @@ import { randomUUID } from 'crypto';
import { createTestClient } from './create-test-client';
import { StreamClient } from '../../src/StreamClient';
import { UserRequest } from '../../src/gen/models';
+import { nsToDate, nsToMs } from '../../src/utils/time';
-describe('Date conversion', () => {
+describe('dates on responses', () => {
let client: StreamClient;
const user = {
id: 'stream-node-test-user',
@@ -36,18 +37,18 @@ describe('Date conversion', () => {
const createdAt = response.call.created_at;
- expect(createdAt instanceof Date).toBe(true);
- expect(now - createdAt.getTime()).toBeLessThan(oneMin);
+ expect(typeof createdAt).toBe('number');
+ expect(now - nsToMs(createdAt)).toBeLessThan(oneMin);
- expect(response.call.starts_at instanceof Date).toBe(true);
- expect(startsAt.getTime()).toEqual(response.call.starts_at?.getTime());
+ expect(typeof response.call.starts_at).toBe('number');
+ expect(startsAt.getTime()).toEqual(nsToMs(response.call.starts_at!));
expect(response.call.ended_at).toBeNull();
expect(response.members.length).toBeGreaterThan(0);
response.members.forEach((m) => {
- expect(m.created_at instanceof Date).toBe(true);
+ expect(typeof m.created_at).toBe('number');
});
const queryResult = await client.video.queryCalls({
@@ -75,28 +76,26 @@ describe('Date conversion', () => {
const createdAt = response.channel!.created_at;
- expect(createdAt instanceof Date).toBe(true);
- expect(now - createdAt.getTime()).toBeLessThan(oneMin);
+ expect(typeof createdAt).toBe('number');
+ expect(now - nsToMs(createdAt)).toBeLessThan(oneMin);
expect(response.channel!.deleted_at).toBeUndefined();
expect(response.members.length).toBeGreaterThan(0);
response.members.forEach((m) => {
- expect(m.created_at instanceof Date).toBe(true);
+ expect(typeof m.created_at).toBe('number');
});
const queryResult = await client.chat.queryChannels({
filter_conditions: {
- created_at: { $lte: createdAt.toISOString() },
+ created_at: { $lte: nsToDate(createdAt).toISOString() },
},
limit: 10,
});
queryResult.channels.forEach((c) => {
- expect(c.channel!.created_at.getTime()).toBeLessThanOrEqual(
- createdAt.getTime(),
- );
+ expect(c.channel!.created_at).toBeLessThanOrEqual(createdAt);
});
await channel.delete();
@@ -113,7 +112,7 @@ describe('Date conversion', () => {
const response = await client.upsertUsers([newUser]);
- expect(response.users[newUser.id].created_at instanceof Date).toBe(true);
+ expect(typeof response.users[newUser.id].created_at).toBe('number');
expect(
typeof response.users[newUser.id].custom.created_at === 'string',
diff --git a/__tests__/integration/devices-push.test.ts b/__tests__/integration/devices-push.test.ts
index 30aad6ff..c665fdef 100644
--- a/__tests__/integration/devices-push.test.ts
+++ b/__tests__/integration/devices-push.test.ts
@@ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from 'vitest';
import { randomUUID } from 'crypto';
import { createTestClient } from './create-test-client';
import { StreamClient } from '../../src/StreamClient';
-import { CreateDeviceRequest, PushProvider } from '../../src/gen/models';
+import { CreateDeviceRequest, PushProviderRequest } from '../../src/gen/models';
describe('devices and push', () => {
let client: StreamClient;
@@ -16,7 +16,7 @@ describe('devices and push', () => {
push_provider_name: 'firebase',
user_id: user.id,
};
- const pushProvider: PushProvider = {
+ const pushProvider: PushProviderRequest = {
name: 'test-push-provider',
type: 'xiaomi',
xiaomi_app_secret: '',
diff --git a/__tests__/integration/messages.test.ts b/__tests__/integration/messages.test.ts
index e6fa2db4..dafa4326 100644
--- a/__tests__/integration/messages.test.ts
+++ b/__tests__/integration/messages.test.ts
@@ -57,7 +57,6 @@ describe('messages API', () => {
});
it('thread replies', async () => {
- const now = new Date();
const response = await channel.sendMessage({
message: {
text: 'Hello from Stream Node SDK',
@@ -77,7 +76,6 @@ describe('messages API', () => {
const getResponse = await client.chat.getReplies({
parent_id: response.message.id,
- created_at_after: now,
});
expect(
diff --git a/__tests__/integration/rate-limit.test.ts b/__tests__/integration/rate-limit.test.ts
index 425a3d7e..361e62be 100644
--- a/__tests__/integration/rate-limit.test.ts
+++ b/__tests__/integration/rate-limit.test.ts
@@ -1,6 +1,7 @@
import { beforeAll, describe, expect, it } from 'vitest';
import { createTestClient } from './create-test-client';
import { StreamClient } from '../../src/StreamClient';
+import { StreamError } from '../../src/types';
describe('rate limit', () => {
let client: StreamClient;
@@ -61,12 +62,14 @@ describe('rate limit', () => {
try {
await call.startTranscription();
throw new Error(`Method didn't throw`);
- } catch (error) {
+ } catch (err) {
+ const error = err as StreamError;
+
expect(error.code).toBeDefined();
expect(error.metadata).toBeDefined();
expect(error.metadata.responseCode).toBe(404);
- const rateLimit = error.metadata.rateLimit;
+ const rateLimit = error.metadata.rateLimit!;
expect(rateLimit.rateLimit).toBeDefined();
}
diff --git a/generate-openapi.sh b/generate-openapi.sh
index 7f360b12..0fbc4d20 100755
--- a/generate-openapi.sh
+++ b/generate-openapi.sh
@@ -6,6 +6,6 @@ CHAT_DIR="../chat"
rm -rf $OUTPUT_DIR
-( cd $CHAT_DIR ; make openapi ; make -C tools/openapi build ; ./build/openapi generate-client --language ts --spec ./releases/v2/serverside-api.yaml --output $OUTPUT_DIR )
+( cd $CHAT_DIR ; make openapi ; make -C tools/openapi build ; ./build/openapi generate-client --language ts --spec ./releases/v2/serverside-api.yaml --opt response_dates_as_number=true --output $OUTPUT_DIR )
yarn lint:gen
diff --git a/index.ts b/index.ts
index 969e9ca3..62427801 100644
--- a/index.ts
+++ b/index.ts
@@ -11,4 +11,14 @@ export {
InvalidWebhookErrorMessages,
verifyAndParseWebhook,
} from './src/utils/webhook';
+export {
+ convertTimestampToDate,
+ dateToNs,
+ msToNs,
+ nowNs,
+ NS_PER_MS,
+ nsToDate,
+ nsToMs,
+ nsToRfc3339,
+} from './src/utils/time';
export { StreamError } from './src/types';
diff --git a/src/gen/chat/ChannelApi.ts b/src/gen/chat/ChannelApi.ts
index 1c4f16c5..435c1b97 100644
--- a/src/gen/chat/ChannelApi.ts
+++ b/src/gen/chat/ChannelApi.ts
@@ -4,6 +4,10 @@ import {
ChannelStateResponse,
DeleteChannelResponse,
EventResponse,
+ ForkChannelRequest,
+ ForkChannelResponse,
+ ForkMessageIDsRequest,
+ ForkMessageIDsResponse,
GetDraftResponse,
GetManyMessagesResponse,
GetPinnedMessagesResponse,
@@ -15,6 +19,8 @@ import {
QueryChannelBansRequest,
QueryChannelBansResponse,
Response,
+ RewindChannelRequest,
+ RewindChannelResponse,
SendEventRequest,
SendMessageRequest,
SendMessageResponse,
@@ -204,6 +210,38 @@ export class ChannelApi {
});
}
+ fork(
+ request: ForkChannelRequest,
+ ): Promise> {
+ if (!this.id) {
+ throw new Error(
+ `Channel isn't yet created, call getOrCreateDistinctChannel() before this operation`,
+ );
+ }
+
+ return this.chatApi.forkChannel({
+ id: this.id,
+ type: this.type,
+ ...request,
+ });
+ }
+
+ forkMessageIDs(
+ request: ForkMessageIDsRequest,
+ ): Promise> {
+ if (!this.id) {
+ throw new Error(
+ `Channel isn't yet created, call getOrCreateDistinctChannel() before this operation`,
+ );
+ }
+
+ return this.chatApi.forkMessageIDs({
+ id: this.id,
+ type: this.type,
+ ...request,
+ });
+ }
+
hide(
request?: HideChannelRequest,
): Promise> {
@@ -359,6 +397,22 @@ export class ChannelApi {
return this.chatApi.markRead({ id: this.id, type: this.type, ...request });
}
+ rewind(
+ request: RewindChannelRequest,
+ ): Promise> {
+ if (!this.id) {
+ throw new Error(
+ `Channel isn't yet created, call getOrCreateDistinctChannel() before this operation`,
+ );
+ }
+
+ return this.chatApi.rewindChannel({
+ id: this.id,
+ type: this.type,
+ ...request,
+ });
+ }
+
show(
request?: ShowChannelRequest,
): Promise> {
diff --git a/src/gen/chat/ChatApi.ts b/src/gen/chat/ChatApi.ts
index ea7c71b8..5d77ef60 100644
--- a/src/gen/chat/ChatApi.ts
+++ b/src/gen/chat/ChatApi.ts
@@ -23,6 +23,8 @@ import {
DeleteChannelsRequest,
DeleteChannelsResponse,
DeleteCommandResponse,
+ DeleteHistoryWindowTeamsRequest,
+ DeleteHistoryWindowTeamsResponse,
DeleteMessageResponse,
DeleteReactionResponse,
DeleteReminderResponse,
@@ -33,10 +35,15 @@ import {
EventResponse,
ExportChannelsRequest,
ExportChannelsResponse,
+ ForkChannelRequest,
+ ForkChannelResponse,
+ ForkMessageIDsRequest,
+ ForkMessageIDsResponse,
GetCampaignResponse,
GetChannelTypeResponse,
GetCommandResponse,
GetDraftResponse,
+ GetHistoryWindowTeamsResponse,
GetManyMessagesResponse,
GetMessageResponse,
GetPinnedMessagesResponse,
@@ -99,6 +106,8 @@ import {
QueryThreadsRequest,
QueryThreadsResponse,
Response,
+ RewindChannelRequest,
+ RewindChannelResponse,
SearchPayload,
SearchResponse,
SendEventRequest,
@@ -108,6 +117,8 @@ import {
SendReactionResponse,
SendScheduledMessageNowResponse,
SendUserCustomEventRequest,
+ SetHistoryWindowTeamsRequest,
+ SetHistoryWindowTeamsResponse,
SetRetentionPolicyRequest,
SetRetentionPolicyResponse,
ShowChannelRequest,
@@ -507,6 +518,7 @@ export class ChatApi {
};
const body = {
hide_for_creator: request?.hide_for_creator,
+ include_scheduled_messages: request?.include_scheduled_messages,
state: request?.state,
thread_unread_counts: request?.thread_unread_counts,
member_custom_include: request?.member_custom_include,
@@ -829,6 +841,60 @@ export class ChatApi {
return response;
}
+ async forkChannel(
+ request: ForkChannelRequest & { type: string; id: string },
+ ): Promise> {
+ const pathParams = {
+ type: request?.type,
+ id: request?.id,
+ };
+ const body = {
+ message_id: request?.message_id,
+ channel: request?.channel,
+ exclude_attachments: request?.exclude_attachments,
+ user_id: request?.user_id,
+ user: request?.user,
+ };
+
+ const response = await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/chat/channels/{type}/{id}/fork',
+ pathParams,
+ undefined,
+ body,
+ );
+
+ decoders['ForkChannelResponse']?.(response);
+
+ return response;
+ }
+
+ async forkMessageIDs(
+ request: ForkMessageIDsRequest & { type: string; id: string },
+ ): Promise> {
+ const pathParams = {
+ type: request?.type,
+ id: request?.id,
+ };
+ const body = {
+ source_message_ids: request?.source_message_ids,
+ user_id: request?.user_id,
+ user: request?.user,
+ };
+
+ const response = await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/chat/channels/{type}/{id}/fork/message_ids',
+ pathParams,
+ undefined,
+ body,
+ );
+
+ decoders['ForkMessageIDsResponse']?.(response);
+
+ return response;
+ }
+
async hideChannel(
request: HideChannelRequest & { type: string; id: string },
): Promise> {
@@ -1063,6 +1129,7 @@ export class ChatApi {
};
const body = {
hide_for_creator: request?.hide_for_creator,
+ include_scheduled_messages: request?.include_scheduled_messages,
state: request?.state,
thread_unread_counts: request?.thread_unread_counts,
member_custom_include: request?.member_custom_include,
@@ -1112,6 +1179,34 @@ export class ChatApi {
return response;
}
+ async rewindChannel(
+ request: RewindChannelRequest & { type: string; id: string },
+ ): Promise> {
+ const pathParams = {
+ type: request?.type,
+ id: request?.id,
+ };
+ const body = {
+ expected_last_message_id: request?.expected_last_message_id,
+ message_id: request?.message_id,
+ inclusive: request?.inclusive,
+ user_id: request?.user_id,
+ user: request?.user,
+ };
+
+ const response = await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/chat/channels/{type}/{id}/rewind',
+ pathParams,
+ undefined,
+ body,
+ );
+
+ decoders['RewindChannelResponse']?.(response);
+
+ return response;
+ }
+
async showChannel(
request: ShowChannelRequest & { type: string; id: string },
): Promise> {
@@ -1515,6 +1610,64 @@ export class ChatApi {
return response;
}
+ async getHistoryWindowTeams(): Promise<
+ StreamResponse
+ > {
+ const response =
+ await this.apiClient.sendRequest(
+ 'GET',
+ '/api/v2/chat/history_window/teams',
+ undefined,
+ undefined,
+ );
+
+ decoders['GetHistoryWindowTeamsResponse']?.(response);
+
+ return response;
+ }
+
+ async setHistoryWindowTeams(
+ request: SetHistoryWindowTeamsRequest,
+ ): Promise> {
+ const body = {
+ teams: request?.teams,
+ };
+
+ const response =
+ await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/chat/history_window/teams',
+ undefined,
+ undefined,
+ body,
+ );
+
+ decoders['SetHistoryWindowTeamsResponse']?.(response);
+
+ return response;
+ }
+
+ async deleteHistoryWindowTeams(
+ request: DeleteHistoryWindowTeamsRequest,
+ ): Promise> {
+ const body = {
+ teams: request?.teams,
+ };
+
+ const response =
+ await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/chat/history_window/teams/delete',
+ undefined,
+ undefined,
+ body,
+ );
+
+ decoders['DeleteHistoryWindowTeamsResponse']?.(response);
+
+ return response;
+ }
+
async queryMembers(request?: {
payload?: QueryMembersPayload;
}): Promise> {
@@ -2810,9 +2963,11 @@ export class ChatApi {
async unreadCounts(request?: {
user_id?: string;
+ include_large_channels?: boolean;
}): Promise> {
const queryParams = {
user_id: request?.user_id,
+ include_large_channels: request?.include_large_channels,
};
const response =
diff --git a/src/gen/common/CommonApi.ts b/src/gen/common/CommonApi.ts
index 557da90d..61eaff68 100644
--- a/src/gen/common/CommonApi.ts
+++ b/src/gen/common/CommonApi.ts
@@ -14,6 +14,8 @@ import {
CheckSNSResponse,
CheckSQSRequest,
CheckSQSResponse,
+ ClaimGuestUserRequest,
+ ClaimGuestUserResponse,
CreateBlockListRequest,
CreateBlockListResponse,
CreateDeviceRequest,
@@ -76,6 +78,7 @@ import {
PollOptionResponse,
PollResponse,
PollVotesResponse,
+ PushProviderName,
QueryPollVotesRequest,
QueryPollsRequest,
QueryPollsResponse,
@@ -186,6 +189,7 @@ export class CommonApi {
sqs_key: request?.sqs_key,
sqs_secret: request?.sqs_secret,
sqs_url: request?.sqs_url,
+ two_way_block_effect_enabled: request?.two_way_block_effect_enabled,
user_response_time_enabled: request?.user_response_time_enabled,
video_primary_use_case: request?.video_primary_use_case,
webhook_url: request?.webhook_url,
@@ -1430,7 +1434,7 @@ export class CommonApi {
}
async deletePushProvider(request: {
- type: string;
+ type: PushProviderName;
name: string;
}): Promise> {
const pathParams = {
@@ -1451,7 +1455,7 @@ export class CommonApi {
}
async getPushTemplates(request: {
- push_provider_type: string;
+ push_provider_type: PushProviderName;
push_provider_name?: string;
}): Promise> {
const queryParams = {
@@ -2008,6 +2012,27 @@ export class CommonApi {
return response;
}
+ async claimGuestUser(
+ request: ClaimGuestUserRequest,
+ ): Promise> {
+ const body = {
+ guest_user_id: request?.guest_user_id,
+ user_id: request?.user_id,
+ };
+
+ const response = await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/users/claim_guest',
+ undefined,
+ undefined,
+ body,
+ );
+
+ decoders['ClaimGuestUserResponse']?.(response);
+
+ return response;
+ }
+
async deactivateUsers(
request: DeactivateUsersRequest,
): Promise> {
diff --git a/src/gen/feeds/FeedsApi.ts b/src/gen/feeds/FeedsApi.ts
index 3bde870a..43bb2065 100644
--- a/src/gen/feeds/FeedsApi.ts
+++ b/src/gen/feeds/FeedsApi.ts
@@ -24,6 +24,7 @@ import {
BatchQueryActivityReactionsResponse,
BatchQueryCommentReactionsRequest,
BatchQueryCommentReactionsResponse,
+ CancelExportFeedsResponse,
CastPollVoteRequest,
ChangeFeedVisibilityRequest,
ChangeFeedVisibilityResponse,
@@ -60,6 +61,9 @@ import {
DeleteFeedsRetentionPolicyResponse,
DeleteUserInterestsResponse,
ExportFeedUserDataResponse,
+ ExportFeedsByTypeRequest,
+ ExportFeedsResponse,
+ FeedVisibility,
FollowBatchRequest,
FollowBatchResponse,
FollowRequest,
@@ -1675,6 +1679,64 @@ export class FeedsApi {
return response;
}
+ async exportFeedsApp(): Promise> {
+ const response = await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/feeds/export/app',
+ undefined,
+ undefined,
+ );
+
+ decoders['ExportFeedsResponse']?.(response);
+
+ return response;
+ }
+
+ async cancelExportFeeds(request: {
+ task_id: string;
+ }): Promise> {
+ const pathParams = {
+ task_id: request?.task_id,
+ };
+
+ const response =
+ await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/feeds/export/{task_id}/cancel',
+ pathParams,
+ undefined,
+ );
+
+ decoders['CancelExportFeedsResponse']?.(response);
+
+ return response;
+ }
+
+ async exportFeedsByType(
+ request: ExportFeedsByTypeRequest & { type: string },
+ ): Promise> {
+ const pathParams = {
+ type: request?.type,
+ };
+ const body = {
+ created_at_from: request?.created_at_from,
+ created_at_to: request?.created_at_to,
+ user_id: request?.user_id,
+ };
+
+ const response = await this.apiClient.sendRequest(
+ 'POST',
+ '/api/v2/feeds/export/{type}',
+ pathParams,
+ undefined,
+ body,
+ );
+
+ decoders['ExportFeedsResponse']?.(response);
+
+ return response;
+ }
+
async listFeedGroups(request?: {
include_soft_deleted?: boolean;
}): Promise> {
@@ -2446,7 +2508,7 @@ export class FeedsApi {
}
async getFeedVisibility(request: {
- name: string;
+ name: FeedVisibility;
}): Promise> {
const pathParams = {
name: request?.name,
@@ -2466,7 +2528,7 @@ export class FeedsApi {
}
async updateFeedVisibility(
- request: UpdateFeedVisibilityRequest & { name: string },
+ request: UpdateFeedVisibilityRequest & { name: FeedVisibility },
): Promise> {
const pathParams = {
name: request?.name,
@@ -2699,6 +2761,7 @@ export class FeedsApi {
return response;
}
+ /** @deprecated This endpoint is deprecated. */
async followBatch(
request: FollowBatchRequest,
): Promise> {
@@ -3082,6 +3145,7 @@ export class FeedsApi {
return response;
}
+ /** @deprecated This endpoint is deprecated. */
async unfollowBatch(
request: UnfollowBatchRequest,
): Promise> {
diff --git a/src/gen/model-decoders/decoders.ts b/src/gen/model-decoders/decoders.ts
index 73661040..a43d0fb7 100644
--- a/src/gen/model-decoders/decoders.ts
+++ b/src/gen/model-decoders/decoders.ts
@@ -34,6494 +34,25 @@ const decode = (typeMappings: TypeMapping, input?: Record) => {
return input;
};
-decoders['AcceptFeedMemberInviteResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- member: { type: 'FeedMemberResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AcceptFollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follow: { type: 'FollowResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActionLogResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
-
- target_user: { type: 'UserResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityFeedbackEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityMarkEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityPinResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityPinnedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- pinned_activity: { type: 'PinActivityResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityReactionAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityReactionDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityReactionUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityRemovedFromFeedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- comments: { type: 'CommentResponse', isSingle: false },
-
- latest_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_bookmarks: { type: 'BookmarkResponse', isSingle: false },
-
- own_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- collections: { type: 'EnrichedCollectionResponse', isSingle: false },
-
- reaction_groups: { type: 'FeedsReactionGroupResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- edited_at: { type: 'DatetimeType', isSingle: true },
-
- expires_at: { type: 'DatetimeType', isSingle: true },
-
- friend_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- latest_shares: { type: 'ShareResponse', isSingle: false },
-
- current_feed: { type: 'FeedResponse', isSingle: true },
-
- parent: { type: 'ActivityResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityRestoredEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivitySelectorConfigResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- cutoff_time: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityUnpinnedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- pinned_activity: { type: 'PinActivityResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ActivityUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddBookmarkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bookmark: { type: 'BookmarkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddCommentBookmarkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bookmark: { type: 'BookmarkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddCommentReactionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comment: { type: 'CommentResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- reference_activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddCommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comment: { type: 'CommentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddCommentsBatchResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comments: { type: 'CommentResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddReactionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- reference_activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AddUserGroupMembersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user_group: { type: 'UserGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AggregatedActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- activities: { type: 'ActivityResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
decoders['AppResponseFields'] = (input?: { [key: string]: any }) => {
const typeMappings: TypeMapping = {
event_hooks: { type: 'EventHook', isSingle: false },
-
- call_types: { type: 'CallType', isSingle: false },
-
- channel_configs: { type: 'ChannelConfig', isSingle: false },
-
- push_notifications: { type: 'PushNotificationFields', isSingle: true },
-
- revoke_tokens_issued_before: { type: 'DatetimeType', isSingle: true },
};
return decode(typeMappings, input);
};
-decoders['AppealAcceptedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- appeal: { type: 'AppealItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AppealCreatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- appeal: { type: 'AppealItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AppealItemResponse'] = (input?: { [key: string]: any }) => {
+decoders['EventHook'] = (input?: { [key: string]: any }) => {
const typeMappings: TypeMapping = {
created_at: { type: 'DatetimeType', isSingle: true },
updated_at: { type: 'DatetimeType', isSingle: true },
-
- actions: { type: 'ActionLogResponse', isSingle: false },
-
- flags: { type: 'ModerationFlagResponse', isSingle: false },
-
- moderation_action: { type: 'ActionLogResponse', isSingle: true },
-
- original_moderation_action: { type: 'ActionLogResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AppealRejectedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- appeal: { type: 'AppealItemResponse', isSingle: true },
};
return decode(typeMappings, input);
};
-decoders['AsyncBulkImageModerationEvent'] = (input?: {
- [key: string]: any;
-}) => {
+decoders['GetApplicationResponse'] = (input?: { [key: string]: any }) => {
const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncExportChannelsEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncExportErrorEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncExportLabelResultsEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncExportModerationLogsEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncExportReviewQueueEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncExportUsersEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncImportErrorEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AsyncImportSuccessEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['AutomodDetailsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- result: { type: 'MessageModerationResult', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BanInfoResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- expires: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelMetadata', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BanResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- expires: { type: 'DatetimeType', isSingle: true },
-
- banned_by: { type: 'UserResponse', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BatchQueryActivityReactionsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- reactions: { type: 'FeedsReactionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BatchQueryCommentReactionsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- reactions: { type: 'FeedsReactionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BlockListResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BlockUsersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BlockedUserEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
-
- blocked_by_user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BlockedUserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- blocked_user: { type: 'UserResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BookmarkAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- bookmark: { type: 'BookmarkResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BookmarkDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- bookmark: { type: 'BookmarkResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BookmarkFolderDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- bookmark_folder: { type: 'BookmarkFolderResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BookmarkFolderResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BookmarkFolderUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- bookmark_folder: { type: 'BookmarkFolderResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BookmarkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- folder: { type: 'BookmarkFolderResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BookmarkUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- bookmark: { type: 'BookmarkResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BulkActionAppealsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- results: { type: 'BulkAppealResult', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['BulkAppealResult'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- appeal_item: { type: 'AppealItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallAcceptedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallClosedCaptionsFailedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallClosedCaptionsStartedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallClosedCaptionsStoppedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallCreatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallDTMFEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- timestamp: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallEndedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallFrameRecordingFailedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallFrameRecordingFrameReadyEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- captured_at: { type: 'DatetimeType', isSingle: true },
-
- created_at: { type: 'DatetimeType', isSingle: true },
-
- users: { type: 'UserResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallFrameRecordingStartedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallFrameRecordingStoppedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallHLSBroadcastingFailedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallHLSBroadcastingStartedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallHLSBroadcastingStoppedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallLiveStartedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallMemberAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallMemberRemovedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallMemberUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallMemberUpdatedPermissionEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallMissedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallModerationBlurEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallModerationWarningEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallNotificationEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallParticipantResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- joined_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallParticipantTimeline'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- timestamp: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallReactionEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRecording'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- end_time: { type: 'DatetimeType', isSingle: true },
-
- start_time: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRecordingFailedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRecordingReadyEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call_recording: { type: 'CallRecording', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRecordingStartedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRecordingStoppedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRejectedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallReportResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- ended_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- egress: { type: 'EgressResponse', isSingle: true },
-
- ended_at: { type: 'DatetimeType', isSingle: true },
-
- starts_at: { type: 'DatetimeType', isSingle: true },
-
- session: { type: 'CallSessionResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRingEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRingSettledEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- rang_at: { type: 'DatetimeType', isSingle: true },
-
- settled_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRtmpBroadcastFailedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRtmpBroadcastStartedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallRtmpBroadcastStoppedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallSessionEndedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallSessionParticipantCountsUpdatedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallSessionParticipantJoinedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- participant: { type: 'CallParticipantResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallSessionParticipantLeftEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- participant: { type: 'CallParticipantResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallSessionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- participants: { type: 'CallParticipantResponse', isSingle: false },
-
- accepted_by: { type: 'DatetimeType', isSingle: false },
-
- missed_by: { type: 'DatetimeType', isSingle: false },
-
- rejected_by: { type: 'DatetimeType', isSingle: false },
-
- ended_at: { type: 'DatetimeType', isSingle: true },
-
- live_ended_at: { type: 'DatetimeType', isSingle: true },
-
- live_started_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
-
- timer_ends_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallSessionStartedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallStateResponseFields'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallStatsParticipant'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- sessions: { type: 'CallStatsParticipantSession', isSingle: false },
-
- latest_activity_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallStatsParticipantSession'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- ended_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallStatsReportReadyEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- participants_overview: { type: 'CallStatsParticipant', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallStatsReportSummaryResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- first_stats_time: { type: 'DatetimeType', isSingle: true },
-
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallStatsSessionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- generated_at: { type: 'DatetimeType', isSingle: true },
-
- call_ended_at: { type: 'DatetimeType', isSingle: true },
-
- call_started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallTranscription'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- end_time: { type: 'DatetimeType', isSingle: true },
-
- start_time: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallTranscriptionFailedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallTranscriptionReadyEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call_transcription: { type: 'CallTranscription', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallTranscriptionStartedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallTranscriptionStoppedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallType'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallUserFeedbackSubmittedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CallUserMutedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CampaignCompletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- campaign: { type: 'CampaignResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CampaignResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- segments: { type: 'Segment', isSingle: false },
-
- users: { type: 'UserResponse', isSingle: false },
-
- stats: { type: 'CampaignStatsResponse', isSingle: true },
-
- scheduled_for: { type: 'DatetimeType', isSingle: true },
-
- stop_at: { type: 'DatetimeType', isSingle: true },
-
- sender: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CampaignStartedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- campaign: { type: 'CampaignResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CampaignStatsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- stats_completed_at: { type: 'DatetimeType', isSingle: true },
-
- stats_started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChangeFeedVisibilityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed: { type: 'FeedResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelBanResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel_ban: { type: 'ChannelBanScopeResponse', isSingle: true },
-
- future_channel_ban: { type: 'ChannelBanScopeResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelBanScopeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- expires: { type: 'DatetimeType', isSingle: true },
-
- banned_by: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelBatchCompletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- batch_created_at: { type: 'DatetimeType', isSingle: true },
-
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelBatchStartedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- batch_created_at: { type: 'DatetimeType', isSingle: true },
-
- created_at: { type: 'DatetimeType', isSingle: true },
-
- finished_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelConfig'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelConfigWithInfo'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- commands: { type: 'Command', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelContextResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_by: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelCreatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelFrozenEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelHiddenEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelMemberResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- archived_at: { type: 'DatetimeType', isSingle: true },
-
- ban_expires: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- future_channel_ban_expires: { type: 'DatetimeType', isSingle: true },
-
- invite_accepted_at: { type: 'DatetimeType', isSingle: true },
-
- invite_rejected_at: { type: 'DatetimeType', isSingle: true },
-
- pinned_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelMetadata'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- last_message_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelMute'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- expires: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelMutedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- mutes: { type: 'ChannelMute', isSingle: false },
-
- mute: { type: 'ChannelMute', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelPushPreferencesResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- disabled_until: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- hide_messages_before: { type: 'DatetimeType', isSingle: true },
-
- last_message_at: { type: 'DatetimeType', isSingle: true },
-
- mute_expires_at: { type: 'DatetimeType', isSingle: true },
-
- truncated_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'ChannelMemberResponse', isSingle: false },
-
- config: { type: 'ChannelConfigWithInfo', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- truncated_by: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelStateResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'ChannelMemberResponse', isSingle: false },
-
- messages: { type: 'MessageResponse', isSingle: false },
-
- pinned_messages: { type: 'MessageResponse', isSingle: false },
-
- threads: { type: 'ThreadStateResponse', isSingle: false },
-
- hide_messages_before: { type: 'DatetimeType', isSingle: true },
-
- active_live_locations: {
- type: 'SharedLocationResponseData',
- isSingle: false,
- },
-
- pending_messages: { type: 'PendingMessageResponse', isSingle: false },
-
- read: { type: 'ReadStateResponse', isSingle: false },
-
- scheduled_messages: { type: 'ScheduledMessageResponse', isSingle: false },
-
- watchers: { type: 'UserResponse', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- draft: { type: 'DraftResponse', isSingle: true },
-
- membership: { type: 'ChannelMemberResponse', isSingle: true },
-
- push_preferences: {
- type: 'ChannelPushPreferencesResponse',
- isSingle: true,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelStateResponseFields'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'ChannelMemberResponse', isSingle: false },
-
- messages: { type: 'MessageResponse', isSingle: false },
-
- pinned_messages: { type: 'MessageResponse', isSingle: false },
-
- threads: { type: 'ThreadStateResponse', isSingle: false },
-
- hide_messages_before: { type: 'DatetimeType', isSingle: true },
-
- active_live_locations: {
- type: 'SharedLocationResponseData',
- isSingle: false,
- },
-
- pending_messages: { type: 'PendingMessageResponse', isSingle: false },
-
- read: { type: 'ReadStateResponse', isSingle: false },
-
- scheduled_messages: { type: 'ScheduledMessageResponse', isSingle: false },
-
- watchers: { type: 'UserResponse', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- draft: { type: 'DraftResponse', isSingle: true },
-
- membership: { type: 'ChannelMemberResponse', isSingle: true },
-
- push_preferences: {
- type: 'ChannelPushPreferencesResponse',
- isSingle: true,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelTruncatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelTypeConfig'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- commands: { type: 'Command', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelUnFrozenEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelUnmutedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- mutes: { type: 'ChannelMute', isSingle: false },
-
- mute: { type: 'ChannelMute', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChannelVisibleEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatActivityStatsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- messages: { type: 'MessageStatsResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatDraftPayloadResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- mentioned_users: { type: 'UserResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatDraftResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'ChatDraftPayloadResponse', isSingle: true },
-
- parent_message: { type: 'ChatMessageResponse', isSingle: true },
-
- quoted_message: { type: 'ChatMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions: { type: 'ChatReactionResponse', isSingle: false },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_reactions: { type: 'ChatReactionResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- message_text_updated_at: { type: 'DatetimeType', isSingle: true },
-
- pin_expires: { type: 'DatetimeType', isSingle: true },
-
- pinned_at: { type: 'DatetimeType', isSingle: true },
-
- mentioned_groups: { type: 'UserGroupResponse', isSingle: false },
-
- thread_participants: { type: 'UserResponse', isSingle: false },
-
- draft: { type: 'ChatDraftResponse', isSingle: true },
-
- pinned_by: { type: 'UserResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- quoted_message: { type: 'ChatMessageResponse', isSingle: true },
-
- reaction_groups: { type: 'ChatReactionGroupResponse', isSingle: false },
-
- reminder: { type: 'ChatReminderResponseData', isSingle: true },
-
- shared_location: { type: 'ChatSharedLocationResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatReactionGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- first_reaction_at: { type: 'DatetimeType', isSingle: true },
-
- last_reaction_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions_by: {
- type: 'ChatReactionGroupUserResponse',
- isSingle: false,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatReactionGroupUserResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatReactionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatReminderResponseData'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- remind_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'ChatMessageResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ChatSharedLocationResponseData'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- end_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'ChatMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CheckResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- item: { type: 'ReviewQueueItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ClosedCaptionEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CollectionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['Command'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentFeedbackEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentReactionAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentReactionDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentReactionUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- edited_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- reaction_groups: { type: 'FeedsReactionGroupResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentRestoredEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CommentUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ConfigResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CountByMinuteResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- start_ts: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateBlockListResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- blocklist: { type: 'BlockListResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateCallTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateCampaignResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- campaign: { type: 'CampaignResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateChannelTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateCollectionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- collections: { type: 'CollectionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateCommandResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- command: { type: 'Command', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateFeedGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_group: { type: 'FeedGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateFeedViewResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_view: { type: 'FeedViewResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateFeedsBatchResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feeds: { type: 'FeedResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateGuestResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateImportResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- import_task: { type: 'ImportTask', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateImportV2TaskResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateMembershipLevelResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- membership_level: { type: 'MembershipLevelResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreatePredefinedFilterResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- predefined_filter: { type: 'PredefinedFilterResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateReminderResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- reminder: { type: 'ReminderResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateRoleResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- role: { type: 'Role', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateSIPTrunkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- sip_trunk: { type: 'SIPTrunkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateSegmentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- segment: { type: 'SegmentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CreateUserGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user_group: { type: 'UserGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CustomCheckResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- item: { type: 'ReviewQueueItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CustomEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['CustomVideoEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeactivateUserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteActivityReactionResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteBookmarkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bookmark: { type: 'BookmarkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteCallResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteChannelResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channel: { type: 'ChannelResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteCommentBookmarkResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- bookmark: { type: 'BookmarkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteCommentReactionResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- comment: { type: 'CommentResponse', isSingle: true },
-
- reaction: { type: 'FeedsReactionResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteCommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteReactionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
-
- reaction: { type: 'ReactionResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeleteScheduledMessageResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- scheduled_message: { type: 'ScheduledMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DeviceResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DraftPayloadResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- mentioned_users: { type: 'UserResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['DraftResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'DraftPayloadResponse', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- parent_message: { type: 'MessageResponse', isSingle: true },
-
- quoted_message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['EgressRTMPResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['EgressResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- rtmps: { type: 'EgressRTMPResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['EnrichedCollectionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['EntityCreatorResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- ban_expires: { type: 'DatetimeType', isSingle: true },
-
- deactivated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- last_active: { type: 'DatetimeType', isSingle: true },
-
- revoke_tokens_issued_before: { type: 'DatetimeType', isSingle: true },
-
- devices: { type: 'DeviceResponse', isSingle: false },
-
- push_notifications: {
- type: 'PushNotificationSettingsResponse',
- isSingle: true,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['EventHook'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['EventResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- event: { type: 'WSEvent', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ExportUserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- messages: { type: 'MessageResponse', isSingle: false },
-
- reactions: { type: 'ReactionResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedCreatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'FeedMemberResponse', isSingle: false },
-
- feed: { type: 'FeedResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedGroupChangedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedGroupDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- activity_selectors: {
- type: 'ActivitySelectorConfigResponse',
- isSingle: false,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedGroupRestoredEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedMemberAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- member: { type: 'FeedMemberResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedMemberRemovedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedMemberResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
-
- invite_accepted_at: { type: 'DatetimeType', isSingle: true },
-
- invite_rejected_at: { type: 'DatetimeType', isSingle: true },
-
- membership_level: { type: 'MembershipLevelResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedMemberUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- member: { type: 'FeedMemberResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedOwnData'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- own_followings: { type: 'FollowResponse', isSingle: false },
-
- own_follows: { type: 'FollowResponse', isSingle: false },
-
- own_membership: { type: 'FeedMemberResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- own_followings: { type: 'FollowResponse', isSingle: false },
-
- own_follows: { type: 'FollowResponse', isSingle: false },
-
- own_membership: { type: 'FeedMemberResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedSuggestionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- own_followings: { type: 'FollowResponse', isSingle: false },
-
- own_follows: { type: 'FollowResponse', isSingle: false },
-
- own_membership: { type: 'FeedMemberResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- feed: { type: 'FeedResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedViewResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- last_used_at: { type: 'DatetimeType', isSingle: true },
-
- activity_selectors: {
- type: 'ActivitySelectorConfigResponse',
- isSingle: false,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsBookmarkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsEnrichedCollectionResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsFeedResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsReactionGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- first_reaction_at: { type: 'DatetimeType', isSingle: true },
-
- last_reaction_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsReactionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsRetentionPolicy'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- enabled_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsShareResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsV3ActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- comments: { type: 'FeedsV3CommentResponse', isSingle: false },
-
- latest_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_bookmarks: { type: 'FeedsBookmarkResponse', isSingle: false },
-
- own_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- collections: { type: 'FeedsEnrichedCollectionResponse', isSingle: false },
-
- reaction_groups: { type: 'FeedsReactionGroupResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- edited_at: { type: 'DatetimeType', isSingle: true },
-
- expires_at: { type: 'DatetimeType', isSingle: true },
-
- friend_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- latest_shares: { type: 'FeedsShareResponse', isSingle: false },
-
- current_feed: { type: 'FeedsFeedResponse', isSingle: true },
-
- parent: { type: 'FeedsV3ActivityResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FeedsV3CommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- edited_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- reaction_groups: { type: 'FeedsReactionGroupResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FlagDetailsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- automod: { type: 'AutomodDetailsResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FlagFeedbackResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FlagUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FollowBatchResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created: { type: 'FollowResponse', isSingle: false },
-
- follows: { type: 'FollowResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FollowCreatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- follow: { type: 'FollowResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FollowDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- follow: { type: 'FollowResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- source_feed: { type: 'FeedResponse', isSingle: true },
-
- target_feed: { type: 'FeedResponse', isSingle: true },
-
- request_accepted_at: { type: 'DatetimeType', isSingle: true },
-
- request_rejected_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FollowUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- follow: { type: 'FollowResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FullUserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- channel_mutes: { type: 'ChannelMute', isSingle: false },
-
- devices: { type: 'DeviceResponse', isSingle: false },
-
- mutes: { type: 'UserMuteResponse', isSingle: false },
-
- ban_expires: { type: 'DatetimeType', isSingle: true },
-
- deactivated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- last_active: { type: 'DatetimeType', isSingle: true },
-
- revoke_tokens_issued_before: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['FutureChannelBanResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- expires: { type: 'DatetimeType', isSingle: true },
-
- banned_by: { type: 'UserResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetActiveCallsStatusResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- end_time: { type: 'DatetimeType', isSingle: true },
-
- start_time: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetAppealResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- item: { type: 'AppealItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetApplicationResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- app: { type: 'AppResponseFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetBlockListResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- blocklist: { type: 'BlockListResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetBlockedUsersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- blocks: { type: 'BlockedUserResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCallParticipantSessionMetricsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- joined_at: { type: 'DatetimeType', isSingle: true },
-
- published_tracks: { type: 'PublishedTrackMetrics', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCallReportResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- report: { type: 'ReportResponse', isSingle: true },
-
- video_reactions: { type: 'VideoReactionsResponse', isSingle: false },
-
- chat_activity: { type: 'ChatActivityStatsResponse', isSingle: true },
-
- session: { type: 'CallSessionResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCallResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCallSessionParticipantStatsDetailsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- timeframe: { type: 'ParticipantSeriesTimeframe', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCallTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCampaignResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- campaign: { type: 'CampaignResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetChannelTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- commands: { type: 'Command', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCommandResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCommentRepliesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comments: { type: 'ThreadedCommentResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comment: { type: 'CommentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetCommentsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comments: { type: 'ThreadedCommentResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetConfigResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- config: { type: 'ConfigResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetDraftResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- draft: { type: 'DraftResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetExternalStorageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetFeedGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_group: { type: 'FeedGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetFeedViewResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_view: { type: 'FeedViewResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetFeedsRetentionPolicyResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- policies: { type: 'FeedsRetentionPolicy', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetFollowSuggestionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- suggestions: { type: 'FeedSuggestionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetImportResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- import_task: { type: 'ImportTask', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetImportV2TaskResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetManyMessagesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- messages: { type: 'MessageResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageWithChannelResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetModerationRuleResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- rule: { type: 'ModerationRuleV2Response', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetOrCreateCallResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetOrCreateFeedGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_group: { type: 'FeedGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetOrCreateFeedResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activities: { type: 'ActivityResponse', isSingle: false },
-
- aggregated_activities: {
- type: 'AggregatedActivityResponse',
- isSingle: false,
- },
-
- followers: { type: 'FollowResponse', isSingle: false },
-
- following: { type: 'FollowResponse', isSingle: false },
-
- members: { type: 'FeedMemberResponse', isSingle: false },
-
- pinned_activities: { type: 'ActivityPinResponse', isSingle: false },
-
- feed: { type: 'FeedResponse', isSingle: true },
-
- notification_status: { type: 'NotificationStatusResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetOrCreateFeedViewResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_view: { type: 'FeedViewResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetOrCreateFollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follow: { type: 'FollowResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetOrCreateUnfollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follow: { type: 'FollowResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetPinnedMessagesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- messages: { type: 'MessageResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetPredefinedFilterResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- predefined_filter: { type: 'PredefinedFilterResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetPushTemplatesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- templates: { type: 'PushTemplateResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetReactionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- reactions: { type: 'ReactionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetRepliesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- messages: { type: 'MessageResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetRetentionPolicyResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- policies: { type: 'RetentionPolicy', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetReviewQueueItemResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- item: { type: 'ReviewQueueItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetSegmentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- segment: { type: 'SegmentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetSetupSessionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- setup_session: { type: 'SetupSession', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetTaskResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetThreadResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- thread: { type: 'ThreadStateResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GetUserGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user_group: { type: 'UserGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GoLiveResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GroupedChannelsBucket'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channels: { type: 'ChannelStateResponseFields', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['GroupedQueryChannelsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- groups: { type: 'GroupedChannelsBucket', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ImportTask'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- history: { type: 'ImportTaskHistory', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ImportTaskHistory'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ImportV2TaskItem'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['IngressErrorEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['IngressStartedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['IngressStoppedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['KickedUserEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
-
- kicked_by_user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['LabelResultResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListBlockListResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- blocklists: { type: 'BlockListResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListCallTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- call_types: { type: 'CallTypeResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListChannelTypesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channel_types: { type: 'ChannelTypeConfig', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListCommandsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- commands: { type: 'Command', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListDevicesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- devices: { type: 'DeviceResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListFeedGroupsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- groups: { type: 'FeedGroupResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListFeedViewsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- views: { type: 'FeedViewResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListImportV2TasksResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- import_tasks: { type: 'ImportV2TaskItem', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListImportsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- import_tasks: { type: 'ImportTask', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListPushProvidersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- push_providers: { type: 'PushProviderResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListQueuesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- queues: { type: 'ModerationQueueResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListRecordingsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- recordings: { type: 'CallRecording', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListRolesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- roles: { type: 'Role', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListSIPInboundRoutingRuleResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- sip_inbound_routing_rules: {
- type: 'SIPInboundRoutingRuleResponse',
- isSingle: false,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListSIPTrunksResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- sip_trunks: { type: 'SIPTrunkResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListTranscriptionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- transcriptions: { type: 'CallTranscription', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ListUserGroupsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user_groups: { type: 'UserGroupResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MarkReadResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- event: { type: 'MarkReadResponseEvent', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MarkReadResponseEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel_last_message_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- thread: { type: 'ThreadResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MaxStreakChangedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MemberAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- member: { type: 'ChannelMemberResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MemberRemovedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- member: { type: 'ChannelMemberResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MemberResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MemberUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- member: { type: 'ChannelMemberResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MembersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'ChannelMemberResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MembershipLevelResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageActionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageFlagResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- approved_at: { type: 'DatetimeType', isSingle: true },
-
- rejected_at: { type: 'DatetimeType', isSingle: true },
-
- reviewed_at: { type: 'DatetimeType', isSingle: true },
-
- details: { type: 'FlagDetailsResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- moderation_feedback: { type: 'FlagFeedbackResponse', isSingle: true },
-
- moderation_result: { type: 'MessageModerationResult', isSingle: true },
-
- reviewed_by: { type: 'UserResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageFlaggedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- details: { type: 'MessageModerationResult', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageHistoryEntryResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message_updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageModerationResult'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageNewEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- thread_participants: { type: 'UserResponseCommonFields', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageReadEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- thread: { type: 'ThreadResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions: { type: 'ReactionResponse', isSingle: false },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_reactions: { type: 'ReactionResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- message_text_updated_at: { type: 'DatetimeType', isSingle: true },
-
- pin_expires: { type: 'DatetimeType', isSingle: true },
-
- pinned_at: { type: 'DatetimeType', isSingle: true },
-
- mentioned_groups: { type: 'UserGroupResponse', isSingle: false },
-
- thread_participants: { type: 'UserResponse', isSingle: false },
-
- draft: { type: 'DraftResponse', isSingle: true },
-
- pinned_by: { type: 'UserResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- quoted_message: { type: 'MessageResponse', isSingle: true },
-
- reaction_groups: { type: 'ReactionGroupResponse', isSingle: false },
-
- reminder: { type: 'ReminderResponseData', isSingle: true },
-
- shared_location: { type: 'SharedLocationResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageStatsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- count_over_time: { type: 'CountByMinuteResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageUnblockedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageUndeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MessageWithChannelResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions: { type: 'ReactionResponse', isSingle: false },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_reactions: { type: 'ReactionResponse', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- message_text_updated_at: { type: 'DatetimeType', isSingle: true },
-
- pin_expires: { type: 'DatetimeType', isSingle: true },
-
- pinned_at: { type: 'DatetimeType', isSingle: true },
-
- mentioned_groups: { type: 'UserGroupResponse', isSingle: false },
-
- thread_participants: { type: 'UserResponse', isSingle: false },
-
- draft: { type: 'DraftResponse', isSingle: true },
-
- pinned_by: { type: 'UserResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- quoted_message: { type: 'MessageResponse', isSingle: true },
-
- reaction_groups: { type: 'ReactionGroupResponse', isSingle: false },
-
- reminder: { type: 'ReminderResponseData', isSingle: true },
-
- shared_location: { type: 'SharedLocationResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationAnalysisFailedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationCallResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- ended_at: { type: 'DatetimeType', isSingle: true },
-
- starts_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationCheckCompletedEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationCustomActionEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationFlagResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- content_published_at: { type: 'DatetimeType', isSingle: true },
-
- review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationFlaggedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationImageAnalysisCompleteEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationMarkReviewedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- item: { type: 'ReviewQueueItemResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationQueueResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationRuleV2Response'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationRulesTriggeredEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ModerationTextAnalysisCompleteEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MuteChannelResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channel_mutes: { type: 'ChannelMute', isSingle: false },
-
- channel_mute: { type: 'ChannelMute', isSingle: true },
-
- own_user: { type: 'OwnUserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['MuteResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- mutes: { type: 'UserMuteResponse', isSingle: false },
-
- own_user: { type: 'OwnUserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['NotificationFeedUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- aggregated_activities: {
- type: 'AggregatedActivityResponse',
- isSingle: false,
- },
-
- notification_status: { type: 'NotificationStatusResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['NotificationMarkUnreadEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- last_read_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['NotificationStatusResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- last_read_at: { type: 'DatetimeType', isSingle: true },
-
- last_seen_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['NotificationThreadMessageNewEvent'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- thread_participants: { type: 'UserResponseCommonFields', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['OwnBatchResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- data: { type: 'FeedOwnData', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['OwnUserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- channel_mutes: { type: 'ChannelMute', isSingle: false },
-
- devices: { type: 'DeviceResponse', isSingle: false },
-
- mutes: { type: 'UserMuteResponse', isSingle: false },
-
- deactivated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- last_active: { type: 'DatetimeType', isSingle: true },
-
- revoke_tokens_issued_before: { type: 'DatetimeType', isSingle: true },
-
- push_preferences: { type: 'PushPreferencesResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ParticipantCountByMinuteResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- start_ts: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ParticipantCountOverTimeResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- by_minute: { type: 'ParticipantCountByMinuteResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ParticipantReportResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- count_over_time: {
- type: 'ParticipantCountOverTimeResponse',
- isSingle: true,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ParticipantSeriesTimeframe'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- since: { type: 'DatetimeType', isSingle: true },
-
- until: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ParticipantSessionDetails'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- joined_at: { type: 'DatetimeType', isSingle: true },
-
- left_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PendingMessageEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PendingMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PerformanceAnalysisResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- last_analyzed: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PermissionRequestEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PinActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PolicyTestResult'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PolicyTestRun'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- completed_at: { type: 'DatetimeType', isSingle: true },
-
- config_updated_at: { type: 'DatetimeType', isSingle: true },
-
- started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PolicyTestRunResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- results: { type: 'PolicyTestResult', isSingle: false },
-
- run: { type: 'PolicyTestRun', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PolicyTestSet'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- last_run: { type: 'PolicyTestRun', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PolicyTestSetListResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- sets: { type: 'PolicyTestSet', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PolicyTestSetResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- recent_runs: { type: 'PolicyTestRun', isSingle: false },
-
- set: { type: 'PolicyTestSet', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollClosedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- poll: { type: 'PollResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollResponseData'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- latest_answers: { type: 'PollVoteResponseData', isSingle: false },
-
- own_votes: { type: 'PollVoteResponseData', isSingle: false },
-
- created_by: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollVoteCastedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- poll_vote: { type: 'PollVoteResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollVoteChangedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- poll_vote: { type: 'PollVoteResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollVoteRemovedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- poll_vote: { type: 'PollVoteResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollVoteResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- poll: { type: 'PollResponseData', isSingle: true },
-
- vote: { type: 'PollVoteResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollVoteResponseData'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PollVotesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- votes: { type: 'PollVoteResponseData', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PredefinedFilterResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- performance: { type: 'PerformanceAnalysisResponse', isSingle: true },
-
- stats: { type: 'PredefinedFilterStatsResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PredefinedFilterStatsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- last_seen: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PublishedTrackMetrics'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- warnings: { type: 'SessionWarningResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PushNotificationFields'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- providers: { type: 'PushProvider', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PushNotificationSettingsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- disabled_until: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PushPreferencesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- disabled_until: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PushProvider'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- disabled_at: { type: 'DatetimeType', isSingle: true },
-
- push_templates: { type: 'PushTemplate', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PushProviderResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- disabled_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PushTemplate'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['PushTemplateResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryActivitiesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activities: { type: 'ActivityResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryActivityReactionsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- reactions: { type: 'FeedsReactionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryActivitySharesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- shares: { type: 'ShareResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryAppealsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- items: { type: 'AppealItemResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryBannedUsersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bans: { type: 'BanResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryBookmarkFoldersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bookmark_folders: { type: 'BookmarkFolderResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryBookmarksResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bookmarks: { type: 'BookmarkResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallMembersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'MemberResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallParticipantSessionsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- participants_sessions: {
- type: 'ParticipantSessionDetails',
- isSingle: false,
- },
-
- session: { type: 'CallSessionResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallParticipantsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- members: { type: 'MemberResponse', isSingle: false },
-
- participants: { type: 'CallParticipantResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallRingAttemptsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- ring_attempts: { type: 'RingAttemptResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallSessionParticipantStatsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- participants: { type: 'CallStatsParticipant', isSingle: false },
-
- call_ended_at: { type: 'DatetimeType', isSingle: true },
-
- call_started_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallSessionParticipantStatsTimelineResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- events: { type: 'CallParticipantTimeline', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallSessionStatsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- call_stats: { type: 'CallStatsSessionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallStatsMapResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- call_ended_at: { type: 'DatetimeType', isSingle: true },
-
- call_started_at: { type: 'DatetimeType', isSingle: true },
-
- end_time: { type: 'DatetimeType', isSingle: true },
-
- generated_at: { type: 'DatetimeType', isSingle: true },
-
- start_time: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallStatsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- reports: { type: 'CallStatsReportSummaryResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCallsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- calls: { type: 'CallStateResponseFields', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCampaignsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- campaigns: { type: 'CampaignResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryChannelBansResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bans: { type: 'ChannelBanResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryChannelsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channels: { type: 'ChannelStateResponseFields', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCollectionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- collections: { type: 'CollectionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCommentReactionsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- reactions: { type: 'FeedsReactionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryCommentsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comments: { type: 'CommentResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryDraftsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- drafts: { type: 'DraftResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryFeedMembersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'FeedMemberResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryFeedModerationTemplate'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryFeedModerationTemplatesResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- templates: { type: 'QueryFeedModerationTemplate', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryFeedsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feeds: { type: 'FeedResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryFollowsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follows: { type: 'FollowResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryFutureChannelBansResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- bans: { type: 'FutureChannelBanResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryLabelResultsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- label_results: { type: 'LabelResultResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryMembershipLevelsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- membership_levels: { type: 'MembershipLevelResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryMessageFlagsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- flags: { type: 'MessageFlagResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryMessageHistoryResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message_history: { type: 'MessageHistoryEntryResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryModerationConfigsResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- configs: { type: 'ConfigResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryModerationFlagsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- flags: { type: 'ModerationFlagResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryModerationLogsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- logs: { type: 'ActionLogResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryModerationRulesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- rules: { type: 'ModerationRuleV2Response', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryPinnedActivitiesResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- pinned_activities: { type: 'ActivityPinResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryPollsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- polls: { type: 'PollResponseData', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryPredefinedFiltersResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- predefined_filters: { type: 'PredefinedFilterResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryReactionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- reactions: { type: 'ReactionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryRemindersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- reminders: { type: 'ReminderResponseData', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryReviewQueueResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- items: { type: 'ReviewQueueItemResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryRevisionHistoryResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- revisions: { type: 'RevisionHistoryResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryScheduledMessagesResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- scheduled_messages: { type: 'ScheduledMessageResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QuerySegmentTargetsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- targets: { type: 'SegmentTargetResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QuerySegmentsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- segments: { type: 'SegmentResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryThreadsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- threads: { type: 'ThreadStateResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueryUsersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- users: { type: 'FullUserResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['QueueResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- queue: { type: 'ModerationQueueResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['Reaction'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReactionDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- thread_participants: { type: 'UserResponseCommonFields', isSingle: false },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- reaction: { type: 'ReactionResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReactionGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- first_reaction_at: { type: 'DatetimeType', isSingle: true },
-
- last_reaction_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions_by: { type: 'ReactionGroupUserResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReactionGroupUserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReactionNewEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- thread_participants: { type: 'UserResponseCommonFields', isSingle: false },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- reaction: { type: 'ReactionResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReactionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReactionUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- reaction: { type: 'ReactionResponse', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReactivateUserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReadCollectionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- collections: { type: 'CollectionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReadStateResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- last_read: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
-
- last_delivered_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RejectFeedMemberInviteResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- member: { type: 'FeedMemberResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RejectFollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follow: { type: 'FollowResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReminderCreatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- reminder: { type: 'ReminderResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReminderDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- reminder: { type: 'ReminderResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReminderNotificationEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- reminder: { type: 'ReminderResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReminderResponseData'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- expires_at: { type: 'DatetimeType', isSingle: true },
-
- remind_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReminderUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- reminder: { type: 'ReminderResponseData', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RemoveUserGroupMembersResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- user_group: { type: 'UserGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReportResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- call: { type: 'CallReportResponse', isSingle: true },
-
- participants: { type: 'ParticipantReportResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ResolveSipInboundResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- sip_routing_rule: { type: 'SIPInboundRoutingRuleResponse', isSingle: true },
-
- sip_trunk: { type: 'SIPTrunkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RestoreActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RestoreCommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
-
- comment: { type: 'CommentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RestoreFeedGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_group: { type: 'FeedGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RetentionPolicy'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- enabled_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReviewQueueItemNewEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- flags: { type: 'ModerationFlagResponse', isSingle: false },
-
- action: { type: 'ActionLogResponse', isSingle: true },
-
- review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReviewQueueItemResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- actions: { type: 'ActionLogResponse', isSingle: false },
-
- bans: { type: 'BanInfoResponse', isSingle: false },
-
- flags: { type: 'ModerationFlagResponse', isSingle: false },
-
- completed_at: { type: 'DatetimeType', isSingle: true },
-
- escalated_at: { type: 'DatetimeType', isSingle: true },
-
- reviewed_at: { type: 'DatetimeType', isSingle: true },
-
- appeal: { type: 'AppealItemResponse', isSingle: true },
-
- assigned_to: { type: 'UserResponse', isSingle: true },
-
- call: { type: 'ModerationCallResponse', isSingle: true },
-
- entity_creator: { type: 'EntityCreatorResponse', isSingle: true },
-
- feeds_v2_reaction: { type: 'Reaction', isSingle: true },
-
- feeds_v3_activity: { type: 'FeedsV3ActivityResponse', isSingle: true },
-
- feeds_v3_comment: { type: 'FeedsV3CommentResponse', isSingle: true },
-
- message: { type: 'ChatMessageResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- reaction: { type: 'Reaction', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ReviewQueueItemUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- flags: { type: 'ModerationFlagResponse', isSingle: false },
-
- action: { type: 'ActionLogResponse', isSingle: true },
-
- review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RevisionHistoryResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RingAttemptResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- expires_at: { type: 'DatetimeType', isSingle: true },
-
- targets: { type: 'RingTargetResponse', isSingle: false },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- settled_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RingDeliveryResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- sent_at: { type: 'DatetimeType', isSingle: true },
-
- acked_at: { type: 'DatetimeType', isSingle: true },
-
- displayed_at: { type: 'DatetimeType', isSingle: true },
-
- fetched_at: { type: 'DatetimeType', isSingle: true },
-
- provider_accepted_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['RingTargetResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- state_at: { type: 'DatetimeType', isSingle: true },
-
- deliveries: { type: 'RingDeliveryResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['Role'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SIPInboundRoutingRuleResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SIPTrunkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ScheduledMessageContentResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- pin_expires: { type: 'DatetimeType', isSingle: true },
-
- shared_location: { type: 'ScheduledMessageLocation', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ScheduledMessageFailedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- scheduled_message: { type: 'ScheduledMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ScheduledMessageLocation'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- end_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ScheduledMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- scheduled_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- message: { type: 'ScheduledMessageContentResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ScheduledMessageSentEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- scheduled_message: { type: 'ScheduledMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SearchResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- results: { type: 'SearchResult', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SearchResult'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'SearchResultMessage', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SearchResultMessage'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions: { type: 'ReactionResponse', isSingle: false },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_reactions: { type: 'ReactionResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- message_text_updated_at: { type: 'DatetimeType', isSingle: true },
-
- pin_expires: { type: 'DatetimeType', isSingle: true },
-
- pinned_at: { type: 'DatetimeType', isSingle: true },
-
- mentioned_groups: { type: 'UserGroupResponse', isSingle: false },
-
- thread_participants: { type: 'UserResponse', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- draft: { type: 'DraftResponse', isSingle: true },
-
- pinned_by: { type: 'UserResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- quoted_message: { type: 'MessageResponse', isSingle: true },
-
- reaction_groups: { type: 'ReactionGroupResponse', isSingle: false },
-
- reminder: { type: 'ReminderResponseData', isSingle: true },
-
- shared_location: { type: 'SharedLocationResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SearchRolesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- roles: { type: 'Role', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SearchUserGroupsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user_groups: { type: 'UserGroupResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['Segment'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SegmentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SegmentTargetResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SendMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
-
- channel_context: { type: 'ChannelContextResponse', isSingle: true },
-
- scheduled_message: { type: 'ScheduledMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SendReactionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
-
- reaction: { type: 'ReactionResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SendScheduledMessageNowResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- scheduled_message: { type: 'ScheduledMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SessionWarningResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- time: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SetFeedsRetentionPolicyResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- policy: { type: 'FeedsRetentionPolicy', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SetRetentionPolicyResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- policy: { type: 'RetentionPolicy', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SetupSession'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- completed_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ShareResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SharedLocationResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- end_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SharedLocationResponseData'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- end_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SharedLocationsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- active_live_locations: {
- type: 'SharedLocationResponseData',
- isSingle: false,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SingleFollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follow: { type: 'FollowResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['StartCampaignResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- campaign: { type: 'CampaignResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['StopCampaignResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- campaign: { type: 'CampaignResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['StopLiveResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['StoriesFeedUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- activities: { type: 'ActivityResponse', isSingle: false },
-
- aggregated_activities: {
- type: 'AggregatedActivityResponse',
- isSingle: false,
- },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['SubmitActionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- appeal_item: { type: 'AppealItemResponse', isSingle: true },
-
- item: { type: 'ReviewQueueItemResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ThreadParticipant'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- last_read_at: { type: 'DatetimeType', isSingle: true },
-
- last_thread_message_at: { type: 'DatetimeType', isSingle: true },
-
- left_thread_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ThreadResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- last_message_at: { type: 'DatetimeType', isSingle: true },
-
- thread_participants: { type: 'ThreadParticipant', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- parent_message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ThreadStateResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- latest_replies: { type: 'MessageResponse', isSingle: false },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- last_message_at: { type: 'DatetimeType', isSingle: true },
-
- read: { type: 'ReadStateResponse', isSingle: false },
-
- thread_participants: { type: 'ThreadParticipant', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- draft: { type: 'DraftResponse', isSingle: true },
-
- parent_message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ThreadUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- thread: { type: 'ThreadResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['ThreadedCommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- mentioned_users: { type: 'UserResponse', isSingle: false },
-
- own_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- user: { type: 'UserResponse', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- edited_at: { type: 'DatetimeType', isSingle: true },
-
- latest_reactions: { type: 'FeedsReactionResponse', isSingle: false },
-
- replies: { type: 'ThreadedCommentResponse', isSingle: false },
-
- reaction_groups: { type: 'FeedsReactionGroupResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['TranslateActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['TranslateCommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comment: { type: 'CommentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['TranslateMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['TruncateChannelResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnblockedUserEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UndeleteMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnfollowBatchResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follows: { type: 'FollowResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnfollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follow: { type: 'FollowResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnpinActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnreadCountsBatchResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- counts_by_user: { type: 'UnreadCountsResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnreadCountsChannel'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- last_read: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnreadCountsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channels: { type: 'UnreadCountsChannel', isSingle: false },
-
- threads: { type: 'UnreadCountsThread', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UnreadCountsThread'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- last_read: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateActivitiesPartialBatchResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- activities: { type: 'ActivityResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateActivityPartialResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateActivityResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activity: { type: 'ActivityResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateBlockListResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- blocklist: { type: 'BlockListResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateBookmarkFolderResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bookmark_folder: { type: 'BookmarkFolderResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateBookmarkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- bookmark: { type: 'BookmarkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCallMembersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'MemberResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCallResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'MemberResponse', isSingle: false },
-
- call: { type: 'CallResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCallTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCampaignResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- campaign: { type: 'CampaignResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateChannelPartialResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'ChannelMemberResponse', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateChannelResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- members: { type: 'ChannelMemberResponse', isSingle: false },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateChannelTypeResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCollectionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- collections: { type: 'CollectionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCommandResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- command: { type: 'Command', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCommentBookmarkResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- bookmark: { type: 'BookmarkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCommentPartialResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comment: { type: 'CommentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateCommentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- comment: { type: 'CommentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateFeedGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_group: { type: 'FeedGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateFeedMembersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- added: { type: 'FeedMemberResponse', isSingle: false },
-
- updated: { type: 'FeedMemberResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateFeedResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed: { type: 'FeedResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateFeedViewResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- feed_view: { type: 'FeedViewResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateFollowResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- follow: { type: 'FollowResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateMemberPartialResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channel_member: { type: 'ChannelMemberResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateMembershipLevelResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- membership_level: { type: 'MembershipLevelResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateMessagePartialResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateMessageResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- message: { type: 'MessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdatePredefinedFilterResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- predefined_filter: { type: 'PredefinedFilterResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateReminderResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- reminder: { type: 'ReminderResponseData', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateSIPInboundRoutingRuleResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- sip_inbound_routing_rule: {
- type: 'SIPInboundRoutingRuleResponse',
- isSingle: true,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateSIPTrunkResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- sip_trunk: { type: 'SIPTrunkResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateScheduledMessageResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- scheduled_message: { type: 'ScheduledMessageResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateSegmentResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- segment: { type: 'SegmentResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateThreadPartialResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- thread: { type: 'ThreadResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateUserGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- user_group: { type: 'UserGroupResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdateUsersResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- users: { type: 'FullUserResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpdatedCallPermissionsEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertActivitiesResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- activities: { type: 'ActivityResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertCollectionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- collections: { type: 'CollectionResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertConfigResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- config: { type: 'ConfigResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertModerationRuleResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- rule: { type: 'ModerationRuleV2Response', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertModerationTemplateResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertPushPreferencesResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- user_preferences: { type: 'PushPreferencesResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertPushProviderResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- push_provider: { type: 'PushProviderResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertPushTemplateResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- template: { type: 'PushTemplateResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UpsertSetupSessionResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- setup_session: { type: 'SetupSession', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserBannedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- expiration: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserDeactivatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserFlaggedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- target_user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserGroupCreatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserGroupDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserGroupMember'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserGroupMemberAddedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserGroupMemberRemovedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserGroupResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- members: { type: 'UserGroupMember', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserGroupUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserMessagesDeletedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserMuteResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- expires: { type: 'DatetimeType', isSingle: true },
-
- target: { type: 'UserResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserMutedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- target_users: { type: 'UserResponseCommonFields', isSingle: false },
-
- target_user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserReactivatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- ban_expires: { type: 'DatetimeType', isSingle: true },
-
- deactivated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- last_active: { type: 'DatetimeType', isSingle: true },
-
- revoke_tokens_issued_before: { type: 'DatetimeType', isSingle: true },
-
- devices: { type: 'DeviceResponse', isSingle: false },
-
- push_notifications: {
- type: 'PushNotificationSettingsResponse',
- isSingle: true,
- },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserResponseCommonFields'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- updated_at: { type: 'DatetimeType', isSingle: true },
-
- deactivated_at: { type: 'DatetimeType', isSingle: true },
-
- deleted_at: { type: 'DatetimeType', isSingle: true },
-
- last_active: { type: 'DatetimeType', isSingle: true },
-
- revoke_tokens_issued_before: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserUnbannedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- created_by: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserUnmutedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
-
- target_users: { type: 'UserResponseCommonFields', isSingle: false },
-
- target_user: { type: 'UserResponseCommonFields', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserUnreadReminderEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- user: { type: 'UserResponseCommonFields', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['UserUpdatedEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- received_at: { type: 'DatetimeType', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['VideoReactionOverTimeResponse'] = (input?: {
- [key: string]: any;
-}) => {
- const typeMappings: TypeMapping = {
- by_minute: { type: 'CountByMinuteResponse', isSingle: false },
- };
- return decode(typeMappings, input);
-};
-
-decoders['VideoReactionsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- count_over_time: { type: 'VideoReactionOverTimeResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['WSEvent'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- created_at: { type: 'DatetimeType', isSingle: true },
-
- channel_last_message_at: { type: 'DatetimeType', isSingle: true },
-
- channel: { type: 'ChannelResponse', isSingle: true },
-
- created_by: { type: 'UserResponse', isSingle: true },
-
- me: { type: 'OwnUserResponse', isSingle: true },
-
- member: { type: 'ChannelMemberResponse', isSingle: true },
-
- message: { type: 'MessageResponse', isSingle: true },
-
- poll: { type: 'PollResponseData', isSingle: true },
-
- poll_vote: { type: 'PollVoteResponseData', isSingle: true },
-
- reaction: { type: 'ReactionResponse', isSingle: true },
-
- thread: { type: 'ThreadResponse', isSingle: true },
-
- user: { type: 'UserResponse', isSingle: true },
- };
- return decode(typeMappings, input);
-};
-
-decoders['WrappedUnreadCountsResponse'] = (input?: { [key: string]: any }) => {
- const typeMappings: TypeMapping = {
- channels: { type: 'UnreadCountsChannel', isSingle: false },
-
- threads: { type: 'UnreadCountsThread', isSingle: false },
+ app: { type: 'AppResponseFields', isSingle: true },
};
return decode(typeMappings, input);
};
diff --git a/src/gen/models/index.ts b/src/gen/models/index.ts
index 753382ed..ac7bf548 100644
--- a/src/gen/models/index.ts
+++ b/src/gen/models/index.ts
@@ -1,3 +1,29 @@
+declare const timestampNsBrand: unique symbol;
+
+/**
+ * A unix-nanosecond timestamp, exactly as the server sends it.
+ *
+ * NOT milliseconds. `new Date(t)` is out of range and yields an Invalid Date, whose
+ * `.toISOString()` then throws; `models/timestamp-guard.ts` turns that into a
+ * compile error. Comparing or sorting two timestamps is fine -- same unit, plain
+ * numbers. Everything else should go through this SDK's time helpers:
+ *
+ * nsToDate(t) a Date
+ * convertTimestampToDate(t) a Date, or undefined when the value is absent or NaN
+ * nsToMs(t) epoch ms, for arithmetic against Date.now()
+ * nsToRfc3339(t) an RFC3339 string keeping sub-millisecond precision
+ * dateToNs(d), msToNs(ms) back to a wire timestamp
+ * nowNs() the local clock, wire-comparable
+ * asTimestampNS(n) brand a number already in ns (DB rows, fixtures) -- no conversion
+ *
+ * A request date field is typed `Date`, not this -- pass `nsToDate(t)` when handing a
+ * server-sent timestamp back to the API, or `nsToRfc3339(t)` where precision matters.
+ *
+ * Values above `Number.MAX_SAFE_INTEGER` are quantised to ~256ns, so ordering holds
+ * but exact equality after a JSON round-trip does not.
+ */
+export type TimestampNS = number & { readonly [timestampNsBrand]: true };
+
export interface AIAudioConfigRequest {
/**
* Bodyguard credentials profile to classify transcripts with. Applies to the NLP engine only; ignored when LLM configurability is enabled. Empty falls back to the app-pinned profile, then the organization's top-level credentials
@@ -152,8 +178,10 @@ export interface APNSPayload {
}
export interface AWSRekognitionRule {
- action:
- 'flag' | 'shadow' | 'remove' | 'bounce' | 'bounce_flag' | 'bounce_remove';
+ /**
+ * What a moderation rule does when it matches.
+ */
+ action: ModerationAction;
label: string;
min_confidence: number;
subclassifications?: Record;
@@ -209,7 +237,7 @@ export interface ActionLogResponse {
/**
* Timestamp when the action was taken
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Unique identifier of the action log
*/
@@ -299,7 +327,7 @@ export interface ActivityAddedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -307,8 +335,11 @@ export interface ActivityAddedEvent {
* The type of event: "feeds.activity.added" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -316,7 +347,7 @@ export interface ActivityDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -324,8 +355,11 @@ export interface ActivityDeletedEvent {
* The type of event: "feeds.activity.deleted" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -333,14 +367,14 @@ export interface ActivityFeedbackEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
activity_feedback: ActivityFeedbackEventPayload;
custom: Record;
/**
* The type of event: "feeds.activity.feedback" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -356,11 +390,11 @@ export interface ActivityFeedbackEventPayload {
/**
* When the feedback was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* When the feedback was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* The feedback value (true/false)
*/
@@ -410,14 +444,17 @@ export interface ActivityMarkEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
custom: Record;
/**
* The type of event: "feeds.activity.marked" in this case
*/
type: string;
- feed_visibility?: string;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
/**
* Whether all activities were marked as read
*/
@@ -426,7 +463,7 @@ export interface ActivityMarkEvent {
* Whether all activities were marked as seen
*/
mark_all_seen?: boolean;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The IDs of activities marked as read
*/
@@ -457,7 +494,7 @@ export interface ActivityPinResponse {
/**
* When the pin was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* ID of the feed where activity is pinned
*/
@@ -465,7 +502,7 @@ export interface ActivityPinResponse {
/**
* When the pin was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
activity: ActivityResponse;
/**
* User response object
@@ -477,7 +514,7 @@ export interface ActivityPinnedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* The ID of the feed
*/
@@ -488,8 +525,11 @@ export interface ActivityPinnedEvent {
* The type of event: "feeds.activity.pinned" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -527,7 +567,7 @@ export interface ActivityReactionAddedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -536,8 +576,11 @@ export interface ActivityReactionAddedEvent {
* The type of event: "feeds.activity.reaction.added" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -545,7 +588,7 @@ export interface ActivityReactionDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -554,8 +597,11 @@ export interface ActivityReactionDeletedEvent {
* The type of the reaction that was removed
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -563,7 +609,7 @@ export interface ActivityReactionUpdatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -572,8 +618,11 @@ export interface ActivityReactionUpdatedEvent {
* The type of event: "feeds.activity.reaction.updated" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -581,7 +630,7 @@ export interface ActivityRemovedFromFeedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -589,8 +638,11 @@ export interface ActivityRemovedFromFeedEvent {
* The type of event: "feeds.activity.removed_from_feed" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -631,7 +683,7 @@ export interface ActivityRequest {
/**
* Controls who can add comments/replies to this activity. One of: everyone, people_i_follow, nobody
*/
- restrict_replies?: 'everyone' | 'people_i_follow' | 'nobody';
+ restrict_replies?: RestrictReplies;
/**
* Whether to skip URL enrichment for the activity
*/
@@ -651,7 +703,7 @@ export interface ActivityRequest {
/**
* Visibility setting for the activity. One of: public, private, tag
*/
- visibility?: 'public' | 'private' | 'tag';
+ visibility?: ActivityVisibility;
/**
* If visibility is 'tag', this is the tag name and is required
*/
@@ -703,7 +755,7 @@ export interface ActivityResponse {
/**
* When the activity was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* If this activity is hidden by this user (using activity feedback)
*/
@@ -727,7 +779,7 @@ export interface ActivityResponse {
/**
* Controls who can add comments/replies to this activity. One of: everyone, people_i_follow, nobody
*/
- restrict_replies: 'everyone' | 'people_i_follow' | 'nobody';
+ restrict_replies: RestrictReplies;
/**
* Ranking score for this activity
*/
@@ -743,11 +795,11 @@ export interface ActivityResponse {
/**
* When the activity was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* Visibility setting for the activity. One of: public, private, tag
*/
- visibility: 'public' | 'private' | 'tag';
+ visibility: ActivityVisibility;
/**
* Media attachments for the activity
*/
@@ -807,15 +859,15 @@ export interface ActivityResponse {
/**
* When the activity was deleted
*/
- deleted_at?: Date;
+ deleted_at?: TimestampNS;
/**
* When the activity was last edited
*/
- edited_at?: Date;
+ edited_at?: TimestampNS;
/**
* When the activity will expire
*/
- expires_at?: Date;
+ expires_at?: TimestampNS;
/**
* Total count of reactions from friends on this activity
*/
@@ -872,7 +924,7 @@ export interface ActivityRestoredEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -880,8 +932,11 @@ export interface ActivityRestoredEvent {
* The type of the event
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -889,14 +944,7 @@ export interface ActivitySelectorConfig {
/**
* Type of selector. One of: popular, proximity, following, current_feed, query, interest, follow_suggestion
*/
- type:
- | 'popular'
- | 'proximity'
- | 'following'
- | 'current_feed'
- | 'query'
- | 'interest'
- | 'follow_suggestion';
+ type: ActivitySelectorType;
/**
* Time threshold for activity selection (string). Expected RFC3339 format (e.g., 2006-01-02T15:04:05Z07:00). Cannot be used together with cutoff_window
*/
@@ -917,6 +965,10 @@ export interface ActivitySelectorConfig {
* Maximum time in milliseconds this selector may take (25-5000). A selector that runs over its budget is dropped from that read instead of failing it, and stays out for the rest of that pagination session. The budget can only tighten the default, never extend it. Omit to use the default. Only supported on the popular, proximity, interest, query and follow_suggestion selectors
*/
time_budget_ms?: number;
+ /**
+ * Radius in kilometers the proximity selector searches around the viewer's location (the request's viewer_location, else the user's custom.location) when its filter has no near or within_bounds. Greater than 0 and at most 250. Omit to use the default (10). Only supported on the proximity selector
+ */
+ viewer_location_radius_km?: number;
/**
* Sort parameters for activity selection
*/
@@ -933,18 +985,11 @@ export interface ActivitySelectorConfigResponse {
/**
* Type of selector
*/
- type:
- | 'popular'
- | 'proximity'
- | 'following'
- | 'current_feed'
- | 'query'
- | 'interest'
- | 'follow_suggestion';
+ type: ActivitySelectorType;
/**
* Time threshold for activity selection (timestamp)
*/
- cutoff_time?: Date;
+ cutoff_time?: TimestampNS;
/**
* Flexible relative time window for activity selection (e.g., '1h', '3d', '1y')
*/
@@ -961,6 +1006,10 @@ export interface ActivitySelectorConfigResponse {
* Maximum time in milliseconds this selector may take before it is dropped from the read
*/
time_budget_ms?: number;
+ /**
+ * Radius in kilometers the proximity selector searches around the viewer's location
+ */
+ viewer_location_radius_km?: number;
/**
* Sort parameters for activity selection
*/
@@ -976,11 +1025,20 @@ export interface ActivitySelectorConfigResponse {
params?: Record;
}
+export type ActivitySelectorType =
+ | 'popular'
+ | 'proximity'
+ | 'following'
+ | 'current_feed'
+ | 'query'
+ | 'interest'
+ | 'follow_suggestion';
+
export interface ActivityUnpinnedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* The ID of the feed
*/
@@ -991,8 +1049,11 @@ export interface ActivityUnpinnedEvent {
* The type of event: "feeds.activity.unpinned" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -1000,7 +1061,7 @@ export interface ActivityUpdatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
custom: Record;
@@ -1008,11 +1069,16 @@ export interface ActivityUpdatedEvent {
* The type of the event
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
+export type ActivityVisibility = 'public' | 'private' | 'tag';
+
export interface AddActivityRequest {
/**
* Type of activity
@@ -1053,7 +1119,7 @@ export interface AddActivityRequest {
/**
* Controls who can add comments/replies to this activity. One of: everyone, people_i_follow, nobody
*/
- restrict_replies?: 'everyone' | 'people_i_follow' | 'nobody';
+ restrict_replies?: RestrictReplies;
/**
* Whether to skip URL enrichment for the activity
*/
@@ -1074,7 +1140,7 @@ export interface AddActivityRequest {
/**
* Visibility setting for the activity. One of: public, private, tag
*/
- visibility?: 'public' | 'private' | 'tag';
+ visibility?: ActivityVisibility;
/**
* If visibility is 'tag', this is the tag name and is required
*/
@@ -1439,7 +1505,7 @@ export interface AggregatedActivityResponse {
/**
* When the aggregation was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Grouping identifier
*/
@@ -1451,7 +1517,7 @@ export interface AggregatedActivityResponse {
/**
* When the aggregation was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* Number of unique users in this aggregation
*/
@@ -1515,6 +1581,10 @@ export interface AnalyzeImageField {
* Hierarchical list of L1 (parent) classifications. Each entry: `name`, `confidence` (0–1), and nested `subclassifications` (L2 leaves with their own confidence). Resolved against the app's effective taxonomy (custom taxonomy when configured, otherwise the standard Bodyguard catalogue).
*/
classifications?: Array;
+ /**
+ * URLs found in the image, as `{url, source}` objects: `source` is `qr` (decoded from a QR code) or `ocr` (in text visible in the image). Independent of the action; omitted when none were found.
+ */
+ detected_urls?: Array;
/**
* Flat list of Bodyguard OCR text-moderation labels on the image's extracted text (e.g. VULGARITY, PII). Each entry: `name` + `severity`. Populated when BG's OCR pipeline returned non-empty results for this image.
*/
@@ -1535,7 +1605,8 @@ export interface AnalyzeRequest {
*/
content_published_at?: Date;
/**
- * ISO 3166-1 alpha-2 country the content is aimed at (e.g. US, DE). Forwarded to the AI text provider as country context so it can resolve words whose meaning changes between countries.
+ * @deprecated
+ * ISO 3166-1 alpha-2 country the content is aimed at (e.g. US, DE). Ignored when country_codes is set, but an invalid value is still rejected with 400.
*/
country_code?: string;
/**
@@ -1551,6 +1622,10 @@ export interface AnalyzeRequest {
*/
entity_type?: string;
user_id?: string;
+ /**
+ * ISO 3166-1 alpha-2 countries the content is aimed at (e.g. US, DE), up to 300. The first is the primary country, which AI text providers receive as country context. Takes precedence over country_code.
+ */
+ country_codes?: Array;
/**
* Optional map from a content label (either a `texts` key or an `image:` multipart label) to a caller-supplied per-instance identifier. Echoed on per-field verdicts and surfaced in `matched_contents` when an aggregation rule fires.
*/
@@ -1618,6 +1693,34 @@ export interface AnalyzeTextField {
* Flat list of detected Bodyguard text labels (e.g. INSULT, VULGARITY). Each entry carries `name` and `severity`.
*/
classifications?: Array;
+ /**
+ * URLs found in the text, as `{url, source}` objects with `source` = `text`. Independent of the action; omitted when none were found.
+ */
+ detected_urls?: Array;
+}
+
+export interface AppChannelCountParameters {
+ threshold?: number;
+ time_window?: string;
+}
+
+export interface AppCountInfo {
+ /**
+ * The app-wide count condition that fired: `app_channel_count` or `app_message_count`.
+ */
+ condition_type: string;
+ /**
+ * Channels created or messages sent in the app within the window when the rule fired, the triggering event included.
+ */
+ count: number;
+ /**
+ * The configured threshold. The rule fires when the count reaches it.
+ */
+ threshold: number;
+ /**
+ * The configured time window, e.g. `5m`.
+ */
+ time_window: string;
}
export interface AppDefaults {
@@ -1631,6 +1734,11 @@ export interface AppDefaults {
query_channels: QueryChannelsDefaults;
}
+export interface AppMessageCountParameters {
+ threshold?: number;
+ time_window?: string;
+}
+
export interface AppResponseFields {
allow_multi_user_devices: boolean;
/**
@@ -1680,6 +1788,7 @@ export interface AppResponseFields {
sqs_url: string;
suspended: boolean;
suspended_explanation: string;
+ two_way_block_effect_enabled: boolean;
use_hook_v2: boolean;
user_response_time_enabled: boolean;
webhook_url: string;
@@ -1702,7 +1811,7 @@ export interface AppResponseFields {
chat_primary_use_case?: string;
moderation_onboarding_complete?: boolean;
moderation_s3_image_access_role_arn?: string;
- revoke_tokens_issued_before?: Date;
+ revoke_tokens_issued_before?: TimestampNS;
video_primary_use_case?: string;
allowed_flag_reasons?: Array;
geofences?: Array;
@@ -1713,18 +1822,18 @@ export interface AppResponseFields {
}
export interface AppealAcceptedEvent {
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
appeal?: AppealItemResponse;
}
export interface AppealCreatedEvent {
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
appeal?: AppealItemResponse;
}
@@ -1736,7 +1845,7 @@ export interface AppealItemResponse {
/**
* When the flag was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* ID of the entity
*/
@@ -1753,7 +1862,7 @@ export interface AppealItemResponse {
/**
* When the flag was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* Text severity level assigned by the AI provider
*/
@@ -1820,10 +1929,10 @@ export interface AppealItemResponse {
}
export interface AppealRejectedEvent {
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
appeal?: AppealItemResponse;
}
@@ -1867,107 +1976,134 @@ export interface AppealResponse {
duration: string;
}
+export interface ArrangeConfig {
+ /**
+ * Activity field that defines a source. One of: type, user_id
+ */
+ key?: string;
+ /**
+ * Arrangement mode. Currently only 'blend' is supported; 'diversify' and 'slot' are reserved
+ */
+ mode?: string;
+ /**
+ * Activities scoring strictly below this value are moved to the end of the feed, in score order, before the mode runs. 0 is a valid value. Must be greater than exclude_below when both are set
+ */
+ tail_below?: number;
+ blend?: BlendArrangeConfig;
+}
+
export interface AsyncBulkImageModerationEvent {
- created_at: Date;
- finished_at: Date;
- started_at: Date;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
+ started_at: TimestampNS;
task_id: string;
url: string;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface AsyncExportChannelsEvent {
- created_at: Date;
- finished_at: Date;
- started_at: Date;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
+ started_at: TimestampNS;
task_id: string;
url: string;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface AsyncExportErrorEvent {
- created_at: Date;
+ created_at: TimestampNS;
error: string;
- finished_at: Date;
- started_at: Date;
+ finished_at: TimestampNS;
+ started_at: TimestampNS;
+ task_id: string;
+ custom: Record;
+ type: string;
+ received_at?: TimestampNS;
+}
+
+export interface AsyncExportFeedsSuccessEvent {
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
+ manifest_path: string;
+ started_at: TimestampNS;
task_id: string;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface AsyncExportLabelResultsEvent {
- created_at: Date;
- finished_at: Date;
- started_at: Date;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
+ started_at: TimestampNS;
task_id: string;
url: string;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface AsyncExportModerationLogsEvent {
- created_at: Date;
- finished_at: Date;
- started_at: Date;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
+ started_at: TimestampNS;
task_id: string;
url: string;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface AsyncExportReviewQueueEvent {
- created_at: Date;
- finished_at: Date;
- started_at: Date;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
+ started_at: TimestampNS;
task_id: string;
url: string;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface AsyncExportUsersEvent {
- created_at: Date;
- finished_at: Date;
- started_at: Date;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
+ started_at: TimestampNS;
task_id: string;
url: string;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface AsyncImportErrorEvent {
- created_at: Date;
+ created_at: TimestampNS;
error: string;
error_code: string;
- finished_at: Date;
+ finished_at: TimestampNS;
product: string;
state: string;
task_id: string;
custom: Record;
type: string;
- received_at?: Date;
- started_at?: Date;
+ received_at?: TimestampNS;
+ started_at?: TimestampNS;
}
export interface AsyncImportSuccessEvent {
- created_at: Date;
- finished_at: Date;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
product: string;
state: string;
task_id: string;
custom: Record;
type: string;
- received_at?: Date;
- started_at?: Date;
+ received_at?: TimestampNS;
+ started_at?: TimestampNS;
}
export interface AsyncModerationCallbackConfig {
@@ -2022,9 +2158,14 @@ export interface Audience {
shape?: string;
}
+export type AudioDefaultDevice = 'speaker' | 'earpiece';
+
export interface AudioSettings {
access_request_enabled: boolean;
- default_device: 'speaker' | 'earpiece';
+ /**
+ * The device a call routes audio to by default.
+ */
+ default_device: AudioDefaultDevice;
hifi_audio_enabled: boolean;
mic_default_on: boolean;
opus_dtx_enabled: boolean;
@@ -2034,7 +2175,10 @@ export interface AudioSettings {
}
export interface AudioSettingsRequest {
- default_device: 'speaker' | 'earpiece';
+ /**
+ * The device a call routes audio to by default.
+ */
+ default_device: AudioDefaultDevice;
access_request_enabled?: boolean;
hifi_audio_enabled?: boolean;
mic_default_on?: boolean;
@@ -2046,7 +2190,10 @@ export interface AudioSettingsRequest {
export interface AudioSettingsResponse {
access_request_enabled: boolean;
- default_device: 'speaker' | 'earpiece';
+ /**
+ * The device a call routes audio to by default.
+ */
+ default_device: AudioDefaultDevice;
hifi_audio_enabled: boolean;
mic_default_on: boolean;
opus_dtx_enabled: boolean;
@@ -2066,6 +2213,8 @@ export interface AutomodDetailsResponse {
result?: MessageModerationResult;
}
+export type AutomodMode = 'disabled' | 'simple' | 'AI';
+
export interface AutomodPlatformCircumventionConfig {
async?: boolean;
enabled?: boolean;
@@ -2073,8 +2222,10 @@ export interface AutomodPlatformCircumventionConfig {
}
export interface AutomodRule {
- action:
- 'flag' | 'shadow' | 'remove' | 'bounce' | 'bounce_flag' | 'bounce_remove';
+ /**
+ * What a moderation rule does when it matches.
+ */
+ action: ModerationAction;
label: string;
threshold: number;
}
@@ -2086,8 +2237,10 @@ export interface AutomodSemanticFiltersConfig {
}
export interface AutomodSemanticFiltersRule {
- action:
- 'flag' | 'shadow' | 'remove' | 'bounce' | 'bounce_flag' | 'bounce_remove';
+ /**
+ * What a moderation rule does when it matches.
+ */
+ action: ModerationAction;
name: string;
threshold: number;
}
@@ -2145,7 +2298,7 @@ export interface BanActionRequestPayload {
/**
* Message deletion mode: soft, pruning, or hard
*/
- delete_messages?: 'soft' | 'pruning' | 'hard';
+ delete_messages?: DeleteMessages;
/**
* Whether to ban by IP address
*/
@@ -2172,7 +2325,7 @@ export interface BanInfoResponse {
/**
* When the ban was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* The channel this ban applies to. Empty if this is an app-wide (global) ban rather than a per-channel ban.
*/
@@ -2180,7 +2333,7 @@ export interface BanInfoResponse {
/**
* When the ban expires
*/
- expires?: Date;
+ expires?: TimestampNS;
/**
* Reason for the ban
*/
@@ -2201,7 +2354,10 @@ export interface BanInfoResponse {
}
export interface BanOptions {
- delete_messages?: 'soft' | 'pruning' | 'hard';
+ /**
+ * How a ban or user deletion treats the user's messages.
+ */
+ delete_messages?: DeleteMessages;
duration?: number;
ip_ban?: boolean;
reason?: string;
@@ -2225,7 +2381,10 @@ export interface BanRequest {
* Channel where the ban applies
*/
channel_cid?: string;
- delete_messages?: 'soft' | 'pruning' | 'hard';
+ /**
+ * How a ban or user deletion treats the user's messages.
+ */
+ delete_messages?: DeleteMessages;
/**
* Whether to delete reactions by the banned user on other users' messages
*/
@@ -2253,8 +2412,8 @@ export interface BanRequest {
}
export interface BanResponse {
- created_at: Date;
- expires?: Date;
+ created_at: TimestampNS;
+ expires?: TimestampNS;
reason?: string;
shadow?: boolean;
/**
@@ -2337,6 +2496,21 @@ export interface BatchQueryCommentReactionsResponse {
prev?: string;
}
+export interface BlendArrangeConfig {
+ /**
+ * Minimum gap, 0 to 50, for every source without a min_gap entry
+ */
+ default_min_gap?: number;
+ /**
+ * Minimum number of slots between two items of a source, per key value, 0 to 50. The key '*' requires weights['*']. Combinations of weights and gaps that cannot be satisfied are rejected
+ */
+ min_gap?: Record;
+ /**
+ * Relative weight per key value, greater than 0, at most 8 entries. Weights are normalized, so 80/20 equals 4/1. The key '*' is the catch-all source for key values without their own weight; unweighted values are otherwise placed after the blended section
+ */
+ weights?: Record;
+}
+
export interface BlockActionRequestPayload {
/**
* Reason for blocking
@@ -2355,7 +2529,7 @@ export interface BlockListOptions {
/**
* Blocklist behavior. One of: flag, block, shadow_block
*/
- behavior: 'flag' | 'block' | 'shadow_block';
+ behavior: ChannelModerationBehavior;
/**
* Blocklist name
*/
@@ -2382,14 +2556,14 @@ export interface BlockListResponse {
/**
* Date/time of creation
*/
- created_at?: Date;
+ created_at?: TimestampNS;
id?: string;
owner_user_id?: string;
team?: string;
/**
* Date/time of the last update
*/
- updated_at?: Date;
+ updated_at?: TimestampNS;
}
export interface BlockListRule {
@@ -2444,7 +2618,7 @@ export interface BlockUsersResponse {
/**
* Timestamp when the user was blocked
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Duration of the request in milliseconds
*/
@@ -2453,7 +2627,7 @@ export interface BlockUsersResponse {
export interface BlockedUserEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* User response object
*/
@@ -2473,7 +2647,7 @@ export interface BlockedUserResponse {
* ID of the user who got blocked
*/
blocked_user_id: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* ID of the user who blocked another user
*/
@@ -2488,6 +2662,8 @@ export interface BlockedUserResponse {
user: UserResponse;
}
+export type BlocklistBehavior = 'flag' | 'block';
+
export interface BodyguardImageAnalysisConfig {
rules?: Array;
}
@@ -2530,14 +2706,14 @@ export interface BookmarkAddedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
bookmark: BookmarkResponse;
custom: Record;
/**
* The type of event: "feeds.bookmark.added" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -2545,14 +2721,14 @@ export interface BookmarkDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
bookmark: BookmarkResponse;
custom: Record;
/**
* The type of event: "feeds.bookmark.deleted" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -2560,14 +2736,14 @@ export interface BookmarkFolderDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
bookmark_folder: BookmarkFolderResponse;
custom: Record;
/**
* The type of event: "feeds.bookmark_folder.deleted" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -2575,7 +2751,7 @@ export interface BookmarkFolderResponse {
/**
* When the folder was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Unique identifier for the folder
*/
@@ -2587,7 +2763,7 @@ export interface BookmarkFolderResponse {
/**
* When the folder was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* User response object
*/
@@ -2602,14 +2778,14 @@ export interface BookmarkFolderUpdatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
bookmark_folder: BookmarkFolderResponse;
custom: Record;
/**
* The type of event: "feeds.bookmark_folder.updated" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -2617,7 +2793,7 @@ export interface BookmarkResponse {
/**
* When the bookmark was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* ID of the bookmarked object
*/
@@ -2629,7 +2805,7 @@ export interface BookmarkResponse {
/**
* When the bookmark was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
activity: ActivityResponse;
/**
* User response object
@@ -2648,14 +2824,14 @@ export interface BookmarkUpdatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
bookmark: BookmarkResponse;
custom: Record;
/**
* The type of event: "feeds.bookmark.updated" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -2879,7 +3055,7 @@ export interface BypassResponse {
export interface CallAcceptedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents a call
*/
@@ -2905,11 +3081,11 @@ export interface CallActionOptions {
}
export interface CallClosedCaption {
- end_time: Date;
+ end_time: TimestampNS;
id: string;
language: string;
speaker_id: string;
- start_time: Date;
+ start_time: TimestampNS;
text: string;
translated: boolean;
/**
@@ -2921,7 +3097,7 @@ export interface CallClosedCaption {
export interface CallClosedCaptionsFailedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The type of event: "call.closed_captions_failed" in this case
*/
@@ -2930,7 +3106,7 @@ export interface CallClosedCaptionsFailedEvent {
export interface CallClosedCaptionsStartedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The type of event: "call.closed_captions_started" in this case
*/
@@ -2939,7 +3115,7 @@ export interface CallClosedCaptionsStartedEvent {
export interface CallClosedCaptionsStoppedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The type of event: "call.transcription_stopped" in this case
*/
@@ -2948,7 +3124,7 @@ export interface CallClosedCaptionsStoppedEvent {
export interface CallCreatedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* the members added to this call
*/
@@ -2970,7 +3146,7 @@ export interface CallCustomPropertyParameters {
export interface CallDTMFEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The DTMF digit (0-9, *, #, A-D)
*/
@@ -2986,7 +3162,7 @@ export interface CallDTMFEvent {
/**
* When the digit press ended and was detected
*/
- timestamp: Date;
+ timestamp: TimestampNS;
/**
* User response object
*/
@@ -2999,7 +3175,7 @@ export interface CallDTMFEvent {
export interface CallDeletedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents a call
*/
@@ -3020,7 +3196,7 @@ export interface CallDurationReportResponse {
export interface CallEndedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents a call
*/
@@ -3045,7 +3221,7 @@ export interface CallEndedEvent {
export interface CallFrameRecordingFailedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* Represents a call
@@ -3062,8 +3238,8 @@ export interface CallFrameRecordingFrameReadyEvent {
/**
* The time the frame was captured
*/
- captured_at: Date;
- created_at: Date;
+ captured_at: TimestampNS;
+ created_at: TimestampNS;
egress_id: string;
/**
* Call session ID
@@ -3089,7 +3265,7 @@ export interface CallFrameRecordingFrameReadyEvent {
export interface CallFrameRecordingStartedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* Represents a call
@@ -3103,7 +3279,7 @@ export interface CallFrameRecordingStartedEvent {
export interface CallFrameRecordingStoppedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* Represents a call
@@ -3117,7 +3293,7 @@ export interface CallFrameRecordingStoppedEvent {
export interface CallHLSBroadcastingFailedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The type of event: "call.hls_broadcasting_failed" in this case
*/
@@ -3126,7 +3302,7 @@ export interface CallHLSBroadcastingFailedEvent {
export interface CallHLSBroadcastingStartedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
hls_playlist_url: string;
/**
* Represents a call
@@ -3140,7 +3316,7 @@ export interface CallHLSBroadcastingStartedEvent {
export interface CallHLSBroadcastingStoppedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The type of event: "call.hls_broadcasting_stopped" in this case
*/
@@ -3163,9 +3339,11 @@ export interface CallLevelEventPayload {
payload?: Record;
}
+export type CallLifecycleMode = 'reusable' | 'single_use';
+
export interface CallLiveStartedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents a call
*/
@@ -3178,7 +3356,7 @@ export interface CallLiveStartedEvent {
export interface CallMemberAddedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* the members added to this call
*/
@@ -3195,7 +3373,7 @@ export interface CallMemberAddedEvent {
export interface CallMemberRemovedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* the list of member IDs removed from the call
*/
@@ -3212,7 +3390,7 @@ export interface CallMemberRemovedEvent {
export interface CallMemberUpdatedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The list of members that were updated
*/
@@ -3229,7 +3407,7 @@ export interface CallMemberUpdatedEvent {
export interface CallMemberUpdatedPermissionEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The list of members that were updated
*/
@@ -3250,7 +3428,7 @@ export interface CallMemberUpdatedPermissionEvent {
export interface CallMissedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
notify_user: boolean;
/**
* Call session ID
@@ -3276,7 +3454,7 @@ export interface CallMissedEvent {
export interface CallModerationBlurEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The user ID whose video stream is being blurred
*/
@@ -3293,7 +3471,7 @@ export interface CallModerationBlurEvent {
export interface CallModerationWarningEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The warning message
*/
@@ -3314,7 +3492,7 @@ export interface CallModerationWarningEvent {
export interface CallNotificationEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Call session ID
*/
@@ -3346,7 +3524,7 @@ export interface CallParticipantCountReportResponse {
}
export interface CallParticipantResponse {
- joined_at: Date;
+ joined_at: TimestampNS;
role: string;
user_session_id: string;
/**
@@ -3357,14 +3535,14 @@ export interface CallParticipantResponse {
export interface CallParticipantTimeline {
severity: string;
- timestamp: Date;
+ timestamp: TimestampNS;
type: string;
data: Record;
}
export interface CallReactionEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
reaction: VideoReactionResponse;
/**
* The type of event: "call.reaction_new" in this case
@@ -3373,22 +3551,25 @@ export interface CallReactionEvent {
}
export interface CallRecording {
- end_time: Date;
+ end_time: TimestampNS;
filename: string;
- recording_type: string;
+ /**
+ * The layout a call recording is produced in.
+ */
+ recording_type: RecordingType;
session_id: string;
- start_time: Date;
+ start_time: TimestampNS;
url: string;
}
export interface CallRecordingFailedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* The type of recording
*/
- recording_type: 'composite' | 'individual' | 'raw';
+ recording_type: RecordingType;
/**
* The type of event: "call.recording_failed" in this case
*/
@@ -3397,12 +3578,12 @@ export interface CallRecordingFailedEvent {
export interface CallRecordingReadyEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* The type of recording
*/
- recording_type: 'composite' | 'individual' | 'raw';
+ recording_type: RecordingType;
/**
* CallRecording represents a recording of a call.
*/
@@ -3415,12 +3596,12 @@ export interface CallRecordingReadyEvent {
export interface CallRecordingStartedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* The type of recording
*/
- recording_type: 'composite' | 'individual' | 'raw';
+ recording_type: RecordingType;
/**
* The type of event: "call.recording_started" in this case
*/
@@ -3429,12 +3610,12 @@ export interface CallRecordingStartedEvent {
export interface CallRecordingStoppedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* The type of recording
*/
- recording_type: 'composite' | 'individual' | 'raw';
+ recording_type: RecordingType;
/**
* The type of event: "call.recording_stopped" in this case
*/
@@ -3443,7 +3624,7 @@ export interface CallRecordingStoppedEvent {
export interface CallRejectedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents a call
*/
@@ -3464,8 +3645,8 @@ export interface CallRejectedEvent {
export interface CallReportResponse {
score: number;
- ended_at?: Date;
- started_at?: Date;
+ ended_at?: TimestampNS;
+ started_at?: TimestampNS;
}
export interface CallRequest {
@@ -3493,7 +3674,7 @@ export interface CallResponse {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
current_session_id: string;
/**
* Call ID
@@ -3509,7 +3690,7 @@ export interface CallResponse {
/**
* Date/time of the last update
*/
- updated_at: Date;
+ updated_at: TimestampNS;
blocked_user_ids: Array;
/**
* User response object
@@ -3529,7 +3710,7 @@ export interface CallResponse {
/**
* Date/time when the call ended
*/
- ended_at?: Date;
+ ended_at?: TimestampNS;
join_ahead_time_seconds?: number;
/**
* 10-digit routing number for SIP routing
@@ -3538,7 +3719,7 @@ export interface CallResponse {
/**
* Date/time when the call will start
*/
- starts_at?: Date;
+ starts_at?: TimestampNS;
team?: string;
session?: CallSessionResponse;
thumbnails?: ThumbnailResponse;
@@ -3546,7 +3727,7 @@ export interface CallResponse {
export interface CallRingEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Call session ID
*/
@@ -3576,7 +3757,7 @@ export interface CallRingEvent {
export interface CallRingSettledEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* ID of the user who rang
*/
@@ -3588,7 +3769,7 @@ export interface CallRingSettledEvent {
/**
* When the ring was sent
*/
- rang_at: Date;
+ rang_at: TimestampNS;
/**
* Identifies this ring of the call session, as carried on call.ring
*/
@@ -3604,7 +3785,7 @@ export interface CallRingSettledEvent {
/**
* When the outcome left ringing
*/
- settled_at: Date;
+ settled_at: TimestampNS;
/**
* ID of the user whose action closed the ring, or 'server' for the missed-call deadline and automatic ends
*/
@@ -3627,7 +3808,7 @@ export interface CallRtmpBroadcastFailedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Name of the given RTMP broadcast
*/
@@ -3646,7 +3827,7 @@ export interface CallRtmpBroadcastStartedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Name of the given RTMP broadcast
*/
@@ -3665,7 +3846,7 @@ export interface CallRtmpBroadcastStoppedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Name of the given RTMP broadcast
*/
@@ -3684,7 +3865,7 @@ export interface CallRuleActionSequence {
export interface CallSessionEndedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Call session ID
*/
@@ -3702,7 +3883,7 @@ export interface CallSessionEndedEvent {
export interface CallSessionParticipantCountsUpdatedEvent {
anonymous_participant_count: number;
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Call session ID
*/
@@ -3716,7 +3897,7 @@ export interface CallSessionParticipantCountsUpdatedEvent {
export interface CallSessionParticipantJoinedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Call session ID
*/
@@ -3730,7 +3911,7 @@ export interface CallSessionParticipantJoinedEvent {
export interface CallSessionParticipantLeftEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The duration participant was in the session in seconds
*/
@@ -3754,20 +3935,20 @@ export interface CallSessionResponse {
anonymous_participant_count: number;
id: string;
participants: Array;
- accepted_by: Record;
- missed_by: Record;
+ accepted_by: Record;
+ missed_by: Record;
participants_count_by_role: Record;
- rejected_by: Record;
- ended_at?: Date;
- live_ended_at?: Date;
- live_started_at?: Date;
- started_at?: Date;
- timer_ends_at?: Date;
+ rejected_by: Record;
+ ended_at?: TimestampNS;
+ live_ended_at?: TimestampNS;
+ live_started_at?: TimestampNS;
+ started_at?: TimestampNS;
+ timer_ends_at?: TimestampNS;
}
export interface CallSessionStartedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Call session ID
*/
@@ -3918,7 +4099,7 @@ export interface CallStatsMapSubscribers {
export interface CallStatsParticipant {
user_id: string;
sessions: Array;
- latest_activity_at?: Date;
+ latest_activity_at?: TimestampNS;
name?: string;
roles?: Array;
}
@@ -3951,7 +4132,7 @@ export interface CallStatsParticipantSession {
current_ip?: string;
current_sfu?: string;
distance_to_sfu_kilometers?: number;
- ended_at?: Date;
+ ended_at?: TimestampNS;
freezes_duration_ms?: number;
ingress?: string;
jitter_ms?: number;
@@ -3960,7 +4141,7 @@ export interface CallStatsParticipantSession {
publisher_type?: string;
sdk?: string;
sdk_version?: string;
- started_at?: Date;
+ started_at?: TimestampNS;
unified_session_id?: string;
webrtc_version?: string;
location?: CallStatsLocation;
@@ -3968,7 +4149,7 @@ export interface CallStatsParticipantSession {
export interface CallStatsReportReadyEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Call session ID
*/
@@ -3993,8 +4174,8 @@ export interface CallStatsReportSummaryResponse {
call_duration_seconds: number;
call_session_id: string;
call_status: string;
- first_stats_time: Date;
- created_at?: Date;
+ first_stats_time: TimestampNS;
+ created_at?: TimestampNS;
min_user_rating?: number;
quality_score?: number;
}
@@ -4003,23 +4184,23 @@ export interface CallStatsSessionResponse {
call_id: string;
call_session_id: string;
call_type: string;
- generated_at: Date;
+ generated_at: TimestampNS;
counts: CallStatsParticipantCounts;
- call_ended_at?: Date;
- call_started_at?: Date;
+ call_ended_at?: TimestampNS;
+ call_started_at?: TimestampNS;
}
export interface CallTranscription {
- end_time: Date;
+ end_time: TimestampNS;
filename: string;
session_id: string;
- start_time: Date;
+ start_time: TimestampNS;
url: string;
}
export interface CallTranscriptionFailedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* The type of event: "call.transcription_failed" in this case
@@ -4033,7 +4214,7 @@ export interface CallTranscriptionFailedEvent {
export interface CallTranscriptionReadyEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* CallTranscription represents a transcription of a call.
@@ -4047,7 +4228,7 @@ export interface CallTranscriptionReadyEvent {
export interface CallTranscriptionStartedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* The type of event: "call.transcription_started" in this case
@@ -4057,7 +4238,7 @@ export interface CallTranscriptionStartedEvent {
export interface CallTranscriptionStoppedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
egress_id: string;
/**
* The type of event: "call.transcription_stopped" in this case
@@ -4067,11 +4248,11 @@ export interface CallTranscriptionStoppedEvent {
export interface CallType {
app: number;
- created_at: Date;
+ created_at: TimestampNS;
id: number;
name: string;
recording_external_storage: string;
- updated_at: Date;
+ updated_at: TimestampNS;
notification_settings?: NotificationSettings;
settings?: CallSettings;
}
@@ -4080,7 +4261,7 @@ export interface CallTypeResponse {
/**
* the time the call type was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* the name of the call type
*/
@@ -4088,7 +4269,7 @@ export interface CallTypeResponse {
/**
* the time the call type was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* the permissions granted to each role
*/
@@ -4107,7 +4288,7 @@ export interface CallTypeRuleParameters {
export interface CallUpdatedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents a call
*/
@@ -4124,7 +4305,7 @@ export interface CallUpdatedEvent {
export interface CallUserFeedbackSubmittedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* The rating given by the user (1-5)
*/
@@ -4155,7 +4336,7 @@ export interface CallUserFeedbackSubmittedEvent {
export interface CallUserMutedEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
from_user_id: string;
reason: string;
muted_user_ids: Array;
@@ -4178,6 +4359,8 @@ export interface CallsPerDayReportResponse {
daily: Array;
}
+export type CameraFacing = 'front' | 'back' | 'external';
+
export interface CampaignChannelMember {
user_id: string;
channel_role?: string;
@@ -4194,10 +4377,10 @@ export interface CampaignChannelTemplate {
}
export interface CampaignCompletedEvent {
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
campaign?: CampaignResponse;
}
@@ -4211,25 +4394,31 @@ export interface CampaignMessageTemplate {
export interface CampaignResponse {
create_channels: boolean;
- created_at: Date;
+ created_at: TimestampNS;
description: string;
id: string;
name: string;
sender_id: string;
- sender_mode: string;
- sender_visibility: string;
show_channels: boolean;
skip_push: boolean;
skip_webhook: boolean;
status: string;
- updated_at: Date;
+ updated_at: TimestampNS;
segment_ids: Array;
segments: Array;
user_ids: Array;
users: Array;
stats: CampaignStatsResponse;
- scheduled_for?: Date;
- stop_at?: Date;
+ scheduled_for?: TimestampNS;
+ /**
+ * Whether a campaign includes or excludes its sender.
+ */
+ sender_mode?: CampaignSenderMode;
+ /**
+ * How a campaign appears to its sender.
+ */
+ sender_visibility?: CampaignSenderVisibility;
+ stop_at?: TimestampNS;
channel_template?: CampaignChannelTemplate;
message_template?: CampaignMessageTemplate;
/**
@@ -4238,24 +4427,32 @@ export interface CampaignResponse {
sender?: UserResponse;
}
+export type CampaignSenderMode = 'exclude' | 'include';
+
+export type CampaignSenderVisibility = 'hidden' | 'archived';
+
export interface CampaignStartedEvent {
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
campaign?: CampaignResponse;
}
export interface CampaignStatsResponse {
progress: number;
stats_channels_created: number;
- stats_completed_at: Date;
+ stats_completed_at: TimestampNS;
stats_messages_sent: number;
- stats_started_at: Date;
+ stats_started_at: TimestampNS;
stats_users_read: number;
stats_users_sent: number;
}
+export interface CancelExportFeedsResponse {
+ duration: string;
+}
+
export interface CancelImportV2TaskResponse {
/**
* Duration of the request in milliseconds
@@ -4284,7 +4481,7 @@ export interface ChangeFeedVisibilityRequest {
/**
* Feed visibility level: public, visible, followers, members, or private
*/
- visibility: 'public' | 'visible' | 'followers' | 'members' | 'private';
+ visibility: FeedVisibility;
/**
* What to do with existing pending follows when loosening visibility from 'followers': auto_approve (default) or reject
*/
@@ -4297,7 +4494,7 @@ export interface ChangeFeedVisibilityResponse {
}
export interface ChannelBanResponse {
- created_at: Date;
+ created_at: TimestampNS;
scopes: Array;
channel_ban?: ChannelBanScopeResponse;
future_channel_ban?: ChannelBanScopeResponse;
@@ -4308,9 +4505,9 @@ export interface ChannelBanResponse {
}
export interface ChannelBanScopeResponse {
- created_at: Date;
+ created_at: TimestampNS;
shadow: boolean;
- expires?: Date;
+ expires?: TimestampNS;
reason?: string;
/**
* User response object
@@ -4324,9 +4521,9 @@ export interface ChannelBansScopeTotals {
}
export interface ChannelBatchCompletedEvent {
- batch_created_at: Date;
- created_at: Date;
- finished_at: Date;
+ batch_created_at: TimestampNS;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
operation: string;
status: string;
success_channels_count: number;
@@ -4334,7 +4531,7 @@ export interface ChannelBatchCompletedEvent {
failed_channels: Array;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface ChannelBatchMemberRequest {
@@ -4343,9 +4540,9 @@ export interface ChannelBatchMemberRequest {
}
export interface ChannelBatchStartedEvent {
- batch_created_at: Date;
- created_at: Date;
- finished_at: Date;
+ batch_created_at: TimestampNS;
+ created_at: TimestampNS;
+ finished_at: TimestampNS;
operation: string;
status: string;
success_channels_count: number;
@@ -4353,7 +4550,7 @@ export interface ChannelBatchStartedEvent {
failed_channels: Array;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface ChannelBatchUpdateRequest {
@@ -4380,7 +4577,7 @@ export interface ChannelBatchUpdateRequest {
*/
hide_history_before?: Date;
/**
- * For updateData only. Requires a root cids $eq or $in filter with at most 100 CIDs and no root $or/$and. Split larger selections into requests of at most 100 CIDs. A success_channels_count response means the database update completed; a task_id response means it was queued and must be polled, including on older API nodes.
+ * For updateData and hide only. Requires a root cids $eq or $in filter with at most 100 CIDs and no root $or/$and. A synchronous hide also requires 1 to 10 distinct member user IDs. Split larger selections into requests of at most 100 CIDs. A success_channels_count response means the database update completed; a task_id response means it was queued and must be polled, including on older API nodes.
*/
synchronous?: boolean;
/**
@@ -4398,7 +4595,7 @@ export interface ChannelBatchUpdateRequest {
export interface ChannelBatchUpdateResponse {
duration: string;
/**
- * Positive count of channels selected for a completed synchronous database update, not an affected-row count. Concurrent deletion may reduce the rows written. task_id is absent.
+ * Count of channels selected for a completed synchronous database update, not an affected-row count. Concurrent deletion may reduce the rows written. Omitted when the count is 0, which happens for a synchronous hide where none of the member users exist. task_id is absent.
*/
success_channels_count?: number;
/**
@@ -4408,11 +4605,17 @@ export interface ChannelBatchUpdateResponse {
}
export interface ChannelConfig {
- automod: 'disabled' | 'simple' | 'AI';
- automod_behavior: 'flag' | 'block' | 'shadow_block';
+ /**
+ * Automod level applied to a channel type.
+ */
+ automod: AutomodMode;
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ automod_behavior: ChannelModerationBehavior;
connect_events: boolean;
count_messages: boolean;
- created_at: Date;
+ created_at: TimestampNS;
custom_events: boolean;
delivery_events: boolean;
mark_messages_pending: boolean;
@@ -4431,7 +4634,7 @@ export interface ChannelConfig {
shared_locations: boolean;
skip_last_msg_update_for_system_msgs: boolean;
typing_events: boolean;
- updated_at: Date;
+ updated_at: TimestampNS;
uploads: boolean;
url_enrichment: boolean;
user_message_reminders: boolean;
@@ -4440,13 +4643,16 @@ export interface ChannelConfig {
*/
commands: Array;
blocklist?: string;
- blocklist_behavior?: 'flag' | 'block' | 'shadow_block';
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ blocklist_behavior?: ChannelModerationBehavior;
partition_size?: number;
partition_ttl?: string;
/**
* Sets the push notification level for a channel type
*/
- push_level?: 'all' | 'all_mentions' | 'mentions' | 'direct_mentions' | 'none';
+ push_level?: PushLevel;
allowed_flag_reasons?: Array;
blocklists?: Array;
/**
@@ -4458,7 +4664,10 @@ export interface ChannelConfig {
export interface ChannelConfigOverrides {
blocklist?: string;
- blocklist_behavior?: 'flag' | 'block';
+ /**
+ * What a blocklist does when it matches. shadow_block is not accepted on a channel override.
+ */
+ blocklist_behavior?: BlocklistBehavior;
/**
* Enable/disable message counting
*/
@@ -4470,7 +4679,7 @@ export interface ChannelConfigOverrides {
/**
* Overrides the push notification level for this channel
*/
- push_level?: 'all' | 'all_mentions' | 'mentions' | 'direct_mentions' | 'none';
+ push_level?: PushLevel;
/**
* Enables message quotes
*/
@@ -4512,11 +4721,17 @@ export interface ChannelConfigOverrides {
}
export interface ChannelConfigWithInfo {
- automod: 'disabled' | 'simple' | 'AI';
- automod_behavior: 'flag' | 'block' | 'shadow_block';
+ /**
+ * Automod level applied to a channel type.
+ */
+ automod: AutomodMode;
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ automod_behavior: ChannelModerationBehavior;
connect_events: boolean;
count_messages: boolean;
- created_at: Date;
+ created_at: TimestampNS;
custom_events: boolean;
delivery_events: boolean;
mark_messages_pending: boolean;
@@ -4535,19 +4750,38 @@ export interface ChannelConfigWithInfo {
shared_locations: boolean;
skip_last_msg_update_for_system_msgs: boolean;
typing_events: boolean;
- updated_at: Date;
+ updated_at: TimestampNS;
uploads: boolean;
url_enrichment: boolean;
user_message_reminders: boolean;
commands: Array;
blocklist?: string;
- blocklist_behavior?: 'flag' | 'block' | 'shadow_block';
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ blocklist_behavior?: ChannelModerationBehavior;
partition_size?: number;
partition_ttl?: string;
/**
* Sets the push notification level for a channel type
*/
- push_level?: 'all' | 'all_mentions' | 'mentions' | 'direct_mentions' | 'none';
+ push_level?: PushLevel;
+ /**
+ * How far ahead, in seconds, a message can be scheduled. Present only when scheduled messages are enabled for the app. A later scheduled_at returns error code 116 (scheduled_at_too_far), whose max_lead_time_seconds carries the same value
+ */
+ scheduled_messages_max_lead_time_seconds?: number;
+ /**
+ * Maximum number of scheduled messages (scheduled or sending) a user can have in this channel. Present only when scheduled messages are enabled for the app. Scheduling more returns error code 117 (scheduled_limit_channel)
+ */
+ scheduled_messages_max_per_channel?: number;
+ /**
+ * Maximum number of scheduled messages (scheduled or sending) a user can have across all channels. Present only when scheduled messages are enabled for the app. Scheduling more returns error code 118 (scheduled_limit_user)
+ */
+ scheduled_messages_max_per_user?: number;
+ /**
+ * Minimum time ahead, in seconds, a message is scheduled for. Present only when scheduled messages are enabled for the app. A future scheduled_at closer than this is moved later, to the first whole minute at least this far ahead; it is never rejected
+ */
+ scheduled_messages_min_lead_time_seconds?: number;
allowed_flag_reasons?: Array;
blocklists?: Array;
/**
@@ -4581,7 +4815,7 @@ export interface ChannelCreatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents channel in chat
*/
@@ -4608,7 +4842,7 @@ export interface ChannelCreatedEvent {
* The CID of the channel which was created
*/
cid?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The team ID
*/
@@ -4634,7 +4868,7 @@ export interface ChannelDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents channel in chat
*/
@@ -4661,7 +4895,7 @@ export interface ChannelDeletedEvent {
* The CID of the channel which was deleted
*/
cid?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The team ID
*/
@@ -4690,11 +4924,121 @@ export interface ChannelExport {
type?: string;
}
+export interface ChannelForkResponse {
+ /**
+ * CID of the fork
+ */
+ channel_cid: string;
+ /**
+ * Number of messages copied into the fork, once completed
+ */
+ copied_message_count: number;
+ /**
+ * Date/time of the fork
+ */
+ created_at: TimestampNS;
+ /**
+ * CID of the channel the fork was copied from
+ */
+ source_channel_cid: string;
+ /**
+ * ID of the message of the source channel the fork was made at
+ */
+ source_message_id: string;
+ /**
+ * `completed` once every message is copied. `backfilling` while a background task copies the history older than the newest page; the fork is usable meanwhile, and the newest messages, which an agent reads, are already there
+ */
+ status: string;
+ /**
+ * Maps the ID of each message the request copied, the newest page, to its ID in the fork. A fork message ID is a UUIDv5 of the fork CID and the source message ID; the fork message IDs endpoint maps any others
+ */
+ message_id_map: Record;
+ /**
+ * ID of the task copying the older history of a backfilling fork
+ */
+ task_id?: string;
+}
+
+export interface ChannelForkSummary {
+ /**
+ * CID of the fork
+ */
+ channel_cid: string;
+ /**
+ * Number of messages copied into the fork, once completed
+ */
+ copied_message_count: number;
+ /**
+ * Date/time of the fork
+ */
+ created_at: TimestampNS;
+ /**
+ * CID of the channel the fork was copied from
+ */
+ source_channel_cid: string;
+ /**
+ * ID of the message of the source channel the fork was made at
+ */
+ source_message_id: string;
+ /**
+ * `completed` once every message is copied. `backfilling` while a background task copies the history older than the newest page; the fork is usable meanwhile, and the newest messages, which an agent reads, are already there
+ */
+ status: string;
+ /**
+ * ID of the task copying the older history of a backfilling fork
+ */
+ task_id?: string;
+}
+
+export interface ChannelForkedEvent {
+ /**
+ * Date/time of creation
+ */
+ created_at: TimestampNS;
+ /**
+ * Represents channel in chat
+ */
+ channel: ChannelResponse;
+ custom: Record;
+ /**
+ * A fork of an agent channel
+ */
+ fork: ChannelForkSummary;
+ /**
+ * The type of event: "channel.forked" in this case
+ */
+ type: string;
+ /**
+ * The ID of the channel which was forked
+ */
+ channel_id?: string;
+ /**
+ * The number of members in the channel
+ */
+ channel_member_count?: number;
+ channel_message_count?: number;
+ /**
+ * The type of the channel which was forked
+ */
+ channel_type?: string;
+ /**
+ * The CID of the channel which was forked
+ */
+ cid?: string;
+ received_at?: TimestampNS;
+ /**
+ * The team ID
+ */
+ team?: string;
+ channel_custom?: Record;
+ user?: UserResponseCommonFields;
+}
+
export interface ChannelFrozenEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
/**
* The type of event: "channel.frozen" in this case
@@ -4712,7 +5056,7 @@ export interface ChannelFrozenEvent {
* The CID of the channel which was frozen
*/
cid?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface ChannelGetOrCreateRequest {
@@ -4720,6 +5064,10 @@ export interface ChannelGetOrCreateRequest {
* Whether this channel will be hidden for the user who created the channel or not
*/
hide_for_creator?: boolean;
+ /**
+ * Adds scheduled_messages to the response: the acting user's own scheduled, sending and failed messages in this channel (server-side requests name the user with data.created_by_id or data.created_by; without one the field is omitted). Absent from the response when there are more than 100 or they could not be loaded; use the scheduled messages query then
+ */
+ include_scheduled_messages?: boolean;
/**
* Refresh channel state
*/
@@ -4743,7 +5091,7 @@ export interface ChannelHiddenEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents channel in chat
*/
@@ -4770,7 +5118,7 @@ export interface ChannelHiddenEvent {
* The CID of the channel which was hidden
*/
cid?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The team ID
*/
@@ -4869,7 +5217,7 @@ export interface ChannelMemberResponse {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
notifications_muted: boolean;
/**
* Whether member is shadow banned in this channel or not
@@ -4878,31 +5226,31 @@ export interface ChannelMemberResponse {
/**
* Date/time of the last update
*/
- updated_at: Date;
+ updated_at: TimestampNS;
user_id: string;
custom: Record;
- archived_at?: Date;
+ archived_at?: TimestampNS;
/**
* Expiration date of the ban
*/
- ban_expires?: Date;
+ ban_expires?: TimestampNS;
/**
* Whether the member's ban also applies to channels the channel's creator will create in the future (an active future channel ban by the creator targets this member)
*/
ban_from_future_channels?: boolean;
- deleted_at?: Date;
+ deleted_at?: TimestampNS;
/**
* Expiration date of the future channel ban; absent when the future channel ban is permanent
*/
- future_channel_ban_expires?: Date;
+ future_channel_ban_expires?: TimestampNS;
/**
* Date when invite was accepted
*/
- invite_accepted_at?: Date;
+ invite_accepted_at?: TimestampNS;
/**
* Date when invite was rejected
*/
- invite_rejected_at?: Date;
+ invite_rejected_at?: TimestampNS;
/**
* Whether member was invited or not
*/
@@ -4911,7 +5259,7 @@ export interface ChannelMemberResponse {
* Whether member is channel moderator or not
*/
is_moderator?: boolean;
- pinned_at?: Date;
+ pinned_at?: TimestampNS;
/**
* Permission level of the member in the channel (DEPRECATED: use channel_role instead). One of: member, moderator, admin, owner
*/
@@ -4945,26 +5293,31 @@ export interface ChannelMetadata {
id: string;
type: string;
custom: Record;
- last_message_at?: Date;
+ last_message_at?: TimestampNS;
member_count?: number;
message_count?: number;
- push_level?: string;
+ /**
+ * How much a channel notifies its members.
+ */
+ push_level?: PushLevel;
team?: string;
}
+export type ChannelModerationBehavior = 'flag' | 'block' | 'shadow_block';
+
export interface ChannelMute {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Date/time of the last update
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* Date/time of mute expiration
*/
- expires?: Date;
+ expires?: TimestampNS;
/**
* Represents channel in chat
*/
@@ -4979,13 +5332,13 @@ export interface ChannelMutedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
/**
* The type of event: "channel.muted" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The mute objects
*/
@@ -5051,7 +5404,7 @@ export interface ChannelPushPreferencesResponse {
/**
* If provided the notifications will be disabled until the set date.
*/
- disabled_until?: Date;
+ disabled_until?: TimestampNS;
chat_preferences?: ChatPreferencesResponse;
}
@@ -5063,7 +5416,7 @@ export interface ChannelResponse {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
disabled: boolean;
/**
* Whether channel is frozen or not
@@ -5080,7 +5433,7 @@ export interface ChannelResponse {
/**
* Date/time of the last update
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* Custom data for this object
*/
@@ -5108,7 +5461,7 @@ export interface ChannelResponse {
/**
* Date/time of deletion
*/
- deleted_at?: Date;
+ deleted_at?: TimestampNS;
/**
* Whether this channel is hidden by current user or not
*/
@@ -5116,11 +5469,11 @@ export interface ChannelResponse {
/**
* Date since when the message history is accessible
*/
- hide_messages_before?: Date;
+ hide_messages_before?: TimestampNS;
/**
* Date of the last message sent
*/
- last_message_at?: Date;
+ last_message_at?: TimestampNS;
/**
* Number of members in the channel
*/
@@ -5132,7 +5485,7 @@ export interface ChannelResponse {
/**
* Date of mute expiration
*/
- mute_expires_at?: Date;
+ mute_expires_at?: TimestampNS;
/**
* Whether this channel is muted or not
*/
@@ -5144,7 +5497,7 @@ export interface ChannelResponse {
/**
* Date of the latest truncation of the channel
*/
- truncated_at?: Date;
+ truncated_at?: TimestampNS;
/**
* List of filter tags associated with the channel
*/
@@ -5168,45 +5521,112 @@ export interface ChannelResponse {
truncated_by?: UserResponse;
}
-export interface ChannelStateResponse {
- duration: string;
+export interface ChannelRewindResponse {
/**
- * List of channel members
+ * Date/time of the rewind
*/
- members: Array;
+ created_at: TimestampNS;
/**
- * List of channel messages
+ * Number of messages the rewind hid that were not hidden already
*/
- messages: Array;
+ hidden_count_at_rewind: number;
/**
- * List of pinned messages in the channel
+ * ID of the rewind
*/
- pinned_messages: Array;
- threads: Array;
+ id: string;
/**
- * Milliseconds until the current user's slow mode cool down period ends. Only set on client-side get or create channel and get channel requests while the user is under cool down
+ * Whether `message_id` was hidden too
*/
- cooldown_remaining_ms?: number;
+ inclusive: boolean;
/**
- * Whether this channel is hidden or not
+ * ID of the message the channel was rewound to
*/
- hidden?: boolean;
+ message_id: string;
+}
+
+export interface ChannelRewoundEvent {
/**
- * Messages before this date are hidden from the user
+ * Date/time of creation
*/
- hide_messages_before?: Date;
+ created_at: TimestampNS;
/**
- * Number of channel watchers
+ * Represents channel in chat
*/
- watcher_count?: number;
+ channel: ChannelResponse;
+ custom: Record;
/**
- * Active live locations in the channel
+ * A rewind of an agent channel
*/
- active_live_locations?: Array;
+ rewind: ChannelRewindResponse;
/**
- * Pending messages that this user has sent
+ * The type of event: "channel.rewound" in this case
*/
- pending_messages?: Array;
+ type: string;
+ /**
+ * The ID of the channel which was rewound
+ */
+ channel_id?: string;
+ /**
+ * The number of members in the channel
+ */
+ channel_member_count?: number;
+ channel_message_count?: number;
+ /**
+ * The type of the channel which was rewound
+ */
+ channel_type?: string;
+ /**
+ * The CID of the channel which was rewound
+ */
+ cid?: string;
+ received_at?: TimestampNS;
+ /**
+ * The team ID
+ */
+ team?: string;
+ channel_custom?: Record;
+ user?: UserResponseCommonFields;
+}
+
+export interface ChannelStateResponse {
+ duration: string;
+ /**
+ * List of channel members
+ */
+ members: Array;
+ /**
+ * List of channel messages
+ */
+ messages: Array;
+ /**
+ * List of pinned messages in the channel
+ */
+ pinned_messages: Array;
+ threads: Array;
+ /**
+ * Milliseconds until the current user's slow mode cool down period ends. Only set on client-side get or create channel and get channel requests while the user is under cool down
+ */
+ cooldown_remaining_ms?: number;
+ /**
+ * Whether this channel is hidden or not
+ */
+ hidden?: boolean;
+ /**
+ * Messages before this date are hidden from the user
+ */
+ hide_messages_before?: TimestampNS;
+ /**
+ * Number of channel watchers
+ */
+ watcher_count?: number;
+ /**
+ * Active live locations in the channel
+ */
+ active_live_locations?: Array;
+ /**
+ * Pending messages that this user has sent
+ */
+ pending_messages?: Array;
/**
* List of read states
*/
@@ -5256,7 +5676,7 @@ export interface ChannelStateResponseFields {
/**
* Messages before this date are hidden from the user
*/
- hide_messages_before?: Date;
+ hide_messages_before?: TimestampNS;
/**
* Number of channel watchers
*/
@@ -5297,7 +5717,7 @@ export interface ChannelTruncatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents channel in chat
*/
@@ -5325,7 +5745,7 @@ export interface ChannelTruncatedEvent {
*/
cid?: string;
message_id?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The team ID
*/
@@ -5339,11 +5759,17 @@ export interface ChannelTruncatedEvent {
}
export interface ChannelTypeConfig {
- automod: 'disabled' | 'simple' | 'AI';
- automod_behavior: 'flag' | 'block' | 'shadow_block';
+ /**
+ * Automod level applied to a channel type.
+ */
+ automod: AutomodMode;
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ automod_behavior: ChannelModerationBehavior;
connect_events: boolean;
count_messages: boolean;
- created_at: Date;
+ created_at: TimestampNS;
custom_events: boolean;
delivery_events: boolean;
mark_messages_pending: boolean;
@@ -5362,7 +5788,7 @@ export interface ChannelTypeConfig {
shared_locations: boolean;
skip_last_msg_update_for_system_msgs: boolean;
typing_events: boolean;
- updated_at: Date;
+ updated_at: TimestampNS;
uploads: boolean;
url_enrichment: boolean;
user_message_reminders: boolean;
@@ -5370,13 +5796,16 @@ export interface ChannelTypeConfig {
permissions: Array;
grants: Record>;
blocklist?: string;
- blocklist_behavior?: 'flag' | 'block' | 'shadow_block';
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ blocklist_behavior?: ChannelModerationBehavior;
partition_size?: number;
partition_ttl?: string;
/**
* Sets the push notification level for a channel type
*/
- push_level?: 'all' | 'all_mentions' | 'mentions' | 'direct_mentions' | 'none';
+ push_level?: PushLevel;
allowed_flag_reasons?: Array;
blocklists?: Array;
/**
@@ -5390,7 +5819,7 @@ export interface ChannelUnFrozenEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
/**
* The type of event: "channel.unfrozen" in this case
@@ -5408,20 +5837,20 @@ export interface ChannelUnFrozenEvent {
* The CID of the channel which was unfrozen
*/
cid?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface ChannelUnmutedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
/**
* The type of event: "channel.unmuted" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The mute objects
*/
@@ -5434,7 +5863,7 @@ export interface ChannelUpdatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents channel in chat
*/
@@ -5462,7 +5891,7 @@ export interface ChannelUpdatedEvent {
*/
cid?: string;
message_id?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The team ID
*/
@@ -5479,7 +5908,7 @@ export interface ChannelVisibleEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Represents channel in chat
*/
@@ -5506,7 +5935,7 @@ export interface ChannelVisibleEvent {
* The CID of the channel which was shown
*/
cid?: string;
- received_at?: Date;
+ received_at?: TimestampNS;
/**
* The team ID
*/
@@ -5537,7 +5966,7 @@ export interface ChatDraftPayloadResponse {
export interface ChatDraftResponse {
channel_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
message: ChatDraftPayloadResponse;
parent_id?: string;
parent_message?: ChatMessageResponse;
@@ -5546,7 +5975,7 @@ export interface ChatDraftResponse {
export interface ChatMessageResponse {
cid: string;
- created_at: Date;
+ created_at: TimestampNS;
deleted_reply_count: number;
html: string;
id: string;
@@ -5558,7 +5987,7 @@ export interface ChatMessageResponse {
silent: boolean;
text: string;
type: string;
- updated_at: Date;
+ updated_at: TimestampNS;
attachments: Array;
latest_reactions: Array;
mentioned_users: Array;
@@ -5572,13 +6001,13 @@ export interface ChatMessageResponse {
*/
user: UserResponse;
command?: string;
- deleted_at?: Date;
+ deleted_at?: TimestampNS;
deleted_for_me?: boolean;
- message_text_updated_at?: Date;
+ message_text_updated_at?: TimestampNS;
mml?: string;
parent_id?: string;
- pin_expires?: Date;
- pinned_at?: Date;
+ pin_expires?: TimestampNS;
+ pinned_at?: TimestampNS;
poll_id?: string;
quoted_message_id?: string;
show_in_channel?: boolean;
@@ -5646,14 +6075,14 @@ export interface ChatPreferencesResponse {
export interface ChatReactionGroupResponse {
count: number;
- first_reaction_at: Date;
- last_reaction_at: Date;
+ first_reaction_at: TimestampNS;
+ last_reaction_at: TimestampNS;
sum_scores: number;
latest_reactions_by: Array;
}
export interface ChatReactionGroupUserResponse {
- created_at: Date;
+ created_at: TimestampNS;
user_id: string;
/**
* User response object
@@ -5662,11 +6091,11 @@ export interface ChatReactionGroupUserResponse {
}
export interface ChatReactionResponse {
- created_at: Date;
+ created_at: TimestampNS;
message_id: string;
score: number;
type: string;
- updated_at: Date;
+ updated_at: TimestampNS;
user_id: string;
custom: Record;
/**
@@ -5677,11 +6106,11 @@ export interface ChatReactionResponse {
export interface ChatReminderResponseData {
channel_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
message_id: string;
- updated_at: Date;
+ updated_at: TimestampNS;
user_id: string;
- remind_at?: Date;
+ remind_at?: TimestampNS;
message?: ChatMessageResponse;
/**
* User response object
@@ -5691,14 +6120,14 @@ export interface ChatReminderResponseData {
export interface ChatSharedLocationResponseData {
channel_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
created_by_device_id: string;
latitude: number;
longitude: number;
message_id: string;
- updated_at: Date;
+ updated_at: TimestampNS;
user_id: string;
- end_at?: Date;
+ end_at?: TimestampNS;
message?: ChatMessageResponse;
}
@@ -5780,7 +6209,7 @@ export interface CheckPushRequest {
/**
* Push provider type. One of: firebase, apn, huawei, xiaomi
*/
- push_provider_type?: string;
+ push_provider_type?: PushProviderName;
/**
* Don't require existing devices to render templates
*/
@@ -5841,7 +6270,8 @@ export interface CheckRequest {
*/
content_published_at?: Date;
/**
- * ISO 3166-1 alpha-2 country the content is aimed at (e.g. US, DE), used as country context by AI text providers
+ * @deprecated
+ * ISO 3166-1 alpha-2 country the content is aimed at (e.g. US, DE). Ignored when country_codes is set, but an invalid value is still rejected with 400.
*/
country_code?: string;
/**
@@ -5849,6 +6279,10 @@ export interface CheckRequest {
*/
test_mode?: boolean;
user_id?: string;
+ /**
+ * ISO 3166-1 alpha-2 countries the content is aimed at (e.g. US, DE), up to 300. The first is the primary country, which AI text providers receive as country context. Takes precedence over country_code.
+ */
+ country_codes?: Array;
config?: ModerationConfig;
moderation_payload?: ModerationPayload;
/**
@@ -5979,6 +6413,28 @@ export interface CheckSQSResponse {
data?: Record;
}
+export interface ClaimGuestUserRequest {
+ /**
+ * ID of the guest user to claim
+ */
+ guest_user_id: string;
+ /**
+ * ID of the regular user that claims the guest and receives its channels, messages and reactions
+ */
+ user_id: string;
+}
+
+export interface ClaimGuestUserResponse {
+ /**
+ * Duration of the request in milliseconds
+ */
+ duration: string;
+ /**
+ * ID of the task that moves the guest's channels, messages and reactions to the claiming user
+ */
+ task_id: string;
+}
+
export interface Classification {
name: string;
confidence?: number;
@@ -6118,7 +6574,7 @@ export interface ClientOSDataResponse {
export interface ClosedCaptionEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* CallClosedCaption represents a closed caption of a call.
*/
@@ -6184,11 +6640,11 @@ export interface CollectionResponse {
/**
* When the collection was created
*/
- created_at?: Date;
+ created_at?: TimestampNS;
/**
* When the collection was last updated
*/
- updated_at?: Date;
+ updated_at?: TimestampNS;
/**
* ID of the user who owns this collection
*/
@@ -6199,6 +6655,8 @@ export interface CollectionResponse {
custom?: Record;
}
+export type CollectionStatus = 'ok' | 'notfound';
+
export interface Command {
/**
* Arguments help text, shown in commands auto-completion
@@ -6219,18 +6677,18 @@ export interface Command {
/**
* Date/time of creation
*/
- created_at?: Date;
+ created_at?: TimestampNS;
/**
* Date/time of the last update
*/
- updated_at?: Date;
+ updated_at?: TimestampNS;
}
export interface CommentAddedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
comment: CommentResponse;
@@ -6239,8 +6697,11 @@ export interface CommentAddedEvent {
* The type of event: "feeds.comment.added" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -6248,7 +6709,7 @@ export interface CommentDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
comment: CommentResponse;
custom: Record;
@@ -6256,8 +6717,11 @@ export interface CommentDeletedEvent {
* The type of event: "feeds.comment.deleted" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -6265,14 +6729,14 @@ export interface CommentFeedbackEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
comment_feedback: CommentFeedbackEventPayload;
custom: Record;
/**
* The type of event: "feeds.comment.feedback" in this case
*/
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -6284,7 +6748,7 @@ export interface CommentFeedbackEventPayload {
/**
* When the feedback was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Whether the comment is hidden for the user
*/
@@ -6292,7 +6756,7 @@ export interface CommentFeedbackEventPayload {
/**
* When the feedback was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* User response object
*/
@@ -6327,7 +6791,7 @@ export interface CommentReactionAddedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
comment: CommentResponse;
@@ -6337,8 +6801,11 @@ export interface CommentReactionAddedEvent {
* The type of event: "feeds.comment.reaction.added" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -6346,7 +6813,7 @@ export interface CommentReactionDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
comment: CommentResponse;
custom: Record;
@@ -6355,15 +6822,18 @@ export interface CommentReactionDeletedEvent {
* The type of reaction that was removed
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
}
export interface CommentReactionUpdatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
activity: ActivityResponse;
comment: CommentResponse;
@@ -6373,8 +6843,11 @@ export interface CommentReactionUpdatedEvent {
* The type of event: "feeds.comment.reaction.updated" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -6387,7 +6860,7 @@ export interface CommentResponse {
/**
* When the comment was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Number of downvotes for this comment
*/
@@ -6423,11 +6896,11 @@ export interface CommentResponse {
/**
* Status of the comment. One of: active, deleted, removed, hidden
*/
- status: 'active' | 'deleted' | 'removed' | 'hidden' | 'shadow_blocked';
+ status: CommentStatus;
/**
* When the comment was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* Number of upvotes for this comment
*/
@@ -6451,11 +6924,11 @@ export interface CommentResponse {
/**
* When the comment was deleted
*/
- deleted_at?: Date;
+ deleted_at?: TimestampNS;
/**
* When the comment was last edited
*/
- edited_at?: Date;
+ edited_at?: TimestampNS;
/**
* ID of parent comment for nested replies
*/
@@ -6488,7 +6961,7 @@ export interface CommentRestoredEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
comment: CommentResponse;
custom: Record;
@@ -6496,16 +6969,22 @@ export interface CommentRestoredEvent {
* The type of event: "feeds.comment.restored" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
+export type CommentStatus =
+ 'active' | 'deleted' | 'removed' | 'hidden' | 'shadow_blocked';
+
export interface CommentUpdatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
comment: CommentResponse;
custom: Record;
@@ -6513,8 +6992,11 @@ export interface CommentUpdatedEvent {
* The type of event: "feeds.comment.updated" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
@@ -6538,7 +7020,7 @@ export interface ConfigOverridesRequest {
/**
* Blocklist behavior. One of: flag, block
*/
- blocklist_behavior?: 'flag' | 'block';
+ blocklist_behavior?: BlocklistBehavior;
/**
* Enable/disable message counting
*/
@@ -6550,7 +7032,7 @@ export interface ConfigOverridesRequest {
/**
* Overrides the push notification level for this channel
*/
- push_level?: 'all' | 'all_mentions' | 'mentions' | 'direct_mentions' | 'none';
+ push_level?: PushLevel;
/**
* Enable/disable quotes
*/
@@ -6602,7 +7084,7 @@ export interface ConfigResponse {
/**
* When the configuration was created
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* Unique identifier for the moderation configuration
*/
@@ -6614,7 +7096,7 @@ export interface ConfigResponse {
/**
* When the configuration was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
supported_video_call_harm_types: Array;
/**
* Configurable image moderation label definitions for dashboard rendering
@@ -6678,7 +7160,7 @@ export interface CoordinatesResponse {
export interface CountByMinuteResponse {
count: number;
- start_ts: Date;
+ start_ts: TimestampNS;
}
export interface Coverage {
@@ -6748,7 +7230,7 @@ export interface CreateCallTypeResponse {
/**
* the time the call type was created
*/
- created_at: Date;
+ created_at: TimestampNS;
duration: string;
/**
* the name of the call type
@@ -6757,7 +7239,7 @@ export interface CreateCallTypeResponse {
/**
* the time the call type was last updated
*/
- updated_at: Date;
+ updated_at: TimestampNS;
/**
* the permissions granted to each role
*/
@@ -6792,11 +7274,11 @@ export interface CreateCampaignRequest {
/**
* The sender mode of the campaign
*/
- sender_mode?: 'exclude' | 'include';
+ sender_mode?: CampaignSenderMode;
/**
* The visibility of the created channels for the sender
*/
- sender_visibility?: 'hidden' | 'archived';
+ sender_visibility?: CampaignSenderVisibility;
/**
* Whether the campaign should show channels, if they are hidden
*/
@@ -6833,11 +7315,11 @@ export interface CreateChannelTypeRequest {
/**
* Automod. One of: disabled, simple, AI
*/
- automod: 'disabled' | 'simple' | 'AI';
+ automod: AutomodMode;
/**
* Automod behavior. One of: flag, block
*/
- automod_behavior: 'flag' | 'block';
+ automod_behavior: BlocklistBehavior;
/**
* Max message length
*/
@@ -6853,7 +7335,7 @@ export interface CreateChannelTypeRequest {
/**
* Blocklist behavior. One of: flag, block, shadow_block
*/
- blocklist_behavior?: 'flag' | 'block' | 'shadow_block';
+ blocklist_behavior?: ChannelModerationBehavior;
/**
* Connect events
*/
@@ -6894,7 +7376,7 @@ export interface CreateChannelTypeRequest {
/**
* Default push notification level for the channel type. One of: all, all_mentions, mentions, direct_mentions, none
*/
- push_level?: 'all' | 'all_mentions' | 'mentions' | 'direct_mentions' | 'none';
+ push_level?: PushLevel;
/**
* Push notifications
*/
@@ -6965,11 +7447,17 @@ export interface CreateChannelTypeRequest {
}
export interface CreateChannelTypeResponse {
- automod: 'disabled' | 'simple' | 'AI';
- automod_behavior: 'flag' | 'block' | 'shadow_block';
+ /**
+ * Automod level applied to a channel type.
+ */
+ automod: AutomodMode;
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ automod_behavior: ChannelModerationBehavior;
connect_events: boolean;
count_messages: boolean;
- created_at: Date;
+ created_at: TimestampNS;
custom_events: boolean;
delivery_events: boolean;
/**
@@ -6992,7 +7480,7 @@ export interface CreateChannelTypeResponse {
shared_locations: boolean;
skip_last_msg_update_for_system_msgs: boolean;
typing_events: boolean;
- updated_at: Date;
+ updated_at: TimestampNS;
uploads: boolean;
url_enrichment: boolean;
user_message_reminders: boolean;
@@ -7000,13 +7488,16 @@ export interface CreateChannelTypeResponse {
permissions: Array;
grants: Record>;
blocklist?: string;
- blocklist_behavior?: 'flag' | 'block' | 'shadow_block';
+ /**
+ * What automod or a blocklist does when it matches.
+ */
+ blocklist_behavior?: ChannelModerationBehavior;
partition_size?: number;
partition_ttl?: string;
/**
* Sets the push notification level for a channel type
*/
- push_level?: 'all' | 'all_mentions' | 'mentions' | 'direct_mentions' | 'none';
+ push_level?: PushLevel;
allowed_flag_reasons?: Array;
blocklists?: Array;
/**
@@ -7071,7 +7562,7 @@ export interface CreateDeviceRequest {
/**
* Push provider
*/
- push_provider: 'firebase' | 'apn' | 'huawei' | 'xiaomi';
+ push_provider: PushProviderName;
/**
* Stable physical device identifier used to deduplicate pushes across push providers (e.g. APNs VoIP and Firebase on the same iOS device). Distinct from 'id', which is the push token.
*/
@@ -7141,8 +7632,7 @@ export interface CreateFeedGroupRequest {
/**
* Default visibility for the feed group, can be 'public', 'visible', 'followers', 'members', or 'private'. Defaults to 'visible' if not provided.
*/
- default_visibility?:
- 'public' | 'visible' | 'followers' | 'members' | 'private';
+ default_visibility?: FeedVisibility;
/**
* Configuration for activity processors
*/
@@ -7272,7 +7762,7 @@ export interface CreateImportV2TaskRequest {
export interface CreateImportV2TaskResponse {
app_pk: number;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Duration of the request in milliseconds
*/
@@ -7280,7 +7770,7 @@ export interface CreateImportV2TaskResponse {
id: string;
product: string;
state: number;
- updated_at: Date;
+ updated_at: TimestampNS;
settings: ImportV2TaskSettings;
}
@@ -7435,7 +7925,7 @@ export interface CreatePollRequest {
/**
* Represents the visibility of votes in a poll.
*/
- voting_visibility?: 'anonymous' | 'public';
+ voting_visibility?: VotingVisibility;
options?: Array;
/**
* Custom data for this object
@@ -7687,15 +8177,15 @@ export interface CustomCheckResponse {
}
export interface CustomEvent {
- created_at: Date;
+ created_at: TimestampNS;
custom: Record;
type: string;
- received_at?: Date;
+ received_at?: TimestampNS;
}
export interface CustomVideoEvent {
call_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Custom data for this object
*/
@@ -8129,6 +8619,24 @@ export interface DeleteFeedsRetentionPolicyResponse {
duration: string;
}
+export interface DeleteHistoryWindowTeamsRequest {
+ /**
+ * Teams to put back on the app's default history window, 1 to 10, each listed once. A team without full history is ignored
+ */
+ teams: Array;
+}
+
+export interface DeleteHistoryWindowTeamsResponse {
+ /**
+ * Duration of the request in milliseconds
+ */
+ duration: string;
+ /**
+ * The teams of the request that had full history and are now back on the default window
+ */
+ teams: Array;
+}
+
export interface DeleteImportV2TaskResponse {
/**
* Duration of the request in milliseconds
@@ -8166,6 +8674,8 @@ export interface DeleteMessageResponse {
message: MessageResponse;
}
+export type DeleteMessages = 'soft' | 'pruning' | 'hard';
+
export interface DeleteModerationConfigResponse {
duration: string;
}
@@ -8260,7 +8770,10 @@ export interface DeleteReminderResponse {
}
export interface DeleteRetentionPolicyRequest {
- policy: 'old-messages' | 'inactive-channels';
+ /**
+ * What a retention policy applies to.
+ */
+ policy: RetentionPolicyType;
}
export interface DeleteRetentionPolicyResponse {
@@ -8315,7 +8828,7 @@ export interface DeleteUserMessagesRequestPayload {
/**
* Message deletion mode: soft, pruning, or hard
*/
- delete_messages: 'soft' | 'pruning' | 'hard';
+ delete_messages: DeleteMessages;
/**
* Optional: scope deletion to a single channel (alternative to app-wide deletion)
*/
@@ -8447,6 +8960,17 @@ export interface DeliveryZoneSegment {
watch_share_pct?: number;
}
+export interface DetectedURLResponse {
+ /**
+ * The URL as found.
+ */
+ url: string;
+ /**
+ * Where it was found: `qr` (decoded from a QR code in an image), `ocr` (in text visible in an image) or `text` (in a moderated text).
+ */
+ source?: string;
+}
+
export interface DeviceDataResponse {
name?: string;
version?: string;
@@ -8462,7 +8986,7 @@ export interface DeviceResponse {
/**
* The date when the device was created.
*/
- created_at: Date;
+ created_at: TimestampNS;
/**
* The device identifier.
*/
@@ -8470,7 +8994,7 @@ export interface DeviceResponse {
/**
* Push provider
*/
- push_provider: string;
+ push_provider: PushProviderName;
/**
* User ID
*/
@@ -8548,7 +9072,7 @@ export interface DraftPayloadResponse {
export interface DraftResponse {
channel_cid: string;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Contains the draft message content
*/
@@ -8603,7 +9127,7 @@ export interface EgressHLSResponse {
export interface EgressRTMPResponse {
name: string;
- started_at: Date;
+ started_at: TimestampNS;
stream_key?: string;
stream_url?: string;
}
@@ -8640,14 +9164,14 @@ export interface EncryptionSettingsRequest {
/**
* Encryption mode. One of: available, disabled, auto-on
*/
- mode?: 'available' | 'disabled' | 'auto-on';
+ mode?: FeatureActivationMode;
}
export interface EncryptionSettingsResponse {
/**
* the resolved encryption mode for the call
*/
- mode: 'available' | 'disabled' | 'auto-on';
+ mode: FeatureActivationMode;
}
export interface EndCallResponse {
@@ -8685,15 +9209,15 @@ export interface EnrichedCollectionResponse {
/**
* Enrichment status of the collection. One of: ok, notfound
*/
- status: 'ok' | 'notfound';
+ status: CollectionStatus;
/**
* When the collection was created
*/
- created_at?: Date;
+ created_at?: TimestampNS;
/**
* When the collection was last updated
*/
- updated_at?: Date;
+ updated_at?: TimestampNS;
/**
* ID of the user who owns this collection
*/
@@ -8813,7 +9337,7 @@ export interface EntityCreatorResponse {
*/
ban_count: number;
banned: boolean;
- created_at: Date;
+ created_at: TimestampNS;
/**
* Number of major actions performed on the user
*/
@@ -8828,19 +9352,19 @@ export interface EntityCreatorResponse {
online: boolean;
role: string;
shadow_banned: boolean;
- updated_at: Date;
+ updated_at: TimestampNS;
blocked_user_ids: Array;
teams: Array;
custom: Record;
avg_response_time?: number;
- ban_expires?: Date;
+ ban_expires?: TimestampNS;
bypass_moderation?: boolean;
- deactivated_at?: Date;
- deleted_at?: Date;
+ deactivated_at?: TimestampNS;
+ deleted_at?: TimestampNS;
image?: string;
- last_active?: Date;
+ last_active?: TimestampNS;
name?: string;
- revoke_tokens_issued_before?: Date;
+ revoke_tokens_issued_before?: TimestampNS;
devices?: Array;
/**
* The privacy settings of the user.
@@ -9002,6 +9526,29 @@ export interface ExportFeedUserDataResponse {
task_id: string;
}
+export interface ExportFeedsByTypeRequest {
+ /**
+ * Only export rows created at or after this time. On reactions and follows it requires user_id; on comments soft-deleted rows are left out
+ */
+ created_at_from?: Date;
+ /**
+ * Only export rows created before this time. On reactions and follows it requires user_id; on comments soft-deleted rows are left out
+ */
+ created_at_to?: Date;
+ /**
+ * Only export rows of this user: activities, comments and reactions by the user; follows where the user created the source or the target feed. With user_id, soft-deleted activities and comments are left out
+ */
+ user_id?: string;
+}
+
+export interface ExportFeedsResponse {
+ duration: string;
+ /**
+ * The task ID for the export task
+ */
+ task_id: string;
+}
+
export interface ExportUserResponse {
/**
* Duration of the request in milliseconds
@@ -9053,11 +9600,13 @@ export interface FailedChannelUpdates {
cids: Array;
}
+export type FeatureActivationMode = 'available' | 'disabled' | 'auto-on';
+
export interface FeedCreatedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
members: Array;
custom: Record;
@@ -9067,39 +9616,48 @@ export interface FeedCreatedEvent {
* The type of event: "feeds.feed.created" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
}
export interface FeedDeletedEvent {
/**
* Date/time of creation
*/
- created_at: Date;
+ created_at: TimestampNS;
fid: string;
custom: Record;
/**
* The type of event: "feeds.feed.deleted" in this case
*/
type: string;
- feed_visibility?: string;
- received_at?: Date;
+ /**
+ * Who can see and follow a feed.
+ */
+ feed_visibility?: FeedVisibility;
+ received_at?: TimestampNS;
user?: UserResponseCommonFields;
}
export interface FeedGroup {
aggregation_version: number;
app_pk: number;
- created_at: Date;
+ created_at: TimestampNS;
default_follower_role: string;
- default_visibility: string;
+ /**
+ * Who can see and follow a feed.
+ */
+ default_visibility: FeedVisibility;
group_id: string;
- updated_at: Date;
+ updated_at: TimestampNS;
activity_processors: Array