Skip to content

Commit

Permalink
Merge pull request #16 from AssemblyAI/E07417BDFEA3614F5967B1520F8B2F61
Browse files Browse the repository at this point in the history
Release 3.0.1 👻
  • Loading branch information
Swimburger authored Oct 30, 2023
2 parents 387b1ac + c2c866e commit 5e244bc
Show file tree
Hide file tree
Showing 22 changed files with 444 additions and 403 deletions.
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Changelog

## [3.0.1] - 2023-10-30
### Changed
- The SDK uses `fetch` instead of Axios. This removes the Axios dependency. Axios relies on XMLHttpRequest which isn't supported in Cloudflare Workers, Deno, Bun, etc. By using `fetch`, the SDK is now more compatible on the forementioned runtimes.

### Fixed
- The SDK uses relative imports instead of using path aliases, to make the library transpilable with tsc for consumers. Fixes [#14](https://github.com/AssemblyAI/assemblyai-node-sdk/issues/14).
- Added `speaker` property to the `TranscriptUtterance` type, and removed `channel` property.

## [3.0.0] - 2023-10-24
### Changed
- `AssemblyAI.files.upload` accepts streams and buffers, in addition to a string (path to file).

### Removed
- **Breaking**: The module does not have a default export anymore, because of inconsistent functionality across module systems. Instead, use `AssemblyAI` as a named import like this: `import { AssemblyAI } from 'assemblyai'`.

## [2.0.2] - 2023-10-13

### Added
- `AssemblyAI.transcripts.wordSearch` searches for keywords in the transcript.
- `AssemblyAI.lemur.purgeRequestData` deletes data related to your LeMUR request.
- `RealtimeService.stream` creates a writable stream that you can write audio data to instead of using `RealtimeService.sendAudio``.

### Fixed
- The AssemblyAI class would be exported as default named export instead in certain module systems.

## [2.0.1] - 2023-10-10

Re-implement the Node SDK in TypeScript and add all AssemblyAI APIs.

### Added
- Transcript API client
- LeMUR API client
- Real-time transcript client
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "assemblyai",
"version": "3.0.0",
"version": "3.0.1",
"description": "The AssemblyAI Node.js SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.",
"main": "dist/index.js",
"module": "dist/index.esm.js",
Expand Down Expand Up @@ -51,6 +51,7 @@
"i": "^0.3.7",
"jest": "^29.5.0",
"jest-cli": "^29.5.0",
"jest-fetch-mock": "^3.0.3",
"jest-junit": "^16.0.0",
"jest-mock-extended": "^3.0.4",
"jest-websocket-mock": "^2.4.1",
Expand All @@ -67,7 +68,6 @@
"typescript": "^5.2.2"
},
"dependencies": {
"axios": "^1.4.0",
"ws": "^8.13.0"
}
}
8 changes: 4 additions & 4 deletions scripts/kitchensink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,12 @@ const createTranscriptParams: CreateTranscriptParameters = {
})().then(deleteTranscript);

(async function listTranscripts() {
let nextPageUrl: string | null = null;
let nextPageUrl: string | undefined | null;
do {
const page = await client.transcripts.list(nextPageUrl)
const page = await client.transcripts.list(nextPageUrl as string | undefined)
console.log(page);
nextPageUrl = page.page_details.next_url;
} while (nextPageUrl !== null)
} while (!!nextPageUrl)
})();

async function searchTranscript(transcript: Transcript) {
Expand Down Expand Up @@ -355,6 +355,6 @@ async function lemurCustomTask(transcript: Transcript) {
}

async function purgeLemurRequestData(lemurResponse: LemurBaseResponse) {
const { response } = await client.lemur.purgeRequestData(lemurResponse.request_id);
const response = await client.lemur.purgeRequestData(lemurResponse.request_id);
console.log(response);
};
47 changes: 44 additions & 3 deletions src/services/base.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,53 @@
import { AxiosInstance } from "axios";
import { BaseServiceParams } from "..";
import { Error as JsonError } from "..";

/**
* Base class for services that communicate with the API.
*/
export abstract class BaseService {
/**
* Create a new service.
* @param params The AxiosInstance to send HTTP requests to the API.
* @param params The parameters to use for the service.
*/
constructor(protected client: AxiosInstance) {}
constructor(private params: BaseServiceParams) {}
protected async fetch(
input: string,
init?: RequestInit | undefined
): Promise<Response> {
init = init ?? {};
init.headers = init.headers ?? {};
init.headers = {
Authorization: this.params.apiKey,
"Content-Type": "application/json",
...init.headers,
};
if (!input.startsWith("http")) input = this.params.baseUrl + input;

const response = await fetch(input, init);

if (response.status >= 400) {
let json: JsonError | undefined;
const text = await response.text();
if (text) {
try {
json = JSON.parse(text);
} catch {
/* empty */
}
if (json?.error) throw new Error(json.error);
throw new Error(text);
}
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}

return response;
}

protected async fetchJson<T>(
input: string,
init?: RequestInit | undefined
): Promise<T> {
const response = await this.fetch(input, init);
return response.json() as Promise<T>;
}
}
22 changes: 10 additions & 12 deletions src/services/files/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// import the fs module instead if specific named exports
// to keep the assemblyai module more compatible. Some fs polyfills don't include `createReadStream`.
import fs from "fs";
import { BaseService } from "@/services/base";
import { UploadedFile, FileUploadParameters, FileUploadData } from "@/types";
import { BaseService } from "../base";
import { UploadedFile, FileUploadParameters, FileUploadData } from "../..";

export class FileService extends BaseService {
/**
Expand All @@ -15,16 +15,14 @@ export class FileService extends BaseService {
if (typeof input === "string") fileData = fs.createReadStream(input);
else fileData = input;

const { data } = await this.client.post<UploadedFile>(
"/v2/upload",
fileData,
{
headers: {
"Content-Type": "application/octet-stream",
},
}
);

const data = await this.fetchJson<UploadedFile>("/v2/upload", {
method: "POST",
body: fileData as BodyInit,
headers: {
"Content-Type": "application/octet-stream",
},
duplex: "half",
} as RequestInit);
return data.upload_url;
}
}
18 changes: 10 additions & 8 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createAxiosClient } from "@/utils/axios";
import { BaseServiceParams } from "@/types";
import { BaseServiceParams } from "..";
import { LemurService } from "./lemur";
import { RealtimeService, RealtimeServiceFactory } from "./realtime";
import { TranscriptService } from "./transcripts";
import { FileService } from "./files";

const defaultBaseUrl = "https://api.assemblyai.com";

class AssemblyAI {
/**
* The files service.
Expand All @@ -31,12 +32,13 @@ class AssemblyAI {
* @param params The parameters for the service, including the API key and base URL, if any.
*/
constructor(params: BaseServiceParams) {
params.baseUrl = params.baseUrl || "https://api.assemblyai.com";
const client = createAxiosClient(params);
this.files = new FileService(client);
this.transcripts = new TranscriptService(client, this.files);
this.lemur = new LemurService(client);
this.realtime = new RealtimeServiceFactory(client, params);
params.baseUrl = params.baseUrl || defaultBaseUrl;
if (params.baseUrl && params.baseUrl.endsWith("/"))
params.baseUrl = params.baseUrl.slice(0, -1);
this.files = new FileService(params);
this.transcripts = new TranscriptService(params, this.files);
this.lemur = new LemurService(params);
this.realtime = new RealtimeServiceFactory(params);
}
}

Expand Down
55 changes: 28 additions & 27 deletions src/services/lemur/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,55 @@ import {
LemurActionItemsResponse,
LemurTaskResponse,
PurgeLemurRequestDataResponse,
} from "@/types";
import { BaseService } from "@/services/base";
} from "../..";
import { BaseService } from "../base";

export class LemurService extends BaseService {
async summary(params: LemurSummaryParameters): Promise<LemurSummaryResponse> {
const { data } = await this.client.post<LemurSummaryResponse>(
"/lemur/v3/generate/summary",
params
);
return data;
summary(params: LemurSummaryParameters): Promise<LemurSummaryResponse> {
return this.fetchJson<LemurSummaryResponse>("/lemur/v3/generate/summary", {
method: "POST",
body: JSON.stringify(params),
});
}

async questionAnswer(
questionAnswer(
params: LemurQuestionAnswerParameters
): Promise<LemurQuestionAnswerResponse> {
const { data } = await this.client.post<LemurQuestionAnswerResponse>(
return this.fetchJson<LemurQuestionAnswerResponse>(
"/lemur/v3/generate/question-answer",
params
{
method: "POST",
body: JSON.stringify(params),
}
);
return data;
}

async actionItems(
actionItems(
params: LemurActionItemsParameters
): Promise<LemurActionItemsResponse> {
const { data } = await this.client.post<LemurActionItemsResponse>(
return this.fetchJson<LemurActionItemsResponse>(
"/lemur/v3/generate/action-items",
params
{
method: "POST",
body: JSON.stringify(params),
}
);
return data;
}

async task(params: LemurTaskParameters): Promise<LemurTaskResponse> {
const { data } = await this.client.post<LemurTaskResponse>(
"/lemur/v3/generate/task",
params
);
return data;
task(params: LemurTaskParameters): Promise<LemurTaskResponse> {
return this.fetchJson<LemurTaskResponse>("/lemur/v3/generate/task", {
method: "POST",
body: JSON.stringify(params),
});
}

/**
* Delete the data for a previously submitted LeMUR request.
* @param id ID of the LeMUR request
*/
async purgeRequestData(id: string): Promise<PurgeLemurRequestDataResponse> {
const { data } = await this.client.delete<PurgeLemurRequestDataResponse>(
`/lemur/v3/${id}`
);
return data;
purgeRequestData(id: string): Promise<PurgeLemurRequestDataResponse> {
return this.fetchJson<PurgeLemurRequestDataResponse>(`/lemur/v3/${id}`, {
method: "DELETE",
});
}
}
29 changes: 17 additions & 12 deletions src/services/realtime/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,35 @@ import {
RealtimeTokenParams,
CreateRealtimeServiceParams,
RealtimeServiceParams,
} from "@/types";
import { AxiosInstance } from "axios";
RealtimeTemporaryTokenResponse,
} from "../..";
import { RealtimeService } from "./service";
import { BaseService } from "../base";

export class RealtimeServiceFactory {
constructor(
private client: AxiosInstance,
private params: BaseServiceParams
) {}
export class RealtimeServiceFactory extends BaseService {
private rtFactoryParams: BaseServiceParams;
constructor(params: BaseServiceParams) {
super(params);
this.rtFactoryParams = params;
}

createService(params?: CreateRealtimeServiceParams): RealtimeService {
if (!params) params = { apiKey: this.params.apiKey };
if (!params) params = { apiKey: this.rtFactoryParams.apiKey };
else if (!("token" in params) && !params.apiKey) {
params.apiKey = this.params.apiKey;
params.apiKey = this.rtFactoryParams.apiKey;
}

return new RealtimeService(params as RealtimeServiceParams);
}

async createTemporaryToken(params: RealtimeTokenParams) {
const response = await this.client.post<{ token: string }>(
const data = await this.fetchJson<RealtimeTemporaryTokenResponse>(
"/v2/realtime/token",
params
{
method: "POST",
body: JSON.stringify(params),
}
);
return response.data.token;
return data.token;
}
}
4 changes: 2 additions & 2 deletions src/services/realtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "@/services/realtime/factory";
export * from "@/services/realtime/service";
export * from "./factory";
export * from "./service";
11 changes: 5 additions & 6 deletions src/services/realtime/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import {
PartialTranscript,
FinalTranscript,
SessionBeginsEventData,
} from "@/types";
} from "../..";
import {
RealtimeError,
RealtimeErrorMessages,
RealtimeErrorType,
} from "@/utils/errors";
import { Writable } from "stream";
} from "../../utils/errors";
import Stream from "stream";

const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";

Expand All @@ -32,7 +32,6 @@ export class RealtimeService {
this.realtimeUrl = params.realtimeUrl ?? defaultRealtimeUrl;
this.sampleRate = params.sampleRate ?? 16_000;
this.wordBoost = params.wordBoost;
this.realtimeUrl = params.realtimeUrl ?? defaultRealtimeUrl;
if ("apiKey" in params) this.apiKey = params.apiKey;
if ("token" in params) this.token = params.token;

Expand Down Expand Up @@ -162,8 +161,8 @@ export class RealtimeService {
this.socket.send(JSON.stringify(payload));
}

stream(): Writable {
const stream = new Writable({
stream(): NodeJS.WritableStream {
const stream = new Stream.Writable({
write: (chunk: Buffer, encoding, next) => {
this.sendAudio(chunk);
next();
Expand Down
Loading

0 comments on commit 5e244bc

Please sign in to comment.