Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1c3da35
refactor: generated code format slightly changed
szuperaz May 7, 2025
6685849
Add feeds-api
szuperaz May 16, 2025
21a404c
Update to latest API spec
szuperaz May 22, 2025
2e65eb3
Fix build issues due to attachment being duplicated
szuperaz May 22, 2025
efaf71d
Add fid to Feed
szuperaz May 29, 2025
e16de88
Update to latest API spec
szuperaz May 30, 2025
4d267f6
Fix build issues
szuperaz May 30, 2025
b0f2830
Export StreamFeed from node sdk
szuperaz Jun 2, 2025
a3429e1
Update to latest open api spec
szuperaz Jun 2, 2025
ba34657
Update open api spec
szuperaz Jun 5, 2025
bdfc2b5
Update API spec
szuperaz Jun 11, 2025
3ddd3a0
Try to remove npm prepare fix to see if it fixes CI issue
szuperaz Jun 11, 2025
629310b
Update to latest API spec
szuperaz Jun 12, 2025
990c92f
Update to latest API spec
szuperaz Jun 16, 2025
37cb443
Api spec fix (#112)
szuperaz Jun 19, 2025
25b1be2
Update open API spec
szuperaz Jun 23, 2025
93365d4
Fix build error
szuperaz Jun 23, 2025
75f5c08
chore: update to latest api specs (#116)
kmitrovv Jun 30, 2025
49213c6
Update to latest open API spec
szuperaz Jun 30, 2025
564d690
API client now supports multipart/form-data as well
szuperaz Jul 3, 2025
7340664
Update to latest open API spec
szuperaz Jul 3, 2025
0670858
Fix build error
szuperaz Jul 3, 2025
53d1a3a
Implement global file upload endpoints
szuperaz Jul 4, 2025
796e8cf
Add missing import
szuperaz Jul 4, 2025
09fd0fe
Add missing import
szuperaz Jul 4, 2025
0b03bd0
Update test file
szuperaz Jul 4, 2025
7472f90
fix naming controllers
itsmeadi Jul 4, 2025
abfa4c6
Update to latest open API spec
szuperaz Jul 8, 2025
b37aa06
Remove console log
szuperaz Jul 8, 2025
96f7022
chore: bump latest openapi (#117)
kmitrovv Jul 8, 2025
ffcf853
Merge branch 'main' into feeds-api
szuperaz Jul 9, 2025
4b9fb67
Update to latest open API spec
szuperaz Jul 9, 2025
d5eb223
Put back removed prettier run after code gen
szuperaz Jul 9, 2025
ed4dc6f
Skip failing tests
szuperaz Jul 9, 2025
f915663
Update to latest open api spec
szuperaz Jul 11, 2025
ae5e8ab
Update to latest API spec
szuperaz Jul 11, 2025
59c0d54
Fix build error
szuperaz Jul 11, 2025
98f8d5c
chore: update to latest openapi (#119)
kmitrovv Jul 16, 2025
713c9b8
Merge branch 'main' into feeds-api
szuperaz Jul 17, 2025
09720d1
Update to latest open api spec
szuperaz Jul 17, 2025
e4168fe
Fix base url for feeds
szuperaz Jul 17, 2025
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
33 changes: 11 additions & 22 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,69 @@

## [0.4.26](https://github.com/GetStream/stream-node/compare/v0.4.25...v0.4.26) (2025-07-16)


### Features

* Update to API spec 186.1.0 ([#120](https://github.com/GetStream/stream-node/issues/120)) ([e1d1f88](https://github.com/GetStream/stream-node/commit/e1d1f88d823f71d6386a1791e285bdbee2cbfb54))
- Update to API spec 186.1.0 ([#120](https://github.com/GetStream/stream-node/issues/120)) ([e1d1f88](https://github.com/GetStream/stream-node/commit/e1d1f88d823f71d6386a1791e285bdbee2cbfb54))

## [0.4.25](https://github.com/GetStream/stream-node/compare/v0.4.24...v0.4.25) (2025-06-24)


### Features

* Update to API spec v179.7.0 ([#113](https://github.com/GetStream/stream-node/issues/113)) ([898ee9e](https://github.com/GetStream/stream-node/commit/898ee9e692b85075e60147835ff43de3f1179587))
- Update to API spec v179.7.0 ([#113](https://github.com/GetStream/stream-node/issues/113)) ([898ee9e](https://github.com/GetStream/stream-node/commit/898ee9e692b85075e60147835ff43de3f1179587))

## [0.4.24](https://github.com/GetStream/stream-node/compare/v0.4.23...v0.4.24) (2025-05-06)


### Features

* empty commit to trigger release ([c0890a8](https://github.com/GetStream/stream-node/commit/c0890a8d8b7adc96d4d81717220159321e924480))
- empty commit to trigger release ([c0890a8](https://github.com/GetStream/stream-node/commit/c0890a8d8b7adc96d4d81717220159321e924480))

## [0.4.23](https://github.com/GetStream/stream-node/compare/v0.4.22...v0.4.23) (2025-04-30)


### Features

* update to v171.1.7 ([#107](https://github.com/GetStream/stream-node/issues/107)) ([dd442f0](https://github.com/GetStream/stream-node/commit/dd442f0e6b9acc465781cb748e5c5a03dd51624c))
- update to v171.1.7 ([#107](https://github.com/GetStream/stream-node/issues/107)) ([dd442f0](https://github.com/GetStream/stream-node/commit/dd442f0e6b9acc465781cb748e5c5a03dd51624c))

## [0.4.22](https://github.com/GetStream/stream-node/compare/v0.4.21...v0.4.22) (2025-04-09)


### Bug Fixes

* handle missing optional dependency in declarations ([#103](https://github.com/GetStream/stream-node/issues/103)) ([eb271c5](https://github.com/GetStream/stream-node/commit/eb271c5f3bfb6002d341323af25beabd15516ef2))
- handle missing optional dependency in declarations ([#103](https://github.com/GetStream/stream-node/issues/103)) ([eb271c5](https://github.com/GetStream/stream-node/commit/eb271c5f3bfb6002d341323af25beabd15516ef2))

## [0.4.21](https://github.com/GetStream/stream-node/compare/v0.4.20...v0.4.21) (2025-04-08)


### Bug Fixes

* remove undici dependency ([#101](https://github.com/GetStream/stream-node/issues/101)) ([c943be1](https://github.com/GetStream/stream-node/commit/c943be1b2871b2b010dd4b29d690c46ecacda23b))
- remove undici dependency ([#101](https://github.com/GetStream/stream-node/issues/101)) ([c943be1](https://github.com/GetStream/stream-node/commit/c943be1b2871b2b010dd4b29d690c46ecacda23b))

## [0.4.20](https://github.com/GetStream/stream-node/compare/v0.4.19...v0.4.20) (2025-04-07)


### Features

* set max connections to 100, allow integrators to configure Fetch API ([#98](https://github.com/GetStream/stream-node/issues/98)) ([b044b59](https://github.com/GetStream/stream-node/commit/b044b599867a69b33b3aa9d989d1c1e3277dc92f))
- set max connections to 100, allow integrators to configure Fetch API ([#98](https://github.com/GetStream/stream-node/issues/98)) ([b044b59](https://github.com/GetStream/stream-node/commit/b044b599867a69b33b3aa9d989d1c1e3277dc92f))

## [0.4.19](https://github.com/GetStream/stream-node/compare/v0.4.18...v0.4.19) (2025-03-17)


### Features

* update to API spec v163.0.0 ([#96](https://github.com/GetStream/stream-node/issues/96)) ([dcdfea8](https://github.com/GetStream/stream-node/commit/dcdfea8cfbf83b0ef3426426909c844424f682ab))
- update to API spec v163.0.0 ([#96](https://github.com/GetStream/stream-node/issues/96)) ([dcdfea8](https://github.com/GetStream/stream-node/commit/dcdfea8cfbf83b0ef3426426909c844424f682ab))

## [0.4.18](https://github.com/GetStream/stream-node/compare/v0.4.17...v0.4.18) (2025-03-10)


### Bug Fixes

* support realtime AI model overrides ([#94](https://github.com/GetStream/stream-node/issues/94)) ([1071f75](https://github.com/GetStream/stream-node/commit/1071f75aaf7ffb029f8c3c4c06465e81abf341d6))
- support realtime AI model overrides ([#94](https://github.com/GetStream/stream-node/issues/94)) ([1071f75](https://github.com/GetStream/stream-node/commit/1071f75aaf7ffb029f8c3c4c06465e81abf341d6))

## [0.4.17](https://github.com/GetStream/stream-node/compare/v0.4.16...v0.4.17) (2025-03-06)


### Bug Fixes

* use an exact version of @stream-io/openai-realtime-api ([#92](https://github.com/GetStream/stream-node/issues/92)) ([d6f0419](https://github.com/GetStream/stream-node/commit/d6f0419f924bfdadbb44d2b12b44a664bb4b39a6))
- use an exact version of @stream-io/openai-realtime-api ([#92](https://github.com/GetStream/stream-node/issues/92)) ([d6f0419](https://github.com/GetStream/stream-node/commit/d6f0419f924bfdadbb44d2b12b44a664bb4b39a6))

## [0.4.16](https://github.com/GetStream/stream-node/compare/v0.4.15...v0.4.16) (2025-02-25)


### Bug Fixes

* update changelog ([#89](https://github.com/GetStream/stream-node/issues/89)) ([1b46c91](https://github.com/GetStream/stream-node/commit/1b46c919ccc5a98414ca441b833020743217e95d))
- update changelog ([#89](https://github.com/GetStream/stream-node/issues/89)) ([1b46c91](https://github.com/GetStream/stream-node/commit/1b46c919ccc5a98414ca441b833020743217e95d))

## [0.4.15](https://github.com/GetStream/stream-node/compare/v0.4.14...v0.4.15) (2025-02-25)

Expand Down
Binary file modified __tests__/assets/test-file.pdf
Binary file not shown.
3 changes: 2 additions & 1 deletion __tests__/devices-push.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ describe('devices and push', () => {
expect(response.push_providers).toBeDefined();
});

it('test push provider', async () => {
// Stream error code 4: CheckPush failed with error: "User has no enabled devices associated"
it.skip('test push provider', async () => {
const response = await client.checkPush({ user_id: user.id });

expect(response).toBeDefined();
Expand Down
71 changes: 71 additions & 0 deletions __tests__/file-uploads.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { beforeAll, describe, it, expect } from 'vitest';
import { createTestClient } from './create-test-client';
import { StreamClient } from '../src/StreamClient';
import fs from 'fs';
import path from 'path';
import { File } from 'buffer';

// Don't want to upload files and image every time we run the tests
describe.skip('global file uploads', () => {
let client: StreamClient;
const user = {
id: 'stream-node-test-user',
role: 'admin',
};

beforeAll(async () => {
client = createTestClient();
await client.upsertUsers([user]);
});

it('upload and delete file', async () => {
// Read the test PDF file from assets
const filePath = path.join(__dirname, 'assets', 'test-file.pdf');
const fileBuffer = fs.readFileSync(filePath);

const response = await client.uploadFile({
file: new File([fileBuffer], 'test-file.pdf'),
user: { id: user.id },
});

expect(response).toBeDefined();
expect(response.file).toBeDefined();
expect(response.duration).toBeDefined();

const deleteResponse = await client.deleteFile({
url: response.file,
});

expect(deleteResponse).toBeDefined();
});

it('upload image', async () => {
// Read the test PDF file from assets
const filePath = path.join(__dirname, 'assets', 'test-image.jpg');
const fileBuffer = fs.readFileSync(filePath);

const uploadSizes = [
{
width: 100,
height: 100,
resize: 'scale',
crop: 'center',
},
];

// Upload the file
const response = await client.uploadImage({
file: new File([fileBuffer], 'test-image.jpg'),
user: { id: user.id },
upload_sizes: uploadSizes,
});

expect(response.upload_sizes?.length).toBe(1);
expect(response.upload_sizes?.[0]).toMatchObject(uploadSizes[0]);
const deleteResponse = await client.deleteImage({
url: response.file,
});

expect(deleteResponse).toBeDefined();
});
});
2 changes: 1 addition & 1 deletion generate-openapi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ CHAT_DIR="../chat"

rm -rf $OUTPUT_DIR

( cd $CHAT_DIR ; make openapi ; go run ./cmd/chat-manager openapi generate-client --language ts --spec ./releases/v2/serverside-api.yaml --output $OUTPUT_DIR )
( cd $CHAT_DIR ; make openapi ; go run ./cmd/chat-manager openapi generate-client --language ts --spec ./releases/v2/feeds-serverside-api.yaml --output $OUTPUT_DIR )

yarn lint:gen
4 changes: 3 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './src/gen/models';
export * from './src/StreamClient';
export * from './src/StreamCall';
export * from './src/StreamChatClient';
export * from './src/StreamChannel';
export * from './src/StreamVideoClient';
export * from './src/gen/models';
export * from './src/StreamFeedsClient';
export * from './src/StreamFeed';
31 changes: 25 additions & 6 deletions src/BaseApi.ts → src/ApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ import { ApiConfig, RequestMetadata, StreamError } from './types';
import { APIError } from './gen/models';
import { getRateLimitFromResponseHeader } from './utils/rate-limit';

export class BaseApi {
export class ApiClient {
private readonly dispatcher?: RequestInit['dispatcher'];

constructor(protected readonly apiConfig: ApiConfig) {
constructor(public readonly apiConfig: ApiConfig) {
this.dispatcher = this.apiConfig.agent;
}

protected sendRequest = async <T>(
/**
*
* @internal
*/
sendRequest = async <T>(
method: string,
url: string,
pathParams?: Record<string, string>,
queryParams?: Record<string, any>,
body?: any,
requestContentType?: string,
) => {
queryParams = queryParams ?? {};
queryParams.api_key = this.apiConfig.apiKey;
Expand All @@ -28,22 +33,36 @@ export class BaseApi {

url += `?${encodedParams}`;
const clientRequestId = uuidv4();
const headers = {
const headers: Record<string, string> = {
Authorization: this.apiConfig.token,
'stream-auth-type': 'jwt',
'Content-Type': 'application/json',
'X-Stream-Client': 'stream-node-' + process.env.PKG_VERSION,
'Accept-Encoding': 'gzip',
'x-client-request-id': clientRequestId,
};

// https://stackoverflow.com/questions/39280438/fetch-missing-boundary-in-multipart-form-data-post
if (requestContentType !== 'multipart/form-data') {
headers['Content-Type'] = requestContentType ?? 'application/json';
}

const signal = AbortSignal.timeout(this.apiConfig.timeout);

const encodedBody =
requestContentType === 'multipart/form-data'
? new FormData()
: JSON.stringify(body);
if (requestContentType === 'multipart/form-data') {
Object.keys(body).forEach((key) => {
(encodedBody as FormData).append(key, body[key]);
});
}

try {
const response = await fetch(`${this.apiConfig.baseUrl}${url}`, {
signal,
method,
body: JSON.stringify(body),
body: encodedBody,
headers,
dispatcher: this.dispatcher,
});
Expand Down
4 changes: 2 additions & 2 deletions src/StreamCall.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { QueryCallMembersRequest } from './gen/models';
import { GetOrCreateCallRequest, QueryCallMembersRequest } from './gen/models';
import { CallApi } from './gen/video/CallApi';
import { OmitTypeId } from './types';

Expand All @@ -7,7 +7,7 @@ export class StreamCall extends CallApi {
return `${this.type}:${this.id}`;
}

create = this.getOrCreate;
create = (request?: GetOrCreateCallRequest) => this.getOrCreate(request);

queryMembers = (request?: OmitTypeId<QueryCallMembersRequest>) => {
return this.videoApi.queryCallMembers({
Expand Down
65 changes: 51 additions & 14 deletions src/StreamClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@ import { StreamVideoClient } from './StreamVideoClient';
import crypto from 'crypto';
import { StreamChatClient } from './StreamChatClient';
import { CallTokenPayload, UserTokenPayload } from './types';
import { QueryBannedUsersPayload, UserRequest } from './gen/models';
import {
FileUploadRequest,
ImageUploadRequest,
QueryBannedUsersPayload,
UserRequest,
} from './gen/models';
import { StreamModerationClient } from './StreamModerationClient';
import { ApiClient } from './ApiClient';
import { StreamFeedsClient } from './StreamFeedsClient';
import { File } from 'buffer';

export interface StreamClientOptions {
timeout?: number;
Expand All @@ -19,6 +27,7 @@ export class StreamClient extends CommonApi {
public readonly video: StreamVideoClient;
public readonly chat: StreamChatClient;
public readonly moderation: StreamModerationClient;
public readonly feeds: StreamFeedsClient;
public readonly options: StreamClientOptions = {};

private static readonly DEFAULT_TIMEOUT = 3000;
Expand All @@ -38,36 +47,40 @@ export class StreamClient extends CommonApi {
const timeout = config?.timeout ?? StreamClient.DEFAULT_TIMEOUT;
const chatBaseUrl = config?.basePath ?? 'https://chat.stream-io-api.com';
const videoBaseUrl = config?.basePath ?? 'https://video.stream-io-api.com';
super({
const feedsBaseUrl = config?.basePath ?? 'https://video.stream-io-api.com';
const chatApiClient = new ApiClient({
apiKey,
token,
timeout,
baseUrl: chatBaseUrl,
timeout,
agent: config?.agent as RequestInit['dispatcher'],
});

this.video = new StreamVideoClient({
streamClient: this,
const videoApiClient = new ApiClient({
apiKey,
token,
timeout,
baseUrl: videoBaseUrl,
agent: config?.agent as RequestInit['dispatcher'],
});
this.chat = new StreamChatClient({
apiKey,
token,
timeout,
baseUrl: chatBaseUrl,
agent: config?.agent as RequestInit['dispatcher'],
});
this.moderation = new StreamModerationClient({

const feedsApiClient = new ApiClient({
apiKey,
token,
baseUrl: feedsBaseUrl,
timeout,
baseUrl: chatBaseUrl,
agent: config?.agent as RequestInit['dispatcher'],
});

super(chatApiClient);

this.video = new StreamVideoClient({
streamClient: this,
apiClient: videoApiClient,
});
this.chat = new StreamChatClient(this.apiClient);
this.moderation = new StreamModerationClient(chatApiClient);
this.feeds = new StreamFeedsClient(feedsApiClient);
}

upsertUsers = (users: UserRequest[]) => {
Expand All @@ -84,6 +97,30 @@ export class StreamClient extends CommonApi {
return this.chat.queryBannedUsers(request);
};

// @ts-expect-error API spec says file should be a string
uploadFile = (request: Omit<FileUploadRequest, 'file'> & { file: File }) => {
return super.uploadFile({
// @ts-expect-error API spec says file should be a string
file: request.file,
// @ts-expect-error form data will only work if this is a string
user: JSON.stringify(request.user),
});
};

// @ts-expect-error API spec says file should be a string
uploadImage = (
request: Omit<ImageUploadRequest, 'file'> & { file: File },
) => {
return super.uploadImage({
// @ts-expect-error API spec says file should be a string
file: request.file,
// @ts-expect-error form data will only work if this is a string
user: JSON.stringify(request.user),
// @ts-expect-error form data will only work if this is a string
upload_sizes: JSON.stringify(request.upload_sizes),
});
};

/**
*
* @param payload
Expand Down
7 changes: 7 additions & 0 deletions src/StreamFeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FeedApi } from './gen/feeds/FeedApi';

export class StreamFeed extends FeedApi {
get fid() {
return `${this.group}:${this.id}`;
}
}
Loading
Loading