Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
93fb57a
feat: update to API spec v238.16.2
szuperaz Sep 16, 2026
bf1d78d
feat!: turn off date transformers
szuperaz Sep 18, 2026
8775455
chore: enable type checks for test, fix tests
szuperaz Sep 18, 2026
84badb2
feat: add same helpers as stream-chat-js
szuperaz Sep 18, 2026
0fe110a
Merge branch 'main' into remove-date-transformers
oliverlaz Sep 23, 2026
7ec8b00
chore: use TimestampNS for date properties
oliverlaz Sep 23, 2026
1a09618
chore: regenerate models
oliverlaz Sep 24, 2026
e7b9d2e
Merge branch 'main' into remove-date-transformers
oliverlaz Oct 2, 2026
ef0dfd9
Merge branch 'main' into remove-date-transformers
oliverlaz Oct 5, 2026
dfbd048
chore: merge main (0.8.12) and regenerate the client from the current…
oliverlaz Oct 14, 2026
c153019
chore: add feeds, video and moderation snippets to runner
szuperaz Oct 19, 2026
7fe1373
feat: regenerate from latest api spec
szuperaz Oct 19, 2026
a91c3e5
Merge branch 'main' into remove-date-transformers
szuperaz Oct 19, 2026
e603a31
chore: update docs snippet runner after v9 snippets added back to docs
szuperaz Oct 16, 2026
35eceb9
chore: test docs snippet runner
szuperaz Oct 19, 2026
a68066f
Merge branch 'docs-snippets-feeds-video-moderation' into remove-date-…
szuperaz Oct 19, 2026
ebe7503
test: migrate feeds, video and moderation docs snippet tests to nanos…
szuperaz Oct 19, 2026
f27a213
Merge remote-tracking branch 'origin/main' into remove-date-transformers
szuperaz Oct 20, 2026
d491ccc
chore: add waitForFeedGroup helper
szuperaz Oct 20, 2026
25b6f44
chore: regenerate the client from the current chat spec
oliverlaz Oct 21, 2026
6ffd040
chore: merge main (0.8.14) and regenerate the client from the current…
oliverlaz Oct 27, 2026
4c7ff45
feat: update to latest API spec
szuperaz Oct 29, 2026
f0520a1
chore: docs sync
szuperaz Oct 29, 2026
e7cc754
chore: test fixes
szuperaz Oct 29, 2026
bc0b1e7
chore: add ts-expect-error to reported type issues
szuperaz Oct 29, 2026
d0d76cf
chore: rename stream-chat-js reference to stream-chat
szuperaz Oct 30, 2026
eb81ccd
chore: merge main (0.8.15) and regenerate the client from the current…
oliverlaz Nov 2, 2026
812575b
feat: update to latest open api spec
szuperaz Nov 4, 2026
e989034
chore: docs sync
szuperaz Nov 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 47 additions & 17 deletions .claude/skills/docs-sync/SKILL.md

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions __tests__/docs-snippets/AGENTS.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions __tests__/docs-snippets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<tabs-item>` 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 `<disclosure label="... (no SDK)">` 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 `<disclosure label="... (no SDK)">` block (reference
implementations without the SDK) are out of scope and are neither tested nor changed.

```sh
Expand Down Expand Up @@ -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=<page path> 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.
3 changes: 2 additions & 1 deletion __tests__/docs-snippets/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = () => {
Expand Down
12 changes: 12 additions & 0 deletions __tests__/docs-snippets/helpers/feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 30 additions & 7 deletions __tests__/docs-snippets/helpers/leaks.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<unknown>;
/**
* Removes many leftovers of this kind in one call (e.g. users in one `deleteUsers`).
Expand Down Expand Up @@ -112,7 +128,7 @@ const scanners: Record<string, Scanner> = {

async channels(client, { matches, ids: channelIds = [], createdSince }) {
const found = new Map<string, Leftover>();
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,
Expand Down Expand Up @@ -337,7 +353,9 @@ const scanners: Record<string, Scanner> = {
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,
}),
}));
Expand Down Expand Up @@ -402,12 +420,17 @@ const scanners: Record<string, Scanner> = {
// 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<string, unknown>) => {
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,
Expand Down
6 changes: 3 additions & 3 deletions __tests__/docs-snippets/maintenance/sweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,6 @@ describe(DOCS, () => {
});
return all.groups[feedGroupId];
});
expect(deleted?.deleted_at).toBeInstanceOf(Date);
expect(deleted?.deleted_at).toEqual(expect.any(Number));
});
});
Loading
Loading