├── .gitignore ├── .prettierrc.yml ├── src ├── api │ ├── client │ │ ├── index.ts │ │ └── requests │ │ │ ├── index.ts │ │ │ ├── ContextualizedEmbedRequest.ts │ │ │ ├── EmbedRequest.ts │ │ │ └── RerankRequest.ts │ ├── index.ts │ └── types │ │ ├── EmbedResponseUsage.ts │ │ ├── RerankResponseUsage.ts │ │ ├── MultimodalEmbedResponseUsage.ts │ │ ├── ContextualizedEmbedResponseUsage.ts │ │ ├── MultimodalEmbedRequestInputsItem.ts │ │ ├── EmbedRequestInputType.ts │ │ ├── EmbedResponse.ts │ │ ├── RerankResponseDataItem.ts │ │ ├── MultimodalEmbedResponse.ts │ │ ├── RerankResponse.ts │ │ ├── EmbedRequestInput.ts │ │ ├── ContextualizedEmbedResponseDataItem.ts │ │ ├── MultimodalEmbedResponseDataItem.ts │ │ ├── ContextualizedEmbedResponse.ts │ │ ├── ContextualizedEmbedRequestOutputDtype.ts │ │ ├── ContextualizedEmbedResponseDataItemDataItem.ts │ │ ├── EmbedResponseDataItem.ts │ │ ├── EmbedRequestOutputDtype.ts │ │ ├── ContextualizedEmbedRequestInputType.ts │ │ ├── index.ts │ │ ├── MultimodalEmbedRequestInputsItemContentItem.ts │ │ └── MultimodalEmbedRequestInputType.ts ├── core │ ├── runtime │ │ ├── index.ts │ │ └── runtime.ts │ ├── schemas │ │ ├── builders │ │ │ ├── set │ │ │ │ ├── index.ts │ │ │ │ └── set.ts │ │ │ ├── date │ │ │ │ ├── index.ts │ │ │ │ └── date.ts │ │ │ ├── enum │ │ │ │ ├── index.ts │ │ │ │ └── enum.ts │ │ │ ├── list │ │ │ │ ├── index.ts │ │ │ │ └── list.ts │ │ │ ├── record │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── record.ts │ │ │ ├── literals │ │ │ │ ├── index.ts │ │ │ │ ├── stringLiteral.ts │ │ │ │ └── booleanLiteral.ts │ │ │ ├── lazy │ │ │ │ ├── index.ts │ │ │ │ ├── lazyObject.ts │ │ │ │ └── lazy.ts │ │ │ ├── object-like │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── getObjectLikeUtils.ts │ │ │ ├── primitives │ │ │ │ ├── index.ts │ │ │ │ ├── any.ts │ │ │ │ ├── unknown.ts │ │ │ │ ├── number.ts │ │ │ │ ├── string.ts │ │ │ │ └── boolean.ts │ │ │ ├── schema-utils │ │ │ │ ├── index.ts │ │ │ │ ├── stringifyValidationErrors.ts │ │ │ │ ├── JsonError.ts │ │ │ │ ├── ParseError.ts │ │ │ │ └── getSchemaUtils.ts │ │ │ ├── undiscriminated-union │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── undiscriminatedUnion.ts │ │ │ ├── union │ │ │ │ ├── index.ts │ │ │ │ ├── discriminant.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ └── object │ │ │ │ ├── property.ts │ │ │ │ ├── index.ts │ │ │ │ ├── objectWithoutOptionalProperties.ts │ │ │ │ └── types.ts │ │ ├── utils │ │ │ ├── MaybePromise.ts │ │ │ ├── keys.ts │ │ │ ├── entries.ts │ │ │ ├── partition.ts │ │ │ ├── filterObject.ts │ │ │ ├── addQuestionMarksToNullableProperties.ts │ │ │ ├── isPlainObject.ts │ │ │ ├── getErrorMessageForIncorrectType.ts │ │ │ ├── createIdentitySchemaCreator.ts │ │ │ └── maybeSkipValidation.ts │ │ ├── index.ts │ │ └── Schema.ts │ ├── auth │ │ ├── index.ts │ │ ├── BearerToken.ts │ │ └── BasicAuth.ts │ ├── index.ts │ └── fetcher │ │ ├── index.ts │ │ ├── getHeader.ts │ │ ├── APIResponse.ts │ │ ├── Supplier.ts │ │ ├── createRequestUrl.ts │ │ ├── getRequestBody.ts │ │ ├── requestWithRetries.ts │ │ ├── getFetchFn.ts │ │ ├── getResponseBody.ts │ │ ├── makeRequest.ts │ │ ├── signals.ts │ │ ├── stream-wrappers │ │ ├── chooseStreamWrapper.ts │ │ └── NodePre18StreamWrapper.ts │ │ └── Fetcher.ts ├── serialization │ ├── client │ │ ├── index.ts │ │ └── requests │ │ │ ├── index.ts │ │ │ ├── RerankRequest.ts │ │ │ ├── MultimodalEmbedRequest.ts │ │ │ ├── ContextualizedEmbedRequest.ts │ │ │ └── EmbedRequest.ts │ ├── index.ts │ └── types │ │ ├── EmbedRequestInputType.ts │ │ ├── MultimodalEmbedRequestInputType.ts │ │ ├── EmbedRequestOutputDtype.ts │ │ ├── ContextualizedEmbedRequestInputType.ts │ │ ├── EmbedRequestInput.ts │ │ ├── EmbedResponseUsage.ts │ │ ├── RerankResponseUsage.ts │ │ ├── ContextualizedEmbedRequestOutputDtype.ts │ │ ├── MultimodalEmbedResponseUsage.ts │ │ ├── ContextualizedEmbedResponseUsage.ts │ │ ├── EmbedResponseDataItem.ts │ │ ├── MultimodalEmbedRequestInputsItem.ts │ │ ├── RerankResponseDataItem.ts │ │ ├── MultimodalEmbedResponseDataItem.ts │ │ ├── ContextualizedEmbedResponseDataItemDataItem.ts │ │ ├── EmbedResponse.ts │ │ ├── RerankResponse.ts │ │ ├── index.ts │ │ ├── ContextualizedEmbedResponseDataItem.ts │ │ ├── MultimodalEmbedResponse.ts │ │ ├── ContextualizedEmbedResponse.ts │ │ └── MultimodalEmbedRequestInputsItemContentItem.ts ├── errors │ ├── index.ts │ ├── VoyageAITimeoutError.ts │ └── VoyageAIError.ts ├── index.ts └── environments.ts ├── .mock ├── fern.config.json └── definition │ └── api.yml ├── tests ├── custom │ ├── example_image_01.jpg │ ├── example_video_01.mp4 │ └── auth.test.ts ├── unit │ ├── zurg │ │ ├── lazy │ │ │ ├── recursive │ │ │ │ ├── a.ts │ │ │ │ └── b.ts │ │ │ ├── lazyObject.test.ts │ │ │ └── lazy.test.ts │ │ ├── primitives │ │ │ ├── any.test.ts │ │ │ ├── unknown.test.ts │ │ │ ├── string.test.ts │ │ │ ├── boolean.test.ts │ │ │ └── number.test.ts │ │ ├── literals │ │ │ └── stringLiteral.test.ts │ │ ├── object │ │ │ ├── objectWithoutOptionalProperties.test.ts │ │ │ └── extend.test.ts │ │ ├── enum │ │ │ └── enum.test.ts │ │ ├── date │ │ │ └── date.test.ts │ │ ├── record │ │ │ └── record.test.ts │ │ ├── list │ │ │ └── list.test.ts │ │ ├── set │ │ │ └── set.test.ts │ │ ├── skipValidation.test.ts │ │ ├── undiscriminated-union │ │ │ └── undiscriminatedUnion.test.ts │ │ ├── utils │ │ │ ├── itValidate.ts │ │ │ └── itSchema.ts │ │ ├── object-like │ │ │ └── withParsedProperties.test.ts │ │ ├── schema.test.ts │ │ ├── schema-utils │ │ │ └── getSchemaUtils.test.ts │ │ └── union │ │ │ └── union.test.ts │ ├── auth │ │ ├── BearerToken.test.ts │ │ └── BasicAuth.test.ts │ └── fetcher │ │ ├── getFetchFn.test.ts │ │ ├── Fetcher.test.ts │ │ ├── stream-wrappers │ │ ├── webpack.test.ts │ │ ├── chooseStreamWrapper.test.ts │ │ └── NodePre18StreamWrapper.test.ts │ │ ├── createRequestUrl.test.ts │ │ ├── makeRequest.test.ts │ │ ├── signals.test.ts │ │ ├── getRequestBody.test.ts │ │ ├── getResponseBody.test.ts │ │ └── requestWithRetries.test.ts └── custom.test.ts ├── .npmignore ├── .fernignore ├── jest.config.js ├── tsconfig.json ├── LICENSE ├── reference.md ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | /dist -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | tabWidth: 4 2 | printWidth: 120 3 | -------------------------------------------------------------------------------- /src/api/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./requests"; 2 | -------------------------------------------------------------------------------- /src/core/runtime/index.ts: -------------------------------------------------------------------------------- 1 | export { RUNTIME } from "./runtime"; 2 | -------------------------------------------------------------------------------- /src/serialization/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./requests"; 2 | -------------------------------------------------------------------------------- /src/core/schemas/builders/set/index.ts: -------------------------------------------------------------------------------- 1 | export { set } from "./set"; 2 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./client"; 3 | -------------------------------------------------------------------------------- /src/core/schemas/builders/date/index.ts: -------------------------------------------------------------------------------- 1 | export { date } from "./date"; 2 | -------------------------------------------------------------------------------- /src/core/schemas/builders/enum/index.ts: -------------------------------------------------------------------------------- 1 | export { enum_ } from "./enum"; 2 | -------------------------------------------------------------------------------- /src/core/schemas/builders/list/index.ts: -------------------------------------------------------------------------------- 1 | export { list } from "./list"; 2 | -------------------------------------------------------------------------------- /src/core/schemas/utils/MaybePromise.ts: -------------------------------------------------------------------------------- 1 | export type MaybePromise = T | Promise; 2 | -------------------------------------------------------------------------------- /src/serialization/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./client"; 3 | -------------------------------------------------------------------------------- /.mock/fern.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "organization" : "voyage", 3 | "version" : "0.53.17" 4 | } -------------------------------------------------------------------------------- /src/core/auth/index.ts: -------------------------------------------------------------------------------- 1 | export { BasicAuth } from "./BasicAuth"; 2 | export { BearerToken } from "./BearerToken"; 3 | -------------------------------------------------------------------------------- /tests/custom/example_image_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voyage-ai/typescript-sdk/HEAD/tests/custom/example_image_01.jpg -------------------------------------------------------------------------------- /tests/custom/example_video_01.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voyage-ai/typescript-sdk/HEAD/tests/custom/example_video_01.mp4 -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | tests 4 | .gitignore 5 | .github 6 | .fernignore 7 | .prettierrc.yml 8 | tsconfig.json 9 | yarn.lock -------------------------------------------------------------------------------- /src/core/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./builders"; 2 | export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; 3 | -------------------------------------------------------------------------------- /src/core/schemas/utils/keys.ts: -------------------------------------------------------------------------------- 1 | export function keys(object: T): (keyof T)[] { 2 | return Object.keys(object) as (keyof T)[]; 3 | } 4 | -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- 1 | export { VoyageAIError } from "./VoyageAIError"; 2 | export { VoyageAITimeoutError } from "./VoyageAITimeoutError"; 3 | -------------------------------------------------------------------------------- /src/core/schemas/builders/record/index.ts: -------------------------------------------------------------------------------- 1 | export { record } from "./record"; 2 | export type { BaseRecordSchema, RecordSchema } from "./types"; 3 | -------------------------------------------------------------------------------- /src/core/schemas/builders/literals/index.ts: -------------------------------------------------------------------------------- 1 | export { stringLiteral } from "./stringLiteral"; 2 | export { booleanLiteral } from "./booleanLiteral"; 3 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./fetcher"; 2 | export * from "./auth"; 3 | export * from "./runtime"; 4 | export * as serialization from "./schemas"; 5 | -------------------------------------------------------------------------------- /.fernignore: -------------------------------------------------------------------------------- 1 | # Specify files that shouldn't be modified by Fern 2 | tests/custom 3 | tests/integration 4 | .github/workflows/ci.yml 5 | jest.config.js 6 | LICENSE -------------------------------------------------------------------------------- /src/core/schemas/builders/lazy/index.ts: -------------------------------------------------------------------------------- 1 | export { lazy } from "./lazy"; 2 | export type { SchemaGetter } from "./lazy"; 3 | export { lazyObject } from "./lazyObject"; 4 | -------------------------------------------------------------------------------- /src/core/schemas/utils/entries.ts: -------------------------------------------------------------------------------- 1 | export function entries(object: T): [keyof T, T[keyof T]][] { 2 | return Object.entries(object) as [keyof T, T[keyof T]][]; 3 | } 4 | -------------------------------------------------------------------------------- /src/core/schemas/builders/object-like/index.ts: -------------------------------------------------------------------------------- 1 | export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; 2 | export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * as VoyageAI from "./api"; 2 | export { VoyageAIClient } from "./Client"; 3 | export { VoyageAIEnvironment } from "./environments"; 4 | export { VoyageAIError, VoyageAITimeoutError } from "./errors"; 5 | -------------------------------------------------------------------------------- /src/core/schemas/builders/primitives/index.ts: -------------------------------------------------------------------------------- 1 | export { any } from "./any"; 2 | export { boolean } from "./boolean"; 3 | export { number } from "./number"; 4 | export { string } from "./string"; 5 | export { unknown } from "./unknown"; 6 | -------------------------------------------------------------------------------- /tests/unit/zurg/lazy/recursive/a.ts: -------------------------------------------------------------------------------- 1 | import { object } from "../../../../../src/core/schemas/builders/object"; 2 | import { schemaB } from "./b"; 3 | 4 | // @ts-expect-error 5 | export const schemaA = object({ 6 | b: schemaB, 7 | }); 8 | -------------------------------------------------------------------------------- /tests/unit/zurg/primitives/any.test.ts: -------------------------------------------------------------------------------- 1 | import { any } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | 4 | describe("any", () => { 5 | itSchemaIdentity(any(), true); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/unit/zurg/primitives/unknown.test.ts: -------------------------------------------------------------------------------- 1 | import { unknown } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | 4 | describe("unknown", () => { 5 | itSchemaIdentity(unknown(), true); 6 | }); 7 | -------------------------------------------------------------------------------- /src/core/fetcher/index.ts: -------------------------------------------------------------------------------- 1 | export type { APIResponse } from "./APIResponse"; 2 | export { fetcher } from "./Fetcher"; 3 | export type { Fetcher, FetchFunction } from "./Fetcher"; 4 | export { getHeader } from "./getHeader"; 5 | export { Supplier } from "./Supplier"; 6 | -------------------------------------------------------------------------------- /src/core/schemas/builders/schema-utils/index.ts: -------------------------------------------------------------------------------- 1 | export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; 2 | export type { SchemaUtils } from "./getSchemaUtils"; 3 | export { JsonError } from "./JsonError"; 4 | export { ParseError } from "./ParseError"; 5 | -------------------------------------------------------------------------------- /src/api/types/EmbedResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface EmbedResponseUsage { 6 | /** The total number of tokens used for computing the embeddings. */ 7 | totalTokens?: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/api/types/RerankResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface RerankResponseUsage { 6 | /** The total number of tokens used for computing the reranking. */ 7 | totalTokens?: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/core/schemas/builders/primitives/any.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | 4 | export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); 5 | -------------------------------------------------------------------------------- /src/core/schemas/builders/undiscriminated-union/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | inferParsedUnidiscriminatedUnionSchema, 3 | inferRawUnidiscriminatedUnionSchema, 4 | UndiscriminatedUnionSchema, 5 | } from "./types"; 6 | export { undiscriminatedUnion } from "./undiscriminatedUnion"; 7 | -------------------------------------------------------------------------------- /src/serialization/client/requests/index.ts: -------------------------------------------------------------------------------- 1 | export { EmbedRequest } from "./EmbedRequest"; 2 | export { RerankRequest } from "./RerankRequest"; 3 | export { MultimodalEmbedRequest } from "./MultimodalEmbedRequest"; 4 | export { ContextualizedEmbedRequest } from "./ContextualizedEmbedRequest"; 5 | -------------------------------------------------------------------------------- /src/api/types/MultimodalEmbedResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface MultimodalEmbedResponseUsage { 6 | /** The total number of tokens used for computing the embeddings. */ 7 | totalTokens?: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/environments.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export const VoyageAIEnvironment = { 6 | Default: "https://api.voyageai.com/v1", 7 | } as const; 8 | 9 | export type VoyageAIEnvironment = typeof VoyageAIEnvironment.Default; 10 | -------------------------------------------------------------------------------- /src/api/client/requests/index.ts: -------------------------------------------------------------------------------- 1 | export { type EmbedRequest } from "./EmbedRequest"; 2 | export { type RerankRequest } from "./RerankRequest"; 3 | export { type MultimodalEmbedRequest } from "./MultimodalEmbedRequest"; 4 | export { type ContextualizedEmbedRequest } from "./ContextualizedEmbedRequest"; 5 | -------------------------------------------------------------------------------- /src/core/schemas/builders/primitives/unknown.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | 4 | export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); 5 | -------------------------------------------------------------------------------- /src/api/types/ContextualizedEmbedResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface ContextualizedEmbedResponseUsage { 6 | /** The total number of tokens used for computing the contextualized embeddings. */ 7 | totalTokens?: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/api/types/MultimodalEmbedRequestInputsItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../index"; 6 | 7 | export interface MultimodalEmbedRequestInputsItem { 8 | content?: VoyageAI.MultimodalEmbedRequestInputsItemContentItem[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/errors/VoyageAITimeoutError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export class VoyageAITimeoutError extends Error { 6 | constructor() { 7 | super("Timeout"); 8 | Object.setPrototypeOf(this, VoyageAITimeoutError.prototype); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/unit/zurg/lazy/recursive/b.ts: -------------------------------------------------------------------------------- 1 | import { object } from "../../../../../src/core/schemas/builders/object"; 2 | import { optional } from "../../../../../src/core/schemas/builders/schema-utils"; 3 | import { schemaA } from "./a"; 4 | 5 | // @ts-expect-error 6 | export const schemaB = object({ 7 | a: optional(schemaA), 8 | }); 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | coverageThreshold: { 6 | "global": { 7 | "branches": 70, 8 | "functions": 80, 9 | "lines": 85, 10 | "statements": 85, 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from "../../Schema"; 2 | 3 | export function stringifyValidationError(error: ValidationError): string { 4 | if (error.path.length === 0) { 5 | return error.message; 6 | } 7 | return `${error.path.join(" -> ")}: ${error.message}`; 8 | } 9 | -------------------------------------------------------------------------------- /src/core/fetcher/getHeader.ts: -------------------------------------------------------------------------------- 1 | export function getHeader(headers: Record, header: string): string | undefined { 2 | for (const [headerKey, headerValue] of Object.entries(headers)) { 3 | if (headerKey.toLowerCase() === header.toLowerCase()) { 4 | return headerValue; 5 | } 6 | } 7 | return undefined; 8 | } 9 | -------------------------------------------------------------------------------- /src/core/fetcher/APIResponse.ts: -------------------------------------------------------------------------------- 1 | export type APIResponse = SuccessfulResponse | FailedResponse; 2 | 3 | export interface SuccessfulResponse { 4 | ok: true; 5 | body: T; 6 | headers?: Record; 7 | } 8 | 9 | export interface FailedResponse { 10 | ok: false; 11 | error: T; 12 | } 13 | -------------------------------------------------------------------------------- /src/core/schemas/builders/union/index.ts: -------------------------------------------------------------------------------- 1 | export { discriminant } from "./discriminant"; 2 | export type { Discriminant } from "./discriminant"; 3 | export type { 4 | inferParsedDiscriminant, 5 | inferParsedUnion, 6 | inferRawDiscriminant, 7 | inferRawUnion, 8 | UnionSubtypes, 9 | } from "./types"; 10 | export { union } from "./union"; 11 | -------------------------------------------------------------------------------- /.mock/definition/api.yml: -------------------------------------------------------------------------------- 1 | name: api 2 | error-discrimination: 3 | strategy: status-code 4 | display-name: Voyage API 5 | environments: 6 | Default: https://api.voyageai.com/v1 7 | default-environment: Default 8 | auth-schemes: 9 | BearerAuthScheme: 10 | scheme: bearer 11 | token: 12 | name: apiKey 13 | env: VOYAGE_API_KEY 14 | auth: BearerAuthScheme 15 | -------------------------------------------------------------------------------- /src/core/fetcher/Supplier.ts: -------------------------------------------------------------------------------- 1 | export type Supplier = T | Promise | (() => T | Promise); 2 | 3 | export const Supplier = { 4 | get: async (supplier: Supplier): Promise => { 5 | if (typeof supplier === "function") { 6 | return (supplier as () => T)(); 7 | } else { 8 | return supplier; 9 | } 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/core/fetcher/createRequestUrl.ts: -------------------------------------------------------------------------------- 1 | import qs from "qs"; 2 | 3 | export function createRequestUrl( 4 | baseUrl: string, 5 | queryParameters?: Record 6 | ): string { 7 | return Object.keys(queryParameters ?? {}).length > 0 8 | ? `${baseUrl}?${qs.stringify(queryParameters, { arrayFormat: "repeat" })}` 9 | : baseUrl; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/types/EmbedRequestInputType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | /** 6 | * Type of the input text. Defaults to `null`. Other options: `query`, `document`. 7 | */ 8 | export type EmbedRequestInputType = "query" | "document"; 9 | 10 | export const EmbedRequestInputType = { 11 | Query: "query", 12 | Document: "document", 13 | } as const; 14 | -------------------------------------------------------------------------------- /tests/custom.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a custom test file, if you wish to add more tests 3 | * to your SDK. 4 | * Be sure to mark this file in `.fernignore`. 5 | * 6 | * If you include example requests/responses in your fern definition, 7 | * you will have tests automatically generated for you. 8 | */ 9 | describe("test", () => { 10 | it("default", () => { 11 | expect(true).toBe(true); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/core/schemas/builders/schema-utils/JsonError.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from "../../Schema"; 2 | import { stringifyValidationError } from "./stringifyValidationErrors"; 3 | 4 | export class JsonError extends Error { 5 | constructor(public readonly errors: ValidationError[]) { 6 | super(errors.map(stringifyValidationError).join("; ")); 7 | Object.setPrototypeOf(this, JsonError.prototype); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/core/schemas/builders/schema-utils/ParseError.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from "../../Schema"; 2 | import { stringifyValidationError } from "./stringifyValidationErrors"; 3 | 4 | export class ParseError extends Error { 5 | constructor(public readonly errors: ValidationError[]) { 6 | super(errors.map(stringifyValidationError).join("; ")); 7 | Object.setPrototypeOf(this, ParseError.prototype); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/core/schemas/builders/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./date"; 2 | export * from "./enum"; 3 | export * from "./lazy"; 4 | export * from "./list"; 5 | export * from "./literals"; 6 | export * from "./object"; 7 | export * from "./object-like"; 8 | export * from "./primitives"; 9 | export * from "./record"; 10 | export * from "./schema-utils"; 11 | export * from "./set"; 12 | export * from "./undiscriminated-union"; 13 | export * from "./union"; 14 | -------------------------------------------------------------------------------- /src/core/schemas/utils/partition.ts: -------------------------------------------------------------------------------- 1 | export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { 2 | const trueItems: T[] = [], 3 | falseItems: T[] = []; 4 | for (const item of items) { 5 | if (predicate(item)) { 6 | trueItems.push(item); 7 | } else { 8 | falseItems.push(item); 9 | } 10 | } 11 | return [trueItems, falseItems]; 12 | } 13 | -------------------------------------------------------------------------------- /src/core/fetcher/getRequestBody.ts: -------------------------------------------------------------------------------- 1 | export declare namespace GetRequestBody { 2 | interface Args { 3 | body: unknown; 4 | type: "json" | "file" | "bytes" | "other"; 5 | } 6 | } 7 | 8 | export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { 9 | if (type.includes("json")) { 10 | return JSON.stringify(body); 11 | } else { 12 | return body as BodyInit; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "extendedDiagnostics": true, 4 | "strict": true, 5 | "target": "ES6", 6 | "module": "CommonJS", 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "declaration": true, 11 | "outDir": "dist", 12 | "rootDir": "src", 13 | "baseUrl": "src" 14 | }, 15 | "include": ["src"], 16 | "exclude": [] 17 | } 18 | -------------------------------------------------------------------------------- /src/api/types/EmbedResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../index"; 6 | 7 | export interface EmbedResponse { 8 | /** The object type, which is always "list". */ 9 | object?: string; 10 | /** An array of embedding objects. */ 11 | data?: VoyageAI.EmbedResponseDataItem[]; 12 | /** Name of the model. */ 13 | model?: string; 14 | usage?: VoyageAI.EmbedResponseUsage; 15 | } 16 | -------------------------------------------------------------------------------- /src/api/types/RerankResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface RerankResponseDataItem { 6 | /** The index of the document in the input list. */ 7 | index?: number; 8 | /** The relevance score of the document with respect to the query. */ 9 | relevanceScore?: number; 10 | /** The document string. Only returned when return_documents is set to true. */ 11 | document?: string; 12 | } 13 | -------------------------------------------------------------------------------- /tests/unit/zurg/primitives/string.test.ts: -------------------------------------------------------------------------------- 1 | import { string } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("string", () => { 6 | itSchemaIdentity(string(), "hello"); 7 | 8 | itValidate("non-string", string(), 42, [ 9 | { 10 | path: [], 11 | message: "Expected string. Received 42.", 12 | }, 13 | ]); 14 | }); 15 | -------------------------------------------------------------------------------- /src/core/schemas/builders/object-like/types.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, Schema } from "../../Schema"; 2 | 3 | export type ObjectLikeSchema = Schema & 4 | BaseSchema & 5 | ObjectLikeUtils; 6 | 7 | export interface ObjectLikeUtils { 8 | withParsedProperties: >(properties: { 9 | [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); 10 | }) => ObjectLikeSchema; 11 | } 12 | -------------------------------------------------------------------------------- /tests/unit/zurg/primitives/boolean.test.ts: -------------------------------------------------------------------------------- 1 | import { boolean } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("boolean", () => { 6 | itSchemaIdentity(boolean(), true); 7 | 8 | itValidate("non-boolean", boolean(), {}, [ 9 | { 10 | path: [], 11 | message: "Expected boolean. Received object.", 12 | }, 13 | ]); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/unit/zurg/primitives/number.test.ts: -------------------------------------------------------------------------------- 1 | import { number } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("number", () => { 6 | itSchemaIdentity(number(), 42); 7 | 8 | itValidate("non-number", number(), "hello", [ 9 | { 10 | path: [], 11 | message: 'Expected number. Received "hello".', 12 | }, 13 | ]); 14 | }); 15 | -------------------------------------------------------------------------------- /src/core/schemas/utils/filterObject.ts: -------------------------------------------------------------------------------- 1 | export function filterObject(obj: T, keysToInclude: K[]): Pick { 2 | const keysToIncludeSet = new Set(keysToInclude); 3 | return Object.entries(obj).reduce((acc, [key, value]) => { 4 | if (keysToIncludeSet.has(key as K)) { 5 | acc[key as K] = value; 6 | } 7 | return acc; 8 | // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter 9 | }, {} as Pick); 10 | } 11 | -------------------------------------------------------------------------------- /src/core/schemas/utils/addQuestionMarksToNullableProperties.ts: -------------------------------------------------------------------------------- 1 | export type addQuestionMarksToNullableProperties = { 2 | [K in OptionalKeys]?: T[K]; 3 | } & Pick>; 4 | 5 | export type OptionalKeys = { 6 | [K in keyof T]-?: undefined extends T[K] 7 | ? K 8 | : null extends T[K] 9 | ? K 10 | : 1 extends (any extends T[K] ? 0 : 1) 11 | ? never 12 | : K; 13 | }[keyof T]; 14 | 15 | export type RequiredKeys = Exclude>; 16 | -------------------------------------------------------------------------------- /src/api/types/MultimodalEmbedResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../index"; 6 | 7 | export interface MultimodalEmbedResponse { 8 | /** The object type, which is always "list". */ 9 | object?: string; 10 | /** An array of embedding objects. */ 11 | data?: VoyageAI.MultimodalEmbedResponseDataItem[]; 12 | /** Name of the model. */ 13 | model?: string; 14 | usage?: VoyageAI.MultimodalEmbedResponseUsage; 15 | } 16 | -------------------------------------------------------------------------------- /src/api/types/RerankResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../index"; 6 | 7 | export interface RerankResponse { 8 | /** The object type, which is always "list". */ 9 | object?: string; 10 | /** An array of the reranking results, sorted by the descending order of relevance scores. */ 11 | data?: VoyageAI.RerankResponseDataItem[]; 12 | /** Name of the model. */ 13 | model?: string; 14 | usage?: VoyageAI.RerankResponseUsage; 15 | } 16 | -------------------------------------------------------------------------------- /src/api/types/EmbedRequestInput.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | /** 6 | * A single text string, or a list of texts as a list of strings. Currently, we have two constraints on the list:
  • The maximum length of the list is 128.
  • The total number of tokens in the list is at most 320K for `voyage-2`, and 120K for `voyage-large-2`, `voyage-finance-2`, `voyage-multilingual-2`, `voyage-law-2`, and `voyage-code-2`.
    • 7 | */ 8 | export type EmbedRequestInput = string | string[]; 9 | -------------------------------------------------------------------------------- /src/core/auth/BearerToken.ts: -------------------------------------------------------------------------------- 1 | export type BearerToken = string; 2 | 3 | const BEARER_AUTH_HEADER_PREFIX = /^Bearer /i; 4 | 5 | export const BearerToken = { 6 | toAuthorizationHeader: (token: BearerToken | undefined): string | undefined => { 7 | if (token == null) { 8 | return undefined; 9 | } 10 | return `Bearer ${token}`; 11 | }, 12 | fromAuthorizationHeader: (header: string): BearerToken => { 13 | return header.replace(BEARER_AUTH_HEADER_PREFIX, "").trim() as BearerToken; 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/api/types/ContextualizedEmbedResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../index"; 6 | 7 | export interface ContextualizedEmbedResponseDataItem { 8 | /** The object type, which is always "list". */ 9 | object?: string; 10 | /** An array of embeddings for each chunk in the document. */ 11 | data?: VoyageAI.ContextualizedEmbedResponseDataItemDataItem[]; 12 | /** The index of this document within the input list. */ 13 | index?: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/core/schemas/builders/undiscriminated-union/types.ts: -------------------------------------------------------------------------------- 1 | import { inferParsed, inferRaw, Schema } from "../../Schema"; 2 | 3 | export type UndiscriminatedUnionSchema = Schema< 4 | inferRawUnidiscriminatedUnionSchema, 5 | inferParsedUnidiscriminatedUnionSchema 6 | >; 7 | 8 | export type inferRawUnidiscriminatedUnionSchema = inferRaw; 9 | 10 | export type inferParsedUnidiscriminatedUnionSchema = inferParsed; 11 | -------------------------------------------------------------------------------- /src/api/types/MultimodalEmbedResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface MultimodalEmbedResponseDataItem { 6 | /** The object type, which is always "embedding". */ 7 | object?: string; 8 | /** The embedding vector consists of a list of floating-point numbers. The length of this vector varies depending on the specific model. */ 9 | embedding?: number[]; 10 | /** An integer representing the index of the embedding within the list of embeddings. */ 11 | index?: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/api/types/ContextualizedEmbedResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../index"; 6 | 7 | export interface ContextualizedEmbedResponse { 8 | /** The object type, which is always "list". */ 9 | object?: string; 10 | /** An array of contextualized embedding results, one for each input document. */ 11 | data?: VoyageAI.ContextualizedEmbedResponseDataItem[]; 12 | /** Name of the model used. */ 13 | model?: string; 14 | usage?: VoyageAI.ContextualizedEmbedResponseUsage; 15 | } 16 | -------------------------------------------------------------------------------- /src/core/schemas/builders/union/discriminant.ts: -------------------------------------------------------------------------------- 1 | export function discriminant( 2 | parsedDiscriminant: ParsedDiscriminant, 3 | rawDiscriminant: RawDiscriminant 4 | ): Discriminant { 5 | return { 6 | parsedDiscriminant, 7 | rawDiscriminant, 8 | }; 9 | } 10 | 11 | export interface Discriminant { 12 | parsedDiscriminant: ParsedDiscriminant; 13 | rawDiscriminant: RawDiscriminant; 14 | } 15 | -------------------------------------------------------------------------------- /src/serialization/types/EmbedRequestInputType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const EmbedRequestInputType: core.serialization.Schema< 10 | serializers.EmbedRequestInputType.Raw, 11 | VoyageAI.EmbedRequestInputType 12 | > = core.serialization.enum_(["query", "document"]); 13 | 14 | export declare namespace EmbedRequestInputType { 15 | type Raw = "query" | "document"; 16 | } 17 | -------------------------------------------------------------------------------- /src/api/types/ContextualizedEmbedRequestOutputDtype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | /** 6 | * The data type for the embeddings to be returned. Defaults to `float`. Other options: `int8`, `uint8`, `binary`, `ubinary`. 7 | */ 8 | export type ContextualizedEmbedRequestOutputDtype = "float" | "int8" | "uint8" | "binary" | "ubinary"; 9 | 10 | export const ContextualizedEmbedRequestOutputDtype = { 11 | Float: "float", 12 | Int8: "int8", 13 | Uint8: "uint8", 14 | Binary: "binary", 15 | Ubinary: "ubinary", 16 | } as const; 17 | -------------------------------------------------------------------------------- /src/core/schemas/utils/isPlainObject.ts: -------------------------------------------------------------------------------- 1 | // borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js 2 | export function isPlainObject(value: unknown): value is Record { 3 | if (typeof value !== "object" || value === null) { 4 | return false; 5 | } 6 | 7 | if (Object.getPrototypeOf(value) === null) { 8 | return true; 9 | } 10 | 11 | let proto = value; 12 | while (Object.getPrototypeOf(proto) !== null) { 13 | proto = Object.getPrototypeOf(proto); 14 | } 15 | 16 | return Object.getPrototypeOf(value) === proto; 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/auth/BearerToken.test.ts: -------------------------------------------------------------------------------- 1 | import { BearerToken } from "../../../src/core/auth/BearerToken"; 2 | 3 | describe("BearerToken", () => { 4 | describe("toAuthorizationHeader", () => { 5 | it("correctly converts to header", () => { 6 | expect(BearerToken.toAuthorizationHeader("my-token")).toBe("Bearer my-token"); 7 | }); 8 | }); 9 | describe("fromAuthorizationHeader", () => { 10 | it("correctly parses header", () => { 11 | expect(BearerToken.fromAuthorizationHeader("Bearer my-token")).toBe("my-token"); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/unit/zurg/lazy/lazyObject.test.ts: -------------------------------------------------------------------------------- 1 | import { lazyObject, number, object, string } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | 4 | describe("lazy", () => { 5 | itSchemaIdentity( 6 | lazyObject(() => object({ foo: string() })), 7 | { foo: "hello" } 8 | ); 9 | 10 | itSchemaIdentity( 11 | lazyObject(() => object({ foo: string() })).extend(object({ bar: number() })), 12 | { 13 | foo: "hello", 14 | bar: 42, 15 | }, 16 | { title: "returned schema has object utils" } 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/serialization/types/MultimodalEmbedRequestInputType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const MultimodalEmbedRequestInputType: core.serialization.Schema< 10 | serializers.MultimodalEmbedRequestInputType.Raw, 11 | VoyageAI.MultimodalEmbedRequestInputType 12 | > = core.serialization.enum_(["query", "document"]); 13 | 14 | export declare namespace MultimodalEmbedRequestInputType { 15 | type Raw = "query" | "document"; 16 | } 17 | -------------------------------------------------------------------------------- /src/serialization/types/EmbedRequestOutputDtype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const EmbedRequestOutputDtype: core.serialization.Schema< 10 | serializers.EmbedRequestOutputDtype.Raw, 11 | VoyageAI.EmbedRequestOutputDtype 12 | > = core.serialization.enum_(["float", "int8", "uint8", "binary", "ubinary"]); 13 | 14 | export declare namespace EmbedRequestOutputDtype { 15 | type Raw = "float" | "int8" | "uint8" | "binary" | "ubinary"; 16 | } 17 | -------------------------------------------------------------------------------- /src/serialization/types/ContextualizedEmbedRequestInputType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const ContextualizedEmbedRequestInputType: core.serialization.Schema< 10 | serializers.ContextualizedEmbedRequestInputType.Raw, 11 | VoyageAI.ContextualizedEmbedRequestInputType 12 | > = core.serialization.enum_(["query", "document"]); 13 | 14 | export declare namespace ContextualizedEmbedRequestInputType { 15 | type Raw = "query" | "document"; 16 | } 17 | -------------------------------------------------------------------------------- /src/api/types/ContextualizedEmbedResponseDataItemDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface ContextualizedEmbedResponseDataItemDataItem { 6 | /** The object type, which is always "embedding". */ 7 | object?: string; 8 | /** The contextualized embedding vector for this chunk, represented as an array of numbers. The data type depends on the `output_dtype` parameter: floating-point numbers for `float`, integers for `int8`, `uint8`, `binary`, and `ubinary`. */ 9 | embedding?: number[]; 10 | /** The index of this chunk within the document. */ 11 | index?: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/serialization/types/EmbedRequestInput.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const EmbedRequestInput: core.serialization.Schema< 10 | serializers.EmbedRequestInput.Raw, 11 | VoyageAI.EmbedRequestInput 12 | > = core.serialization.undiscriminatedUnion([ 13 | core.serialization.string(), 14 | core.serialization.list(core.serialization.string()), 15 | ]); 16 | 17 | export declare namespace EmbedRequestInput { 18 | type Raw = string | string[]; 19 | } 20 | -------------------------------------------------------------------------------- /src/core/schemas/builders/record/types.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from "../../Schema"; 2 | import { SchemaUtils } from "../schema-utils"; 3 | 4 | export type RecordSchema< 5 | RawKey extends string | number, 6 | RawValue, 7 | ParsedKey extends string | number, 8 | ParsedValue 9 | > = BaseRecordSchema & 10 | SchemaUtils, Record>; 11 | 12 | export type BaseRecordSchema< 13 | RawKey extends string | number, 14 | RawValue, 15 | ParsedKey extends string | number, 16 | ParsedValue 17 | > = BaseSchema, Record>; 18 | -------------------------------------------------------------------------------- /src/core/schemas/utils/getErrorMessageForIncorrectType.ts: -------------------------------------------------------------------------------- 1 | export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { 2 | return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; 3 | } 4 | 5 | function getTypeAsString(value: unknown): string { 6 | if (Array.isArray(value)) { 7 | return "list"; 8 | } 9 | if (value === null) { 10 | return "null"; 11 | } 12 | switch (typeof value) { 13 | case "string": 14 | return `"${value}"`; 15 | case "number": 16 | case "boolean": 17 | case "undefined": 18 | return `${value}`; 19 | } 20 | return typeof value; 21 | } 22 | -------------------------------------------------------------------------------- /src/serialization/types/EmbedResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const EmbedResponseUsage: core.serialization.ObjectSchema< 10 | serializers.EmbedResponseUsage.Raw, 11 | VoyageAI.EmbedResponseUsage 12 | > = core.serialization.object({ 13 | totalTokens: core.serialization.property("total_tokens", core.serialization.number().optional()), 14 | }); 15 | 16 | export declare namespace EmbedResponseUsage { 17 | interface Raw { 18 | total_tokens?: number | null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/serialization/types/RerankResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const RerankResponseUsage: core.serialization.ObjectSchema< 10 | serializers.RerankResponseUsage.Raw, 11 | VoyageAI.RerankResponseUsage 12 | > = core.serialization.object({ 13 | totalTokens: core.serialization.property("total_tokens", core.serialization.number().optional()), 14 | }); 15 | 16 | export declare namespace RerankResponseUsage { 17 | interface Raw { 18 | total_tokens?: number | null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit/zurg/literals/stringLiteral.test.ts: -------------------------------------------------------------------------------- 1 | import { stringLiteral } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("stringLiteral", () => { 6 | itSchemaIdentity(stringLiteral("A"), "A"); 7 | 8 | itValidate("incorrect string", stringLiteral("A"), "B", [ 9 | { 10 | path: [], 11 | message: 'Expected "A". Received "B".', 12 | }, 13 | ]); 14 | 15 | itValidate("non-string", stringLiteral("A"), 42, [ 16 | { 17 | path: [], 18 | message: 'Expected "A". Received 42.', 19 | }, 20 | ]); 21 | }); 22 | -------------------------------------------------------------------------------- /src/serialization/types/ContextualizedEmbedRequestOutputDtype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const ContextualizedEmbedRequestOutputDtype: core.serialization.Schema< 10 | serializers.ContextualizedEmbedRequestOutputDtype.Raw, 11 | VoyageAI.ContextualizedEmbedRequestOutputDtype 12 | > = core.serialization.enum_(["float", "int8", "uint8", "binary", "ubinary"]); 13 | 14 | export declare namespace ContextualizedEmbedRequestOutputDtype { 15 | type Raw = "float" | "int8" | "uint8" | "binary" | "ubinary"; 16 | } 17 | -------------------------------------------------------------------------------- /tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts: -------------------------------------------------------------------------------- 1 | import { objectWithoutOptionalProperties, string, stringLiteral } from "../../../../src/core/schemas/builders"; 2 | import { itSchema } from "../utils/itSchema"; 3 | 4 | describe("objectWithoutOptionalProperties", () => { 5 | itSchema( 6 | "all properties are required", 7 | objectWithoutOptionalProperties({ 8 | foo: string(), 9 | bar: stringLiteral("bar").optional(), 10 | }), 11 | { 12 | raw: { 13 | foo: "hello", 14 | }, 15 | // @ts-expect-error 16 | parsed: { 17 | foo: "hello", 18 | }, 19 | } 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /src/api/types/EmbedResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface EmbedResponseDataItem { 6 | /** The object type, which is always "embedding". */ 7 | object?: string; 8 | /** Each embedding is a vector represented as an array of `float` numbers when `output_dtype` is set to `float` and as an array of integers for all other values of `output_dtype` (`int8`, `uint8`, `binary`, and `ubinary`). The length of this vector varies depending on the specific model, `output_dimension`, and `output_dtype`. */ 9 | embedding?: number[]; 10 | /** An integer representing the index of the embedding within the list of embeddings. */ 11 | index?: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/api/types/EmbedRequestOutputDtype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | /** 6 | * The data type for the embeddings to be returned. Defaults to `float`. Other options: `int8`, `uint8`, `binary`, `ubinary`. `float` is supported for all models. `int8`, `uint8`, `binary`, and `ubinary` are supported by `voyage-3-large` and `voyage-code-3`. Please see our guide for more details about output data types. 7 | */ 8 | export type EmbedRequestOutputDtype = "float" | "int8" | "uint8" | "binary" | "ubinary"; 9 | 10 | export const EmbedRequestOutputDtype = { 11 | Float: "float", 12 | Int8: "int8", 13 | Uint8: "uint8", 14 | Binary: "binary", 15 | Ubinary: "ubinary", 16 | } as const; 17 | -------------------------------------------------------------------------------- /src/serialization/types/MultimodalEmbedResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const MultimodalEmbedResponseUsage: core.serialization.ObjectSchema< 10 | serializers.MultimodalEmbedResponseUsage.Raw, 11 | VoyageAI.MultimodalEmbedResponseUsage 12 | > = core.serialization.object({ 13 | totalTokens: core.serialization.property("total_tokens", core.serialization.number().optional()), 14 | }); 15 | 16 | export declare namespace MultimodalEmbedResponseUsage { 17 | interface Raw { 18 | total_tokens?: number | null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/serialization/types/ContextualizedEmbedResponseUsage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const ContextualizedEmbedResponseUsage: core.serialization.ObjectSchema< 10 | serializers.ContextualizedEmbedResponseUsage.Raw, 11 | VoyageAI.ContextualizedEmbedResponseUsage 12 | > = core.serialization.object({ 13 | totalTokens: core.serialization.property("total_tokens", core.serialization.number().optional()), 14 | }); 15 | 16 | export declare namespace ContextualizedEmbedResponseUsage { 17 | interface Raw { 18 | total_tokens?: number | null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/core/fetcher/requestWithRetries.ts: -------------------------------------------------------------------------------- 1 | const INITIAL_RETRY_DELAY = 1; 2 | const MAX_RETRY_DELAY = 60; 3 | const DEFAULT_MAX_RETRIES = 2; 4 | 5 | export async function requestWithRetries( 6 | requestFn: () => Promise, 7 | maxRetries: number = DEFAULT_MAX_RETRIES 8 | ): Promise { 9 | let response: Response = await requestFn(); 10 | 11 | for (let i = 0; i < maxRetries; ++i) { 12 | if ([408, 409, 429].includes(response.status) || response.status >= 500) { 13 | const delay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, i), MAX_RETRY_DELAY); 14 | await new Promise((resolve) => setTimeout(resolve, delay)); 15 | response = await requestFn(); 16 | } else { 17 | break; 18 | } 19 | } 20 | return response!; 21 | } 22 | -------------------------------------------------------------------------------- /src/core/schemas/utils/createIdentitySchemaCreator.ts: -------------------------------------------------------------------------------- 1 | import { getSchemaUtils } from "../builders/schema-utils"; 2 | import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; 3 | import { maybeSkipValidation } from "./maybeSkipValidation"; 4 | 5 | export function createIdentitySchemaCreator( 6 | schemaType: SchemaType, 7 | validate: (value: unknown, opts?: SchemaOptions) => MaybeValid 8 | ): () => Schema { 9 | return () => { 10 | const baseSchema: BaseSchema = { 11 | parse: validate, 12 | json: validate, 13 | getType: () => schemaType, 14 | }; 15 | 16 | return { 17 | ...maybeSkipValidation(baseSchema), 18 | ...getSchemaUtils(baseSchema), 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/core/schemas/builders/object/property.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from "../../Schema"; 2 | 3 | export function property( 4 | rawKey: RawKey, 5 | valueSchema: Schema 6 | ): Property { 7 | return { 8 | rawKey, 9 | valueSchema, 10 | isProperty: true, 11 | }; 12 | } 13 | 14 | export interface Property { 15 | rawKey: RawKey; 16 | valueSchema: Schema; 17 | isProperty: true; 18 | } 19 | 20 | export function isProperty>(maybeProperty: unknown): maybeProperty is O { 21 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 22 | return (maybeProperty as O).isProperty; 23 | } 24 | -------------------------------------------------------------------------------- /tests/unit/auth/BasicAuth.test.ts: -------------------------------------------------------------------------------- 1 | import { BasicAuth } from "../../../src/core/auth/BasicAuth"; 2 | 3 | describe("BasicAuth", () => { 4 | describe("toAuthorizationHeader", () => { 5 | it("correctly converts to header", () => { 6 | expect( 7 | BasicAuth.toAuthorizationHeader({ 8 | username: "username", 9 | password: "password", 10 | }) 11 | ).toBe("Basic dXNlcm5hbWU6cGFzc3dvcmQ="); 12 | }); 13 | }); 14 | describe("fromAuthorizationHeader", () => { 15 | it("correctly parses header", () => { 16 | expect(BasicAuth.fromAuthorizationHeader("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")).toEqual({ 17 | username: "username", 18 | password: "password", 19 | }); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/serialization/types/EmbedResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const EmbedResponseDataItem: core.serialization.ObjectSchema< 10 | serializers.EmbedResponseDataItem.Raw, 11 | VoyageAI.EmbedResponseDataItem 12 | > = core.serialization.object({ 13 | object: core.serialization.string().optional(), 14 | embedding: core.serialization.list(core.serialization.number()).optional(), 15 | index: core.serialization.number().optional(), 16 | }); 17 | 18 | export declare namespace EmbedResponseDataItem { 19 | interface Raw { 20 | object?: string | null; 21 | embedding?: number[] | null; 22 | index?: number | null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/core/schemas/builders/object/index.ts: -------------------------------------------------------------------------------- 1 | export { getObjectUtils, object } from "./object"; 2 | export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; 3 | export type { 4 | inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, 5 | inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, 6 | } from "./objectWithoutOptionalProperties"; 7 | export { isProperty, property } from "./property"; 8 | export type { Property } from "./property"; 9 | export type { 10 | BaseObjectSchema, 11 | inferObjectSchemaFromPropertySchemas, 12 | inferParsedObject, 13 | inferParsedObjectFromPropertySchemas, 14 | inferParsedPropertySchema, 15 | inferRawKey, 16 | inferRawObject, 17 | inferRawObjectFromPropertySchemas, 18 | inferRawPropertySchema, 19 | ObjectSchema, 20 | ObjectUtils, 21 | PropertySchemas, 22 | } from "./types"; 23 | -------------------------------------------------------------------------------- /src/serialization/types/MultimodalEmbedRequestInputsItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | import { MultimodalEmbedRequestInputsItemContentItem } from "./MultimodalEmbedRequestInputsItemContentItem"; 9 | 10 | export const MultimodalEmbedRequestInputsItem: core.serialization.ObjectSchema< 11 | serializers.MultimodalEmbedRequestInputsItem.Raw, 12 | VoyageAI.MultimodalEmbedRequestInputsItem 13 | > = core.serialization.object({ 14 | content: core.serialization.list(MultimodalEmbedRequestInputsItemContentItem).optional(), 15 | }); 16 | 17 | export declare namespace MultimodalEmbedRequestInputsItem { 18 | interface Raw { 19 | content?: MultimodalEmbedRequestInputsItemContentItem.Raw[] | null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/core/fetcher/getFetchFn.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIME } from "../runtime"; 2 | 3 | /** 4 | * Returns a fetch function based on the runtime 5 | */ 6 | export async function getFetchFn(): Promise { 7 | // In Node.js 18+ environments, use native fetch 8 | if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { 9 | return fetch; 10 | } 11 | 12 | // In Node.js 18 or lower environments, the SDK always uses`node-fetch`. 13 | if (RUNTIME.type === "node") { 14 | return (await import("node-fetch")).default as any; 15 | } 16 | 17 | // Otherwise the SDK uses global fetch if available, 18 | // and falls back to node-fetch. 19 | if (typeof fetch == "function") { 20 | return fetch; 21 | } 22 | 23 | // Defaults to node `node-fetch` if global fetch isn't available 24 | return (await import("node-fetch")).default as any; 25 | } 26 | -------------------------------------------------------------------------------- /src/serialization/types/RerankResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const RerankResponseDataItem: core.serialization.ObjectSchema< 10 | serializers.RerankResponseDataItem.Raw, 11 | VoyageAI.RerankResponseDataItem 12 | > = core.serialization.object({ 13 | index: core.serialization.number().optional(), 14 | relevanceScore: core.serialization.property("relevance_score", core.serialization.number().optional()), 15 | document: core.serialization.string().optional(), 16 | }); 17 | 18 | export declare namespace RerankResponseDataItem { 19 | interface Raw { 20 | index?: number | null; 21 | relevance_score?: number | null; 22 | document?: string | null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/unit/fetcher/getFetchFn.test.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIME } from "../../../src/core/runtime"; 2 | import { getFetchFn } from "../../../src/core/fetcher/getFetchFn"; 3 | 4 | describe("Test for getFetchFn", () => { 5 | it("should get node-fetch function", async () => { 6 | if (RUNTIME.type == "node") { 7 | if (RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { 8 | expect(await getFetchFn()).toBe(fetch); 9 | } else { 10 | expect(await getFetchFn()).toEqual((await import("node-fetch")).default as any); 11 | } 12 | } 13 | }); 14 | 15 | it("should get fetch function", async () => { 16 | if (RUNTIME.type == "browser") { 17 | const fetchFn = await getFetchFn(); 18 | expect(typeof fetchFn).toBe("function"); 19 | expect(fetchFn.name).toBe("fetch"); 20 | } 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/unit/zurg/enum/enum.test.ts: -------------------------------------------------------------------------------- 1 | import { enum_ } from "../../../../src/core/schemas/builders/enum"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("enum", () => { 6 | itSchemaIdentity(enum_(["A", "B", "C"]), "A"); 7 | 8 | itSchemaIdentity(enum_(["A", "B", "C"]), "D" as any, { 9 | opts: { allowUnrecognizedEnumValues: true }, 10 | }); 11 | 12 | itValidate("invalid enum", enum_(["A", "B", "C"]), "D", [ 13 | { 14 | message: 'Expected enum. Received "D".', 15 | path: [], 16 | }, 17 | ]); 18 | 19 | itValidate( 20 | "non-string", 21 | enum_(["A", "B", "C"]), 22 | [], 23 | [ 24 | { 25 | message: "Expected string. Received list.", 26 | path: [], 27 | }, 28 | ] 29 | ); 30 | }); 31 | -------------------------------------------------------------------------------- /src/serialization/types/MultimodalEmbedResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const MultimodalEmbedResponseDataItem: core.serialization.ObjectSchema< 10 | serializers.MultimodalEmbedResponseDataItem.Raw, 11 | VoyageAI.MultimodalEmbedResponseDataItem 12 | > = core.serialization.object({ 13 | object: core.serialization.string().optional(), 14 | embedding: core.serialization.list(core.serialization.number()).optional(), 15 | index: core.serialization.number().optional(), 16 | }); 17 | 18 | export declare namespace MultimodalEmbedResponseDataItem { 19 | interface Raw { 20 | object?: string | null; 21 | embedding?: number[] | null; 22 | index?: number | null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/api/types/ContextualizedEmbedRequestInputType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | /** 6 | * Type of the input text. Defaults to `null`. Other options: `query`, `document`.
      • When `input_type` is `null`, the embedding model directly converts your input data into numerical vectors.
      • For retrieval/search purposes, we recommend specifying whether your inputs are intended as queries or documents by setting `input_type` to `query` or `document`, respectively.
      • When specified, Voyage prepends a specific prompt to your input before vectorizing it, helping the model create more effective vectors tailored for retrieval/search tasks.
      7 | */ 8 | export type ContextualizedEmbedRequestInputType = "query" | "document"; 9 | 10 | export const ContextualizedEmbedRequestInputType = { 11 | Query: "query", 12 | Document: "document", 13 | } as const; 14 | -------------------------------------------------------------------------------- /src/core/schemas/builders/primitives/number.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | 5 | export const number = createIdentitySchemaCreator( 6 | SchemaType.NUMBER, 7 | (value, { breadcrumbsPrefix = [] } = {}) => { 8 | if (typeof value === "number") { 9 | return { 10 | ok: true, 11 | value, 12 | }; 13 | } else { 14 | return { 15 | ok: false, 16 | errors: [ 17 | { 18 | path: breadcrumbsPrefix, 19 | message: getErrorMessageForIncorrectType(value, "number"), 20 | }, 21 | ], 22 | }; 23 | } 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /src/core/schemas/builders/primitives/string.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | 5 | export const string = createIdentitySchemaCreator( 6 | SchemaType.STRING, 7 | (value, { breadcrumbsPrefix = [] } = {}) => { 8 | if (typeof value === "string") { 9 | return { 10 | ok: true, 11 | value, 12 | }; 13 | } else { 14 | return { 15 | ok: false, 16 | errors: [ 17 | { 18 | path: breadcrumbsPrefix, 19 | message: getErrorMessageForIncorrectType(value, "string"), 20 | }, 21 | ], 22 | }; 23 | } 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /src/core/schemas/builders/primitives/boolean.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | 5 | export const boolean = createIdentitySchemaCreator( 6 | SchemaType.BOOLEAN, 7 | (value, { breadcrumbsPrefix = [] } = {}) => { 8 | if (typeof value === "boolean") { 9 | return { 10 | ok: true, 11 | value, 12 | }; 13 | } else { 14 | return { 15 | ok: false, 16 | errors: [ 17 | { 18 | path: breadcrumbsPrefix, 19 | message: getErrorMessageForIncorrectType(value, "boolean"), 20 | }, 21 | ], 22 | }; 23 | } 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /tests/unit/fetcher/Fetcher.test.ts: -------------------------------------------------------------------------------- 1 | import fetchMock from "fetch-mock-jest"; 2 | import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; 3 | 4 | describe("Test fetcherImpl", () => { 5 | it("should handle successful request", async () => { 6 | const mockArgs: Fetcher.Args = { 7 | url: "https://httpbin.org/post", 8 | method: "POST", 9 | headers: { "X-Test": "x-test-header" }, 10 | body: { data: "test" }, 11 | contentType: "application/json", 12 | requestType: "json", 13 | }; 14 | 15 | fetchMock.mock("https://httpbin.org/post", 200, { 16 | response: JSON.stringify({ data: "test" }), 17 | }); 18 | 19 | const result = await fetcherImpl(mockArgs); 20 | expect(result.ok).toBe(true); 21 | if (result.ok) { 22 | expect(result.body).toEqual({ data: "test" }); 23 | } 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/core/schemas/builders/lazy/lazyObject.ts: -------------------------------------------------------------------------------- 1 | import { getObjectUtils } from "../object"; 2 | import { getObjectLikeUtils } from "../object-like"; 3 | import { BaseObjectSchema, ObjectSchema } from "../object/types"; 4 | import { getSchemaUtils } from "../schema-utils"; 5 | import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; 6 | 7 | export function lazyObject(getter: SchemaGetter>): ObjectSchema { 8 | const baseSchema: BaseObjectSchema = { 9 | ...constructLazyBaseSchema(getter), 10 | _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), 11 | _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), 12 | }; 13 | 14 | return { 15 | ...baseSchema, 16 | ...getSchemaUtils(baseSchema), 17 | ...getObjectLikeUtils(baseSchema), 18 | ...getObjectUtils(baseSchema), 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/serialization/types/ContextualizedEmbedResponseDataItemDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const ContextualizedEmbedResponseDataItemDataItem: core.serialization.ObjectSchema< 10 | serializers.ContextualizedEmbedResponseDataItemDataItem.Raw, 11 | VoyageAI.ContextualizedEmbedResponseDataItemDataItem 12 | > = core.serialization.object({ 13 | object: core.serialization.string().optional(), 14 | embedding: core.serialization.list(core.serialization.number()).optional(), 15 | index: core.serialization.number().optional(), 16 | }); 17 | 18 | export declare namespace ContextualizedEmbedResponseDataItemDataItem { 19 | interface Raw { 20 | object?: string | null; 21 | embedding?: number[] | null; 22 | index?: number | null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/core/schemas/builders/object/objectWithoutOptionalProperties.ts: -------------------------------------------------------------------------------- 1 | import { object } from "./object"; 2 | import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; 3 | 4 | export function objectWithoutOptionalProperties>( 5 | schemas: T 6 | ): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { 7 | return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; 8 | } 9 | 10 | export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = 11 | ObjectSchema< 12 | inferRawObjectFromPropertySchemas, 13 | inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas 14 | >; 15 | 16 | export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { 17 | [K in keyof T]: inferParsedPropertySchema; 18 | }; 19 | -------------------------------------------------------------------------------- /tests/unit/zurg/date/date.test.ts: -------------------------------------------------------------------------------- 1 | import { date } from "../../../../src/core/schemas/builders/date"; 2 | import { itSchema } from "../utils/itSchema"; 3 | import { itValidateJson, itValidateParse } from "../utils/itValidate"; 4 | 5 | describe("date", () => { 6 | itSchema("converts between raw ISO string and parsed Date", date(), { 7 | raw: "2022-09-29T05:41:21.939Z", 8 | parsed: new Date("2022-09-29T05:41:21.939Z"), 9 | }); 10 | 11 | itValidateParse("non-string", date(), 42, [ 12 | { 13 | message: "Expected string. Received 42.", 14 | path: [], 15 | }, 16 | ]); 17 | 18 | itValidateParse("non-ISO", date(), "hello world", [ 19 | { 20 | message: 'Expected ISO 8601 date string. Received "hello world".', 21 | path: [], 22 | }, 23 | ]); 24 | 25 | itValidateJson("non-Date", date(), "hello", [ 26 | { 27 | message: 'Expected Date object. Received "hello".', 28 | path: [], 29 | }, 30 | ]); 31 | }); 32 | -------------------------------------------------------------------------------- /src/core/auth/BasicAuth.ts: -------------------------------------------------------------------------------- 1 | import { Base64 } from "js-base64"; 2 | 3 | export interface BasicAuth { 4 | username: string; 5 | password: string; 6 | } 7 | 8 | const BASIC_AUTH_HEADER_PREFIX = /^Basic /i; 9 | 10 | export const BasicAuth = { 11 | toAuthorizationHeader: (basicAuth: BasicAuth | undefined): string | undefined => { 12 | if (basicAuth == null) { 13 | return undefined; 14 | } 15 | const token = Base64.encode(`${basicAuth.username}:${basicAuth.password}`); 16 | return `Basic ${token}`; 17 | }, 18 | fromAuthorizationHeader: (header: string): BasicAuth => { 19 | const credentials = header.replace(BASIC_AUTH_HEADER_PREFIX, ""); 20 | const decoded = Base64.decode(credentials); 21 | const [username, password] = decoded.split(":", 2); 22 | 23 | if (username == null || password == null) { 24 | throw new Error("Invalid basic auth"); 25 | } 26 | return { 27 | username, 28 | password, 29 | }; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/api/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./EmbedRequestInput"; 2 | export * from "./EmbedRequestInputType"; 3 | export * from "./EmbedRequestOutputDtype"; 4 | export * from "./EmbedResponseDataItem"; 5 | export * from "./EmbedResponseUsage"; 6 | export * from "./EmbedResponse"; 7 | export * from "./RerankResponseDataItem"; 8 | export * from "./RerankResponseUsage"; 9 | export * from "./RerankResponse"; 10 | export * from "./MultimodalEmbedRequestInputsItemContentItem"; 11 | export * from "./MultimodalEmbedRequestInputsItem"; 12 | export * from "./MultimodalEmbedRequestInputType"; 13 | export * from "./MultimodalEmbedResponseDataItem"; 14 | export * from "./MultimodalEmbedResponseUsage"; 15 | export * from "./MultimodalEmbedResponse"; 16 | export * from "./ContextualizedEmbedRequestInputType"; 17 | export * from "./ContextualizedEmbedRequestOutputDtype"; 18 | export * from "./ContextualizedEmbedResponseDataItemDataItem"; 19 | export * from "./ContextualizedEmbedResponseDataItem"; 20 | export * from "./ContextualizedEmbedResponseUsage"; 21 | export * from "./ContextualizedEmbedResponse"; 22 | -------------------------------------------------------------------------------- /src/serialization/types/EmbedResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | import { EmbedResponseDataItem } from "./EmbedResponseDataItem"; 9 | import { EmbedResponseUsage } from "./EmbedResponseUsage"; 10 | 11 | export const EmbedResponse: core.serialization.ObjectSchema = 12 | core.serialization.object({ 13 | object: core.serialization.string().optional(), 14 | data: core.serialization.list(EmbedResponseDataItem).optional(), 15 | model: core.serialization.string().optional(), 16 | usage: EmbedResponseUsage.optional(), 17 | }); 18 | 19 | export declare namespace EmbedResponse { 20 | interface Raw { 21 | object?: string | null; 22 | data?: EmbedResponseDataItem.Raw[] | null; 23 | model?: string | null; 24 | usage?: EmbedResponseUsage.Raw | null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/serialization/types/RerankResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | import { RerankResponseDataItem } from "./RerankResponseDataItem"; 9 | import { RerankResponseUsage } from "./RerankResponseUsage"; 10 | 11 | export const RerankResponse: core.serialization.ObjectSchema = 12 | core.serialization.object({ 13 | object: core.serialization.string().optional(), 14 | data: core.serialization.list(RerankResponseDataItem).optional(), 15 | model: core.serialization.string().optional(), 16 | usage: RerankResponseUsage.optional(), 17 | }); 18 | 19 | export declare namespace RerankResponse { 20 | interface Raw { 21 | object?: string | null; 22 | data?: RerankResponseDataItem.Raw[] | null; 23 | model?: string | null; 24 | usage?: RerankResponseUsage.Raw | null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/serialization/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./EmbedRequestInput"; 2 | export * from "./EmbedRequestInputType"; 3 | export * from "./EmbedRequestOutputDtype"; 4 | export * from "./EmbedResponseDataItem"; 5 | export * from "./EmbedResponseUsage"; 6 | export * from "./EmbedResponse"; 7 | export * from "./RerankResponseDataItem"; 8 | export * from "./RerankResponseUsage"; 9 | export * from "./RerankResponse"; 10 | export * from "./MultimodalEmbedRequestInputsItemContentItem"; 11 | export * from "./MultimodalEmbedRequestInputsItem"; 12 | export * from "./MultimodalEmbedRequestInputType"; 13 | export * from "./MultimodalEmbedResponseDataItem"; 14 | export * from "./MultimodalEmbedResponseUsage"; 15 | export * from "./MultimodalEmbedResponse"; 16 | export * from "./ContextualizedEmbedRequestInputType"; 17 | export * from "./ContextualizedEmbedRequestOutputDtype"; 18 | export * from "./ContextualizedEmbedResponseDataItemDataItem"; 19 | export * from "./ContextualizedEmbedResponseDataItem"; 20 | export * from "./ContextualizedEmbedResponseUsage"; 21 | export * from "./ContextualizedEmbedResponse"; 22 | -------------------------------------------------------------------------------- /src/serialization/types/ContextualizedEmbedResponseDataItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | import { ContextualizedEmbedResponseDataItemDataItem } from "./ContextualizedEmbedResponseDataItemDataItem"; 9 | 10 | export const ContextualizedEmbedResponseDataItem: core.serialization.ObjectSchema< 11 | serializers.ContextualizedEmbedResponseDataItem.Raw, 12 | VoyageAI.ContextualizedEmbedResponseDataItem 13 | > = core.serialization.object({ 14 | object: core.serialization.string().optional(), 15 | data: core.serialization.list(ContextualizedEmbedResponseDataItemDataItem).optional(), 16 | index: core.serialization.number().optional(), 17 | }); 18 | 19 | export declare namespace ContextualizedEmbedResponseDataItem { 20 | interface Raw { 21 | object?: string | null; 22 | data?: ContextualizedEmbedResponseDataItemDataItem.Raw[] | null; 23 | index?: number | null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/core/schemas/builders/union/types.ts: -------------------------------------------------------------------------------- 1 | import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; 2 | import { Discriminant } from "./discriminant"; 3 | 4 | export type UnionSubtypes = { 5 | [K in DiscriminantValues]: ObjectSchema; 6 | }; 7 | 8 | export type inferRawUnion, U extends UnionSubtypes> = { 9 | [K in keyof U]: Record, K> & inferRawObject; 10 | }[keyof U]; 11 | 12 | export type inferParsedUnion, U extends UnionSubtypes> = { 13 | [K in keyof U]: Record, K> & inferParsedObject; 14 | }[keyof U]; 15 | 16 | export type inferRawDiscriminant> = D extends string 17 | ? D 18 | : D extends Discriminant 19 | ? Raw 20 | : never; 21 | 22 | export type inferParsedDiscriminant> = D extends string 23 | ? D 24 | : D extends Discriminant 25 | ? Parsed 26 | : never; 27 | -------------------------------------------------------------------------------- /src/core/schemas/builders/literals/stringLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Schema, SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | 5 | export function stringLiteral(literal: V): Schema { 6 | const schemaCreator = createIdentitySchemaCreator( 7 | SchemaType.STRING_LITERAL, 8 | (value, { breadcrumbsPrefix = [] } = {}) => { 9 | if (value === literal) { 10 | return { 11 | ok: true, 12 | value: literal, 13 | }; 14 | } else { 15 | return { 16 | ok: false, 17 | errors: [ 18 | { 19 | path: breadcrumbsPrefix, 20 | message: getErrorMessageForIncorrectType(value, `"${literal}"`), 21 | }, 22 | ], 23 | }; 24 | } 25 | } 26 | ); 27 | 28 | return schemaCreator(); 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/zurg/record/record.test.ts: -------------------------------------------------------------------------------- 1 | import { number, record, string } from "../../../../src/core/schemas/builders"; 2 | import { itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("record", () => { 6 | itSchemaIdentity(record(string(), string()), { hello: "world" }); 7 | itSchemaIdentity(record(number(), string()), { 42: "world" }); 8 | 9 | itValidate( 10 | "non-record", 11 | record(number(), string()), 12 | [], 13 | [ 14 | { 15 | path: [], 16 | message: "Expected object. Received list.", 17 | }, 18 | ] 19 | ); 20 | 21 | itValidate("invalid key type", record(number(), string()), { hello: "world" }, [ 22 | { 23 | path: ["hello (key)"], 24 | message: 'Expected number. Received "hello".', 25 | }, 26 | ]); 27 | 28 | itValidate("invalid value type", record(string(), number()), { hello: "world" }, [ 29 | { 30 | path: ["hello"], 31 | message: 'Expected number. Received "world".', 32 | }, 33 | ]); 34 | }); 35 | -------------------------------------------------------------------------------- /src/core/schemas/builders/literals/booleanLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Schema, SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | 5 | export function booleanLiteral(literal: V): Schema { 6 | const schemaCreator = createIdentitySchemaCreator( 7 | SchemaType.BOOLEAN_LITERAL, 8 | (value, { breadcrumbsPrefix = [] } = {}) => { 9 | if (value === literal) { 10 | return { 11 | ok: true, 12 | value: literal, 13 | }; 14 | } else { 15 | return { 16 | ok: false, 17 | errors: [ 18 | { 19 | path: breadcrumbsPrefix, 20 | message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), 21 | }, 22 | ], 23 | }; 24 | } 25 | } 26 | ); 27 | 28 | return schemaCreator(); 29 | } 30 | -------------------------------------------------------------------------------- /src/serialization/client/requests/RerankRequest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../../index"; 6 | import * as VoyageAI from "../../../api/index"; 7 | import * as core from "../../../core"; 8 | 9 | export const RerankRequest: core.serialization.Schema = 10 | core.serialization.object({ 11 | query: core.serialization.string(), 12 | documents: core.serialization.list(core.serialization.string()), 13 | model: core.serialization.string(), 14 | topK: core.serialization.property("top_k", core.serialization.number().optional()), 15 | returnDocuments: core.serialization.property("return_documents", core.serialization.boolean().optional()), 16 | truncation: core.serialization.boolean().optional(), 17 | }); 18 | 19 | export declare namespace RerankRequest { 20 | interface Raw { 21 | query: string; 22 | documents: string[]; 23 | model: string; 24 | top_k?: number | null; 25 | return_documents?: boolean | null; 26 | truncation?: boolean | null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/serialization/types/MultimodalEmbedResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | import { MultimodalEmbedResponseDataItem } from "./MultimodalEmbedResponseDataItem"; 9 | import { MultimodalEmbedResponseUsage } from "./MultimodalEmbedResponseUsage"; 10 | 11 | export const MultimodalEmbedResponse: core.serialization.ObjectSchema< 12 | serializers.MultimodalEmbedResponse.Raw, 13 | VoyageAI.MultimodalEmbedResponse 14 | > = core.serialization.object({ 15 | object: core.serialization.string().optional(), 16 | data: core.serialization.list(MultimodalEmbedResponseDataItem).optional(), 17 | model: core.serialization.string().optional(), 18 | usage: MultimodalEmbedResponseUsage.optional(), 19 | }); 20 | 21 | export declare namespace MultimodalEmbedResponse { 22 | interface Raw { 23 | object?: string | null; 24 | data?: MultimodalEmbedResponseDataItem.Raw[] | null; 25 | model?: string | null; 26 | usage?: MultimodalEmbedResponseUsage.Raw | null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2023 OpenAI (https://openai.com) 4 | 5 | Copyright (c) 2023 VoyageAI (https://voyageai.com) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /tests/unit/zurg/list/list.test.ts: -------------------------------------------------------------------------------- 1 | import { list, object, property, string } from "../../../../src/core/schemas/builders"; 2 | import { itSchema, itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("list", () => { 6 | itSchemaIdentity(list(string()), ["hello", "world"], { 7 | title: "functions as identity when item type is primitive", 8 | }); 9 | 10 | itSchema( 11 | "converts objects correctly", 12 | list( 13 | object({ 14 | helloWorld: property("hello_world", string()), 15 | }) 16 | ), 17 | { 18 | raw: [{ hello_world: "123" }], 19 | parsed: [{ helloWorld: "123" }], 20 | } 21 | ); 22 | 23 | itValidate("not a list", list(string()), 42, [ 24 | { 25 | path: [], 26 | message: "Expected list. Received 42.", 27 | }, 28 | ]); 29 | 30 | itValidate( 31 | "invalid item type", 32 | list(string()), 33 | [42], 34 | [ 35 | { 36 | path: ["[0]"], 37 | message: "Expected string. Received 42.", 38 | }, 39 | ] 40 | ); 41 | }); 42 | -------------------------------------------------------------------------------- /src/serialization/types/ContextualizedEmbedResponse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | import { ContextualizedEmbedResponseDataItem } from "./ContextualizedEmbedResponseDataItem"; 9 | import { ContextualizedEmbedResponseUsage } from "./ContextualizedEmbedResponseUsage"; 10 | 11 | export const ContextualizedEmbedResponse: core.serialization.ObjectSchema< 12 | serializers.ContextualizedEmbedResponse.Raw, 13 | VoyageAI.ContextualizedEmbedResponse 14 | > = core.serialization.object({ 15 | object: core.serialization.string().optional(), 16 | data: core.serialization.list(ContextualizedEmbedResponseDataItem).optional(), 17 | model: core.serialization.string().optional(), 18 | usage: ContextualizedEmbedResponseUsage.optional(), 19 | }); 20 | 21 | export declare namespace ContextualizedEmbedResponse { 22 | interface Raw { 23 | object?: string | null; 24 | data?: ContextualizedEmbedResponseDataItem.Raw[] | null; 25 | model?: string | null; 26 | usage?: ContextualizedEmbedResponseUsage.Raw | null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/errors/VoyageAIError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export class VoyageAIError extends Error { 6 | readonly statusCode?: number; 7 | readonly body?: unknown; 8 | 9 | constructor({ message, statusCode, body }: { message?: string; statusCode?: number; body?: unknown }) { 10 | super(buildMessage({ message, statusCode, body })); 11 | Object.setPrototypeOf(this, VoyageAIError.prototype); 12 | if (statusCode != null) { 13 | this.statusCode = statusCode; 14 | } 15 | 16 | if (body !== undefined) { 17 | this.body = body; 18 | } 19 | } 20 | } 21 | 22 | function buildMessage({ 23 | message, 24 | statusCode, 25 | body, 26 | }: { 27 | message: string | undefined; 28 | statusCode: number | undefined; 29 | body: unknown | undefined; 30 | }): string { 31 | let lines: string[] = []; 32 | if (message != null) { 33 | lines.push(message); 34 | } 35 | 36 | if (statusCode != null) { 37 | lines.push(`Status code: ${statusCode.toString()}`); 38 | } 39 | 40 | if (body != null) { 41 | lines.push(`Body: ${JSON.stringify(body, undefined, 2)}`); 42 | } 43 | 44 | return lines.join("\n"); 45 | } 46 | -------------------------------------------------------------------------------- /tests/unit/fetcher/stream-wrappers/webpack.test.ts: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | 3 | describe("test env compatibility", () => { 4 | test("webpack", () => { 5 | return new Promise((resolve, reject) => { 6 | webpack( 7 | { 8 | mode: "production", 9 | entry: "./src/index.ts", 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.tsx?$/, 14 | use: "ts-loader", 15 | exclude: /node_modules/, 16 | }, 17 | ], 18 | }, 19 | resolve: { 20 | extensions: [".tsx", ".ts", ".js"], 21 | }, 22 | }, 23 | (err, stats) => { 24 | try { 25 | expect(err).toBe(null); 26 | expect(stats?.hasErrors()).toBe(false); 27 | resolve(); 28 | } catch (error) { 29 | reject(error); 30 | } 31 | } 32 | ); 33 | }); 34 | }, 60_000); 35 | }); 36 | -------------------------------------------------------------------------------- /src/core/fetcher/getResponseBody.ts: -------------------------------------------------------------------------------- 1 | import { chooseStreamWrapper } from "./stream-wrappers/chooseStreamWrapper"; 2 | 3 | export async function getResponseBody(response: Response, responseType?: string): Promise { 4 | if (response.body != null && responseType === "blob") { 5 | return await response.blob(); 6 | } else if (response.body != null && responseType === "sse") { 7 | return response.body; 8 | } else if (response.body != null && responseType === "streaming") { 9 | return chooseStreamWrapper(response.body); 10 | } else if (response.body != null && responseType === "text") { 11 | return await response.text(); 12 | } else { 13 | const text = await response.text(); 14 | if (text.length > 0) { 15 | try { 16 | let responseBody = JSON.parse(text); 17 | return responseBody; 18 | } catch (err) { 19 | return { 20 | ok: false, 21 | error: { 22 | reason: "non-json", 23 | statusCode: response.status, 24 | rawBody: text, 25 | }, 26 | }; 27 | } 28 | } else { 29 | return undefined; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /reference.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 |
      client.contextualizedEmbed({ ...params }) -> VoyageAI.ContextualizedEmbedResponse 4 |
      5 |
      6 | 7 | #### 📝 Description 8 | 9 |
      10 |
      11 | 12 |
      13 |
      14 | 15 | The Voyage contextualized embeddings endpoint receives as input a list of documents (each document is a list of chunks), and returns contextualized embeddings for each chunk. The embeddings capture both the local chunk content and the global document context, making them particularly effective for retrieval tasks where understanding document-level context is important. 16 | 17 |
      18 |
      19 |
      20 |
      21 | 22 | #### 🔌 Usage 23 | 24 |
      25 |
      26 | 27 |
      28 |
      29 | 30 | ```typescript 31 | await client.contextualizedEmbed({ 32 | inputs: [["inputs"]], 33 | model: "model", 34 | }); 35 | ``` 36 | 37 |
      38 |
      39 |
      40 |
      41 | 42 | #### ⚙️ Parameters 43 | 44 |
      45 |
      46 | 47 |
      48 |
      49 | 50 | **request:** `VoyageAI.ContextualizedEmbedRequest` 51 | 52 |
      53 |
      54 | 55 |
      56 |
      57 | 58 | **requestOptions:** `VoyageAIClient.RequestOptions` 59 | 60 |
      61 |
      62 |
      63 |
      64 | 65 |
      66 |
      67 |
      68 | 69 | ## 70 | -------------------------------------------------------------------------------- /tests/unit/zurg/set/set.test.ts: -------------------------------------------------------------------------------- 1 | import { set, string } from "../../../../src/core/schemas/builders"; 2 | import { itSchema } from "../utils/itSchema"; 3 | import { itValidateJson, itValidateParse } from "../utils/itValidate"; 4 | 5 | describe("set", () => { 6 | itSchema("converts between raw list and parsed Set", set(string()), { 7 | raw: ["A", "B"], 8 | parsed: new Set(["A", "B"]), 9 | }); 10 | 11 | itValidateParse("not a list", set(string()), 42, [ 12 | { 13 | path: [], 14 | message: "Expected list. Received 42.", 15 | }, 16 | ]); 17 | 18 | itValidateJson( 19 | "not a Set", 20 | set(string()), 21 | [], 22 | [ 23 | { 24 | path: [], 25 | message: "Expected Set. Received list.", 26 | }, 27 | ] 28 | ); 29 | 30 | itValidateParse( 31 | "invalid item type", 32 | set(string()), 33 | [42], 34 | [ 35 | { 36 | path: ["[0]"], 37 | message: "Expected string. Received 42.", 38 | }, 39 | ] 40 | ); 41 | 42 | itValidateJson("invalid item type", set(string()), new Set([42]), [ 43 | { 44 | path: ["[0]"], 45 | message: "Expected string. Received 42.", 46 | }, 47 | ]); 48 | }); 49 | -------------------------------------------------------------------------------- /src/core/schemas/builders/lazy/lazy.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, Schema } from "../../Schema"; 2 | import { getSchemaUtils } from "../schema-utils"; 3 | 4 | export type SchemaGetter> = () => SchemaType; 5 | 6 | export function lazy(getter: SchemaGetter>): Schema { 7 | const baseSchema = constructLazyBaseSchema(getter); 8 | return { 9 | ...baseSchema, 10 | ...getSchemaUtils(baseSchema), 11 | }; 12 | } 13 | 14 | export function constructLazyBaseSchema( 15 | getter: SchemaGetter> 16 | ): BaseSchema { 17 | return { 18 | parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), 19 | json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), 20 | getType: () => getMemoizedSchema(getter).getType(), 21 | }; 22 | } 23 | 24 | type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; 25 | 26 | export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { 27 | const castedGetter = getter as MemoizedGetter; 28 | if (castedGetter.__zurg_memoized == null) { 29 | castedGetter.__zurg_memoized = getter(); 30 | } 31 | return castedGetter.__zurg_memoized; 32 | } 33 | -------------------------------------------------------------------------------- /src/core/fetcher/makeRequest.ts: -------------------------------------------------------------------------------- 1 | import { anySignal, getTimeoutSignal } from "./signals"; 2 | 3 | export const makeRequest = async ( 4 | fetchFn: (url: string, init: RequestInit) => Promise, 5 | url: string, 6 | method: string, 7 | headers: Record, 8 | requestBody: BodyInit | undefined, 9 | timeoutMs?: number, 10 | abortSignal?: AbortSignal, 11 | withCredentials?: boolean, 12 | duplex?: "half" 13 | ): Promise => { 14 | const signals: AbortSignal[] = []; 15 | 16 | // Add timeout signal 17 | let timeoutAbortId: NodeJS.Timeout | undefined = undefined; 18 | if (timeoutMs != null) { 19 | const { signal, abortId } = getTimeoutSignal(timeoutMs); 20 | timeoutAbortId = abortId; 21 | signals.push(signal); 22 | } 23 | 24 | // Add arbitrary signal 25 | if (abortSignal != null) { 26 | signals.push(abortSignal); 27 | } 28 | let newSignals = anySignal(signals); 29 | const response = await fetchFn(url, { 30 | method: method, 31 | headers, 32 | body: requestBody, 33 | signal: newSignals, 34 | credentials: withCredentials ? "include" : undefined, 35 | // @ts-ignore 36 | duplex, 37 | }); 38 | 39 | if (timeoutAbortId != null) { 40 | clearTimeout(timeoutAbortId); 41 | } 42 | 43 | return response; 44 | }; 45 | -------------------------------------------------------------------------------- /src/serialization/types/MultimodalEmbedRequestInputsItemContentItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../index"; 6 | import * as VoyageAI from "../../api/index"; 7 | import * as core from "../../core"; 8 | 9 | export const MultimodalEmbedRequestInputsItemContentItem: core.serialization.ObjectSchema< 10 | serializers.MultimodalEmbedRequestInputsItemContentItem.Raw, 11 | VoyageAI.MultimodalEmbedRequestInputsItemContentItem 12 | > = core.serialization.object({ 13 | type: core.serialization.string(), 14 | text: core.serialization.string().optional(), 15 | imageBase64: core.serialization.property("image_base64", core.serialization.string().optional()), 16 | imageUrl: core.serialization.property("image_url", core.serialization.string().optional()), 17 | videoBase64: core.serialization.property("video_base64", core.serialization.string().optional()), 18 | videoUrl: core.serialization.property("video_url", core.serialization.string().optional()), 19 | }); 20 | 21 | export declare namespace MultimodalEmbedRequestInputsItemContentItem { 22 | interface Raw { 23 | type: string; 24 | text?: string | null; 25 | image_base64?: string | null; 26 | image_url?: string | null; 27 | video_base64?: string | null; 28 | video_url?: string | null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voyageai", 3 | "version": "0.1.0", 4 | "private": false, 5 | "repository": "https://github.com/voyage-ai/typescript-sdk", 6 | "main": "./index.js", 7 | "types": "./index.d.ts", 8 | "scripts": { 9 | "format": "prettier . --write --ignore-unknown", 10 | "build": "tsc", 11 | "prepack": "cp -rv dist/. .", 12 | "test": "jest" 13 | }, 14 | "dependencies": { 15 | "url-join": "4.0.1", 16 | "form-data": "^4.0.0", 17 | "formdata-node": "^6.0.3", 18 | "node-fetch": "2.7.0", 19 | "qs": "6.11.2", 20 | "readable-stream": "^4.5.2", 21 | "js-base64": "3.7.2" 22 | }, 23 | "devDependencies": { 24 | "@types/url-join": "4.0.1", 25 | "@types/qs": "6.9.8", 26 | "@types/node-fetch": "2.6.9", 27 | "@types/readable-stream": "^4.0.15", 28 | "fetch-mock-jest": "^1.5.1", 29 | "webpack": "^5.94.0", 30 | "ts-loader": "^9.3.1", 31 | "jest": "29.7.0", 32 | "@types/jest": "29.5.5", 33 | "ts-jest": "29.1.1", 34 | "jest-environment-jsdom": "29.7.0", 35 | "@types/node": "17.0.33", 36 | "prettier": "2.7.1", 37 | "typescript": "4.6.4" 38 | }, 39 | "browser": { 40 | "fs": false, 41 | "os": false, 42 | "path": false 43 | }, 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /src/api/types/MultimodalEmbedRequestInputsItemContentItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | export interface MultimodalEmbedRequestInputsItemContentItem { 6 | /** Specifies the type of the piece of the content. Allowed values are `text`, `image_url`, `image_base64`, `video_url`, or `video_base64`. */ 7 | type: string; 8 | /** Only present when type is `text`. The value should be a text string. */ 9 | text?: string; 10 | /** Only present when type is `image_base64`. The value should be a Base64-encoded image in the data URL format `data:[];base64,`. Currently supported mediatypes are: `image/png`, `image/jpeg`, `image/webp`, and `image/gif`. */ 11 | imageBase64?: string; 12 | /** Only present when `type` is `image_url`. The value should be a URL linking to the image. We support PNG, JPEG, WEBP, and GIF images. */ 13 | imageUrl?: string; 14 | /** Only present when type is `video_base64`. The value should be a Base64-encoded video in the data URL format `data:[];base64,`. Currently supported mediatypes are: `video/mp4`. Currently, the `voyage-multimodal-3.5` model supports this input type. */ 15 | videoBase64?: string; 16 | /** Only present when `type` is `video_url`. The value should be a URL linking to the video. We support MP4 images. Currently, the `voyage-multimodal-3.5` model supports this input type. */ 17 | videoUrl?: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/serialization/client/requests/MultimodalEmbedRequest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../../index"; 6 | import * as VoyageAI from "../../../api/index"; 7 | import * as core from "../../../core"; 8 | import { MultimodalEmbedRequestInputsItem } from "../../types/MultimodalEmbedRequestInputsItem"; 9 | import { MultimodalEmbedRequestInputType } from "../../types/MultimodalEmbedRequestInputType"; 10 | 11 | export const MultimodalEmbedRequest: core.serialization.Schema< 12 | serializers.MultimodalEmbedRequest.Raw, 13 | VoyageAI.MultimodalEmbedRequest 14 | > = core.serialization.object({ 15 | inputs: core.serialization.list(MultimodalEmbedRequestInputsItem), 16 | model: core.serialization.string(), 17 | inputType: core.serialization.property("input_type", MultimodalEmbedRequestInputType.optional()), 18 | truncation: core.serialization.boolean().optional(), 19 | encodingFormat: core.serialization.property( 20 | "encoding_format", 21 | core.serialization.stringLiteral("base64").optional() 22 | ), 23 | }); 24 | 25 | export declare namespace MultimodalEmbedRequest { 26 | interface Raw { 27 | inputs: MultimodalEmbedRequestInputsItem.Raw[]; 28 | model: string; 29 | input_type?: MultimodalEmbedRequestInputType.Raw | null; 30 | truncation?: boolean | null; 31 | encoding_format?: "base64" | null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/core/fetcher/signals.ts: -------------------------------------------------------------------------------- 1 | const TIMEOUT = "timeout"; 2 | 3 | export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: NodeJS.Timeout } { 4 | const controller = new AbortController(); 5 | const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); 6 | return { signal: controller.signal, abortId }; 7 | } 8 | 9 | /** 10 | * Returns an abort signal that is getting aborted when 11 | * at least one of the specified abort signals is aborted. 12 | * 13 | * Requires at least node.js 18. 14 | */ 15 | export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { 16 | // Allowing signals to be passed either as array 17 | // of signals or as multiple arguments. 18 | const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args); 19 | 20 | const controller = new AbortController(); 21 | 22 | for (const signal of signals) { 23 | if (signal.aborted) { 24 | // Exiting early if one of the signals 25 | // is already aborted. 26 | controller.abort((signal as any)?.reason); 27 | break; 28 | } 29 | 30 | // Listening for signals and removing the listeners 31 | // when at least one symbol is aborted. 32 | signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { 33 | signal: controller.signal, 34 | }); 35 | } 36 | 37 | return controller.signal; 38 | } 39 | -------------------------------------------------------------------------------- /src/core/schemas/utils/maybeSkipValidation.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; 2 | 3 | export function maybeSkipValidation, Raw, Parsed>(schema: S): S { 4 | return { 5 | ...schema, 6 | json: transformAndMaybeSkipValidation(schema.json), 7 | parse: transformAndMaybeSkipValidation(schema.parse), 8 | }; 9 | } 10 | 11 | function transformAndMaybeSkipValidation( 12 | transform: (value: unknown, opts?: SchemaOptions) => MaybeValid 13 | ): (value: unknown, opts?: SchemaOptions) => MaybeValid { 14 | return (value, opts): MaybeValid => { 15 | const transformed = transform(value, opts); 16 | const { skipValidation = false } = opts ?? {}; 17 | if (!transformed.ok && skipValidation) { 18 | // eslint-disable-next-line no-console 19 | console.warn( 20 | [ 21 | "Failed to validate.", 22 | ...transformed.errors.map( 23 | (error) => 24 | " - " + 25 | (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) 26 | ), 27 | ].join("\n") 28 | ); 29 | 30 | return { 31 | ok: true, 32 | value: value as T, 33 | }; 34 | } else { 35 | return transformed; 36 | } 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/serialization/client/requests/ContextualizedEmbedRequest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../../index"; 6 | import * as VoyageAI from "../../../api/index"; 7 | import * as core from "../../../core"; 8 | import { ContextualizedEmbedRequestInputType } from "../../types/ContextualizedEmbedRequestInputType"; 9 | import { ContextualizedEmbedRequestOutputDtype } from "../../types/ContextualizedEmbedRequestOutputDtype"; 10 | 11 | export const ContextualizedEmbedRequest: core.serialization.Schema< 12 | serializers.ContextualizedEmbedRequest.Raw, 13 | VoyageAI.ContextualizedEmbedRequest 14 | > = core.serialization.object({ 15 | inputs: core.serialization.list(core.serialization.list(core.serialization.string())), 16 | model: core.serialization.string(), 17 | inputType: core.serialization.property("input_type", ContextualizedEmbedRequestInputType.optional()), 18 | outputDimension: core.serialization.property("output_dimension", core.serialization.number().optional()), 19 | outputDtype: core.serialization.property("output_dtype", ContextualizedEmbedRequestOutputDtype.optional()), 20 | }); 21 | 22 | export declare namespace ContextualizedEmbedRequest { 23 | interface Raw { 24 | inputs: string[][]; 25 | model: string; 26 | input_type?: ContextualizedEmbedRequestInputType.Raw | null; 27 | output_dimension?: number | null; 28 | output_dtype?: ContextualizedEmbedRequestOutputDtype.Raw | null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/zurg/skipValidation.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { boolean, number, object, property, string, undiscriminatedUnion } from "../../../src/core/schemas/builders"; 4 | 5 | describe("skipValidation", () => { 6 | it("allows data that doesn't conform to the schema", async () => { 7 | const warningLogs: string[] = []; 8 | const originalConsoleWarn = console.warn; 9 | console.warn = (...args) => warningLogs.push(args.join(" ")); 10 | 11 | const schema = object({ 12 | camelCase: property("snake_case", string()), 13 | numberProperty: number(), 14 | requiredProperty: boolean(), 15 | anyPrimitive: undiscriminatedUnion([string(), number(), boolean()]), 16 | }); 17 | 18 | const parsed = await schema.parse( 19 | { 20 | snake_case: "hello", 21 | numberProperty: "oops", 22 | anyPrimitive: true, 23 | }, 24 | { 25 | skipValidation: true, 26 | } 27 | ); 28 | 29 | expect(parsed).toEqual({ 30 | ok: true, 31 | value: { 32 | camelCase: "hello", 33 | numberProperty: "oops", 34 | anyPrimitive: true, 35 | }, 36 | }); 37 | 38 | expect(warningLogs).toEqual([ 39 | `Failed to validate. 40 | - numberProperty: Expected number. Received "oops".`, 41 | ]); 42 | 43 | console.warn = originalConsoleWarn; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts: -------------------------------------------------------------------------------- 1 | import type { Readable } from "readable-stream"; 2 | import { RUNTIME } from "../../runtime"; 3 | 4 | export type EventCallback = (data?: any) => void; 5 | 6 | export interface StreamWrapper { 7 | setEncoding(encoding?: string): void; 8 | on(event: string, callback: EventCallback): void; 9 | off(event: string, callback: EventCallback): void; 10 | pipe(dest: WritableStream): WritableStream; 11 | pipeTo(dest: WritableStream): WritableStream; 12 | unpipe(dest?: WritableStream): void; 13 | destroy(error?: Error): void; 14 | pause(): void; 15 | resume(): void; 16 | get isPaused(): boolean; 17 | read(): Promise; 18 | text(): Promise; 19 | json(): Promise; 20 | [Symbol.asyncIterator](): AsyncIterableIterator; 21 | } 22 | 23 | export async function chooseStreamWrapper(responseBody: any): Promise>> { 24 | if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { 25 | return new (await import("./Node18UniversalStreamWrapper")).Node18UniversalStreamWrapper( 26 | responseBody as ReadableStream 27 | ); 28 | } else if (RUNTIME.type !== "node" && typeof fetch === "function") { 29 | return new (await import("./UndiciStreamWrapper")).UndiciStreamWrapper(responseBody as ReadableStream); 30 | } else { 31 | return new (await import("./NodePre18StreamWrapper")).NodePre18StreamWrapper(responseBody as Readable); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/core/schemas/builders/enum/enum.ts: -------------------------------------------------------------------------------- 1 | import { Schema, SchemaType } from "../../Schema"; 2 | import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | 5 | export function enum_(values: E): Schema { 6 | const validValues = new Set(values); 7 | 8 | const schemaCreator = createIdentitySchemaCreator( 9 | SchemaType.ENUM, 10 | (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { 11 | if (typeof value !== "string") { 12 | return { 13 | ok: false, 14 | errors: [ 15 | { 16 | path: breadcrumbsPrefix, 17 | message: getErrorMessageForIncorrectType(value, "string"), 18 | }, 19 | ], 20 | }; 21 | } 22 | 23 | if (!validValues.has(value) && !allowUnrecognizedEnumValues) { 24 | return { 25 | ok: false, 26 | errors: [ 27 | { 28 | path: breadcrumbsPrefix, 29 | message: getErrorMessageForIncorrectType(value, "enum"), 30 | }, 31 | ], 32 | }; 33 | } 34 | 35 | return { 36 | ok: true, 37 | value: value as U, 38 | }; 39 | } 40 | ); 41 | 42 | return schemaCreator(); 43 | } 44 | -------------------------------------------------------------------------------- /src/core/schemas/builders/set/set.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, Schema, SchemaType } from "../../Schema"; 2 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 3 | import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; 4 | import { list } from "../list"; 5 | import { getSchemaUtils } from "../schema-utils"; 6 | 7 | export function set(schema: Schema): Schema> { 8 | const listSchema = list(schema); 9 | const baseSchema: BaseSchema> = { 10 | parse: (raw, opts) => { 11 | const parsedList = listSchema.parse(raw, opts); 12 | if (parsedList.ok) { 13 | return { 14 | ok: true, 15 | value: new Set(parsedList.value), 16 | }; 17 | } else { 18 | return parsedList; 19 | } 20 | }, 21 | json: (parsed, opts) => { 22 | if (!(parsed instanceof Set)) { 23 | return { 24 | ok: false, 25 | errors: [ 26 | { 27 | path: opts?.breadcrumbsPrefix ?? [], 28 | message: getErrorMessageForIncorrectType(parsed, "Set"), 29 | }, 30 | ], 31 | }; 32 | } 33 | const jsonList = listSchema.json([...parsed], opts); 34 | return jsonList; 35 | }, 36 | getType: () => SchemaType.SET, 37 | }; 38 | 39 | return { 40 | ...maybeSkipValidation(baseSchema), 41 | ...getSchemaUtils(baseSchema), 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/serialization/client/requests/EmbedRequest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as serializers from "../../index"; 6 | import * as VoyageAI from "../../../api/index"; 7 | import * as core from "../../../core"; 8 | import { EmbedRequestInput } from "../../types/EmbedRequestInput"; 9 | import { EmbedRequestInputType } from "../../types/EmbedRequestInputType"; 10 | import { EmbedRequestOutputDtype } from "../../types/EmbedRequestOutputDtype"; 11 | 12 | export const EmbedRequest: core.serialization.Schema = 13 | core.serialization.object({ 14 | input: EmbedRequestInput, 15 | model: core.serialization.string(), 16 | inputType: core.serialization.property("input_type", EmbedRequestInputType.optional()), 17 | truncation: core.serialization.boolean().optional(), 18 | encodingFormat: core.serialization.property( 19 | "encoding_format", 20 | core.serialization.stringLiteral("base64").optional() 21 | ), 22 | outputDimension: core.serialization.property("output_dimension", core.serialization.number().optional()), 23 | outputDtype: core.serialization.property("output_dtype", EmbedRequestOutputDtype.optional()), 24 | }); 25 | 26 | export declare namespace EmbedRequest { 27 | interface Raw { 28 | input: EmbedRequestInput.Raw; 29 | model: string; 30 | input_type?: EmbedRequestInputType.Raw | null; 31 | truncation?: boolean | null; 32 | encoding_format?: "base64" | null; 33 | output_dimension?: number | null; 34 | output_dtype?: EmbedRequestOutputDtype.Raw | null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts: -------------------------------------------------------------------------------- 1 | import { number, object, property, string, undiscriminatedUnion } from "../../../../src/core/schemas/builders"; 2 | import { itSchema, itSchemaIdentity } from "../utils/itSchema"; 3 | 4 | describe("undiscriminatedUnion", () => { 5 | itSchemaIdentity(undiscriminatedUnion([string(), number()]), "hello world"); 6 | 7 | itSchemaIdentity(undiscriminatedUnion([object({ hello: string() }), object({ goodbye: string() })]), { 8 | goodbye: "foo", 9 | }); 10 | 11 | itSchema( 12 | "Correctly transforms", 13 | undiscriminatedUnion([object({ hello: string() }), object({ helloWorld: property("hello_world", string()) })]), 14 | { 15 | raw: { hello_world: "foo " }, 16 | parsed: { helloWorld: "foo " }, 17 | } 18 | ); 19 | 20 | it("Returns errors for all variants", async () => { 21 | const result = await undiscriminatedUnion([string(), number()]).parse(true); 22 | if (result.ok) { 23 | throw new Error("Unexpectedly passed validation"); 24 | } 25 | expect(result.errors).toEqual([ 26 | { 27 | message: "[Variant 0] Expected string. Received true.", 28 | path: [], 29 | }, 30 | { 31 | message: "[Variant 1] Expected number. Received true.", 32 | path: [], 33 | }, 34 | ]); 35 | }); 36 | 37 | describe("compile", () => { 38 | // eslint-disable-next-line jest/expect-expect 39 | it("doesn't compile with zero members", () => { 40 | // @ts-expect-error 41 | () => undiscriminatedUnion([]); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/unit/zurg/lazy/lazy.test.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from "../../../../src/core/schemas/Schema"; 2 | import { lazy, list, object, string } from "../../../../src/core/schemas/builders"; 3 | import { itSchemaIdentity } from "../utils/itSchema"; 4 | 5 | describe("lazy", () => { 6 | it("doesn't run immediately", () => { 7 | let wasRun = false; 8 | lazy(() => { 9 | wasRun = true; 10 | return string(); 11 | }); 12 | expect(wasRun).toBe(false); 13 | }); 14 | 15 | it("only runs first time", async () => { 16 | let count = 0; 17 | const schema = lazy(() => { 18 | count++; 19 | return string(); 20 | }); 21 | await schema.parse("hello"); 22 | await schema.json("world"); 23 | expect(count).toBe(1); 24 | }); 25 | 26 | itSchemaIdentity( 27 | lazy(() => object({})), 28 | { foo: "hello" }, 29 | { 30 | title: "passes opts through", 31 | opts: { unrecognizedObjectKeys: "passthrough" }, 32 | } 33 | ); 34 | 35 | itSchemaIdentity( 36 | lazy(() => object({ foo: string() })), 37 | { foo: "hello" } 38 | ); 39 | 40 | // eslint-disable-next-line jest/expect-expect 41 | it("self-referencial schema doesn't compile", () => { 42 | () => { 43 | // @ts-expect-error 44 | const a = lazy(() => object({ foo: a })); 45 | }; 46 | }); 47 | 48 | // eslint-disable-next-line jest/expect-expect 49 | it("self-referencial compiles with explicit type", () => { 50 | () => { 51 | interface TreeNode { 52 | children: TreeNode[]; 53 | } 54 | const TreeNode: Schema = lazy(() => object({ children: list(TreeNode) })); 55 | }; 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /tests/unit/zurg/utils/itValidate.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable jest/no-export */ 2 | import { Schema, SchemaOptions, ValidationError } from "../../../../src/core/schemas/Schema"; 3 | 4 | export function itValidate( 5 | title: string, 6 | schema: Schema, 7 | input: unknown, 8 | errors: ValidationError[], 9 | opts?: SchemaOptions 10 | ): void { 11 | // eslint-disable-next-line jest/valid-title 12 | describe("parse()", () => { 13 | itValidateParse(title, schema, input, errors, opts); 14 | }); 15 | describe("json()", () => { 16 | itValidateJson(title, schema, input, errors, opts); 17 | }); 18 | } 19 | 20 | export function itValidateParse( 21 | title: string, 22 | schema: Schema, 23 | raw: unknown, 24 | errors: ValidationError[], 25 | opts?: SchemaOptions 26 | ): void { 27 | describe("parse", () => { 28 | // eslint-disable-next-line jest/valid-title 29 | it(title, async () => { 30 | const maybeValid = await schema.parse(raw, opts); 31 | if (maybeValid.ok) { 32 | throw new Error("Value passed validation"); 33 | } 34 | expect(maybeValid.errors).toStrictEqual(errors); 35 | }); 36 | }); 37 | } 38 | 39 | export function itValidateJson( 40 | title: string, 41 | schema: Schema, 42 | parsed: unknown, 43 | errors: ValidationError[], 44 | opts?: SchemaOptions 45 | ): void { 46 | describe("json", () => { 47 | // eslint-disable-next-line jest/valid-title 48 | it(title, async () => { 49 | const maybeValid = await schema.json(parsed, opts); 50 | if (maybeValid.ok) { 51 | throw new Error("Value passed validation"); 52 | } 53 | expect(maybeValid.errors).toStrictEqual(errors); 54 | }); 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /src/api/client/requests/ContextualizedEmbedRequest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../../index"; 6 | 7 | /** 8 | * @example 9 | * { 10 | * inputs: [["inputs"]], 11 | * model: "model" 12 | * } 13 | */ 14 | export interface ContextualizedEmbedRequest { 15 | /** 16 | * A list of documents, where each document is represented as a list of text chunks (strings).
      • The maximum number of inputs (documents) is 1,000.
      • The total number of chunks across all documents cannot exceed 16,000.
      • The total number of tokens across all inputs cannot exceed 120,000.
      17 | * 18 | */ 19 | inputs: string[][]; 20 | /** 21 | * Name of the model. Currently, the recommended model is `voyage-context-3`, which has a context length of 32,000 tokens and supports output dimensions of 256, 512, 1024, and 2048. 22 | * 23 | */ 24 | model: string; 25 | /** 26 | * Type of the input text. Defaults to `null`. Other options: `query`, `document`.
      • When `input_type` is `null`, the embedding model directly converts your input data into numerical vectors.
      • For retrieval/search purposes, we recommend specifying whether your inputs are intended as queries or documents by setting `input_type` to `query` or `document`, respectively.
      • When specified, Voyage prepends a specific prompt to your input before vectorizing it, helping the model create more effective vectors tailored for retrieval/search tasks.
      27 | * 28 | */ 29 | inputType?: VoyageAI.ContextualizedEmbedRequestInputType; 30 | /** 31 | * The number of dimensions for resulting output embeddings. Defaults to 1024 for `voyage-context-3`. Supported dimensions: 256, 512, 1024, 2048. 32 | * 33 | */ 34 | outputDimension?: number; 35 | /** 36 | * The data type for the embeddings to be returned. Defaults to `float`. Other options: `int8`, `uint8`, `binary`, `ubinary`. 37 | * 38 | */ 39 | outputDtype?: VoyageAI.ContextualizedEmbedRequestOutputDtype; 40 | } 41 | -------------------------------------------------------------------------------- /tests/unit/zurg/object-like/withParsedProperties.test.ts: -------------------------------------------------------------------------------- 1 | import { object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; 2 | 3 | describe("withParsedProperties", () => { 4 | it("Added properties included on parsed object", async () => { 5 | const schema = object({ 6 | foo: property("raw_foo", string()), 7 | bar: stringLiteral("bar"), 8 | }).withParsedProperties({ 9 | printFoo: (parsed) => () => parsed.foo, 10 | printHelloWorld: () => () => "Hello world", 11 | helloWorld: "Hello world", 12 | }); 13 | 14 | const parsed = await schema.parse({ raw_foo: "value of foo", bar: "bar" }); 15 | if (!parsed.ok) { 16 | throw new Error("Failed to parse"); 17 | } 18 | expect(parsed.value.printFoo()).toBe("value of foo"); 19 | expect(parsed.value.printHelloWorld()).toBe("Hello world"); 20 | expect(parsed.value.helloWorld).toBe("Hello world"); 21 | }); 22 | 23 | it("Added property is removed on raw object", async () => { 24 | const schema = object({ 25 | foo: property("raw_foo", string()), 26 | bar: stringLiteral("bar"), 27 | }).withParsedProperties({ 28 | printFoo: (parsed) => () => parsed.foo, 29 | }); 30 | 31 | const original = { raw_foo: "value of foo", bar: "bar" } as const; 32 | const parsed = await schema.parse(original); 33 | if (!parsed.ok) { 34 | throw new Error("Failed to parse()"); 35 | } 36 | 37 | const raw = await schema.json(parsed.value); 38 | 39 | if (!raw.ok) { 40 | throw new Error("Failed to json()"); 41 | } 42 | 43 | expect(raw.value).toEqual(original); 44 | }); 45 | 46 | describe("compile", () => { 47 | // eslint-disable-next-line jest/expect-expect 48 | it("doesn't compile with non-object schema", () => { 49 | () => 50 | object({ 51 | foo: string(), 52 | }) 53 | // @ts-expect-error 54 | .withParsedProperties(42); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIME } from "../../../../src/core/runtime"; 2 | import { chooseStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; 3 | import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; 4 | import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; 5 | import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; 6 | 7 | describe("chooseStreamWrapper", () => { 8 | beforeEach(() => { 9 | RUNTIME.type = "unknown"; 10 | RUNTIME.parsedVersion = 0; 11 | }); 12 | 13 | it('should return a Node18UniversalStreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is greater than or equal to 18', async () => { 14 | const expected = new Node18UniversalStreamWrapper(new ReadableStream()); 15 | RUNTIME.type = "node"; 16 | RUNTIME.parsedVersion = 18; 17 | 18 | const result = await chooseStreamWrapper(new ReadableStream()); 19 | 20 | expect(JSON.stringify(result)).toBe(JSON.stringify(expected)); 21 | }); 22 | 23 | it('should return a NodePre18StreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is less than 18', async () => { 24 | const stream = await import("readable-stream"); 25 | const expected = new NodePre18StreamWrapper(new stream.Readable()); 26 | 27 | RUNTIME.type = "node"; 28 | RUNTIME.parsedVersion = 16; 29 | 30 | const result = await chooseStreamWrapper(new stream.Readable()); 31 | 32 | expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); 33 | }); 34 | 35 | it('should return a Undici when RUNTIME.type is not "node"', async () => { 36 | const expected = new UndiciStreamWrapper(new ReadableStream()); 37 | RUNTIME.type = "browser"; 38 | 39 | const result = await chooseStreamWrapper(new ReadableStream()); 40 | 41 | expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/unit/fetcher/createRequestUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; 2 | 3 | describe("Test createRequestUrl", () => { 4 | it("should return the base URL when no query parameters are provided", () => { 5 | const baseUrl = "https://api.example.com"; 6 | expect(createRequestUrl(baseUrl)).toBe(baseUrl); 7 | }); 8 | 9 | it("should append simple query parameters", () => { 10 | const baseUrl = "https://api.example.com"; 11 | const queryParams = { key: "value", another: "param" }; 12 | expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?key=value&another=param"); 13 | }); 14 | 15 | it("should handle array query parameters", () => { 16 | const baseUrl = "https://api.example.com"; 17 | const queryParams = { items: ["a", "b", "c"] }; 18 | expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?items=a&items=b&items=c"); 19 | }); 20 | 21 | it("should handle object query parameters", () => { 22 | const baseUrl = "https://api.example.com"; 23 | const queryParams = { filter: { name: "John", age: 30 } }; 24 | expect(createRequestUrl(baseUrl, queryParams)).toBe( 25 | "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30" 26 | ); 27 | }); 28 | 29 | it("should handle mixed types of query parameters", () => { 30 | const baseUrl = "https://api.example.com"; 31 | const queryParams = { 32 | simple: "value", 33 | array: ["x", "y"], 34 | object: { key: "value" }, 35 | }; 36 | expect(createRequestUrl(baseUrl, queryParams)).toBe( 37 | "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value" 38 | ); 39 | }); 40 | 41 | it("should handle empty query parameters object", () => { 42 | const baseUrl = "https://api.example.com"; 43 | expect(createRequestUrl(baseUrl, {})).toBe(baseUrl); 44 | }); 45 | 46 | it("should encode special characters in query parameters", () => { 47 | const baseUrl = "https://api.example.com"; 48 | const queryParams = { special: "a&b=c d" }; 49 | expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?special=a%26b%3Dc%20d"); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/unit/zurg/utils/itSchema.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable jest/no-export */ 2 | import { Schema, SchemaOptions } from "../../../../src/core/schemas/Schema"; 3 | 4 | export function itSchemaIdentity( 5 | schema: Schema, 6 | value: T, 7 | { title = "functions as identity", opts }: { title?: string; opts?: SchemaOptions } = {} 8 | ): void { 9 | itSchema(title, schema, { raw: value, parsed: value, opts }); 10 | } 11 | 12 | export function itSchema( 13 | title: string, 14 | schema: Schema, 15 | { 16 | raw, 17 | parsed, 18 | opts, 19 | only = false, 20 | }: { 21 | raw: Raw; 22 | parsed: Parsed; 23 | opts?: SchemaOptions; 24 | only?: boolean; 25 | } 26 | ): void { 27 | // eslint-disable-next-line jest/valid-title 28 | (only ? describe.only : describe)(title, () => { 29 | itParse("parse()", schema, { raw, parsed, opts }); 30 | itJson("json()", schema, { raw, parsed, opts }); 31 | }); 32 | } 33 | 34 | export function itParse( 35 | title: string, 36 | schema: Schema, 37 | { 38 | raw, 39 | parsed, 40 | opts, 41 | }: { 42 | raw: Raw; 43 | parsed: Parsed; 44 | opts?: SchemaOptions; 45 | } 46 | ): void { 47 | // eslint-disable-next-line jest/valid-title 48 | it(title, () => { 49 | const maybeValid = schema.parse(raw, opts); 50 | if (!maybeValid.ok) { 51 | throw new Error("Failed to parse() " + JSON.stringify(maybeValid.errors, undefined, 4)); 52 | } 53 | expect(maybeValid.value).toStrictEqual(parsed); 54 | }); 55 | } 56 | 57 | export function itJson( 58 | title: string, 59 | schema: Schema, 60 | { 61 | raw, 62 | parsed, 63 | opts, 64 | }: { 65 | raw: Raw; 66 | parsed: Parsed; 67 | opts?: SchemaOptions; 68 | } 69 | ): void { 70 | // eslint-disable-next-line jest/valid-title 71 | it(title, () => { 72 | const maybeValid = schema.json(parsed, opts); 73 | if (!maybeValid.ok) { 74 | throw new Error("Failed to json() " + JSON.stringify(maybeValid.errors, undefined, 4)); 75 | } 76 | expect(maybeValid.value).toStrictEqual(raw); 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; 2 | import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; 3 | import { getSchemaUtils } from "../schema-utils"; 4 | import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; 5 | 6 | export function undiscriminatedUnion, ...Schema[]]>( 7 | schemas: Schemas 8 | ): Schema, inferParsedUnidiscriminatedUnionSchema> { 9 | const baseSchema: BaseSchema< 10 | inferRawUnidiscriminatedUnionSchema, 11 | inferParsedUnidiscriminatedUnionSchema 12 | > = { 13 | parse: (raw, opts) => { 14 | return validateAndTransformUndiscriminatedUnion>( 15 | (schema, opts) => schema.parse(raw, opts), 16 | schemas, 17 | opts 18 | ); 19 | }, 20 | json: (parsed, opts) => { 21 | return validateAndTransformUndiscriminatedUnion>( 22 | (schema, opts) => schema.json(parsed, opts), 23 | schemas, 24 | opts 25 | ); 26 | }, 27 | getType: () => SchemaType.UNDISCRIMINATED_UNION, 28 | }; 29 | 30 | return { 31 | ...maybeSkipValidation(baseSchema), 32 | ...getSchemaUtils(baseSchema), 33 | }; 34 | } 35 | 36 | function validateAndTransformUndiscriminatedUnion( 37 | transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, 38 | schemas: Schema[], 39 | opts: SchemaOptions | undefined 40 | ): MaybeValid { 41 | const errors: ValidationError[] = []; 42 | for (const [index, schema] of schemas.entries()) { 43 | const transformed = transform(schema, { ...opts, skipValidation: false }); 44 | if (transformed.ok) { 45 | return transformed; 46 | } else { 47 | for (const error of transformed.errors) { 48 | errors.push({ 49 | path: error.path, 50 | message: `[Variant ${index}] ${error.message}`, 51 | }); 52 | } 53 | } 54 | } 55 | 56 | return { 57 | ok: false, 58 | errors, 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /tests/unit/zurg/schema.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | boolean, 3 | discriminant, 4 | list, 5 | number, 6 | object, 7 | string, 8 | stringLiteral, 9 | union, 10 | } from "../../../src/core/schemas/builders"; 11 | import { booleanLiteral } from "../../../src/core/schemas/builders/literals/booleanLiteral"; 12 | import { property } from "../../../src/core/schemas/builders/object/property"; 13 | import { itSchema } from "./utils/itSchema"; 14 | 15 | describe("Schema", () => { 16 | itSchema( 17 | "large nested object", 18 | object({ 19 | a: string(), 20 | b: stringLiteral("b value"), 21 | c: property( 22 | "raw_c", 23 | list( 24 | object({ 25 | animal: union(discriminant("type", "_type"), { 26 | dog: object({ value: boolean() }), 27 | cat: object({ value: property("raw_cat", number()) }), 28 | }), 29 | }) 30 | ) 31 | ), 32 | d: property("raw_d", boolean()), 33 | e: booleanLiteral(true), 34 | }), 35 | { 36 | raw: { 37 | a: "hello", 38 | b: "b value", 39 | raw_c: [ 40 | { 41 | animal: { 42 | _type: "dog", 43 | value: true, 44 | }, 45 | }, 46 | { 47 | animal: { 48 | _type: "cat", 49 | raw_cat: 42, 50 | }, 51 | }, 52 | ], 53 | raw_d: false, 54 | e: true, 55 | }, 56 | parsed: { 57 | a: "hello", 58 | b: "b value", 59 | c: [ 60 | { 61 | animal: { 62 | type: "dog", 63 | value: true, 64 | }, 65 | }, 66 | { 67 | animal: { 68 | type: "cat", 69 | value: 42, 70 | }, 71 | }, 72 | ], 73 | d: false, 74 | e: true, 75 | }, 76 | } 77 | ); 78 | }); 79 | -------------------------------------------------------------------------------- /tests/unit/fetcher/makeRequest.test.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIME } from "../../../src/core/runtime"; 2 | import { makeRequest } from "../../../src/core/fetcher/makeRequest"; 3 | 4 | if (RUNTIME.type === "browser") { 5 | require("jest-fetch-mock").enableMocks(); 6 | } 7 | 8 | describe("Test makeRequest", () => { 9 | const mockPostUrl = "https://httpbin.org/post"; 10 | const mockGetUrl = "https://httpbin.org/get"; 11 | const mockHeaders = { "Content-Type": "application/json" }; 12 | const mockBody = JSON.stringify({ key: "value" }); 13 | 14 | let mockFetch: jest.Mock; 15 | 16 | beforeEach(() => { 17 | mockFetch = jest.fn(); 18 | mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); 19 | }); 20 | 21 | it("should handle POST request correctly", async () => { 22 | const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); 23 | const responseBody = await response.json(); 24 | expect(responseBody).toEqual({ test: "successful" }); 25 | expect(mockFetch).toHaveBeenCalledTimes(1); 26 | const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; 27 | expect(calledUrl).toBe(mockPostUrl); 28 | expect(calledOptions).toEqual( 29 | expect.objectContaining({ 30 | method: "POST", 31 | headers: mockHeaders, 32 | body: mockBody, 33 | credentials: undefined, 34 | }) 35 | ); 36 | expect(calledOptions.signal).toBeDefined(); 37 | expect(calledOptions.signal).toBeInstanceOf(AbortSignal); 38 | }); 39 | 40 | it("should handle GET request correctly", async () => { 41 | const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); 42 | const responseBody = await response.json(); 43 | expect(responseBody).toEqual({ test: "successful" }); 44 | expect(mockFetch).toHaveBeenCalledTimes(1); 45 | const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; 46 | expect(calledUrl).toBe(mockGetUrl); 47 | expect(calledOptions).toEqual( 48 | expect.objectContaining({ 49 | method: "GET", 50 | headers: mockHeaders, 51 | body: undefined, 52 | credentials: undefined, 53 | }) 54 | ); 55 | expect(calledOptions.signal).toBeDefined(); 56 | expect(calledOptions.signal).toBeInstanceOf(AbortSignal); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /tests/unit/fetcher/signals.test.ts: -------------------------------------------------------------------------------- 1 | import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; 2 | 3 | describe("Test getTimeoutSignal", () => { 4 | beforeEach(() => { 5 | jest.useFakeTimers(); 6 | }); 7 | 8 | afterEach(() => { 9 | jest.useRealTimers(); 10 | }); 11 | 12 | it("should return an object with signal and abortId", () => { 13 | const { signal, abortId } = getTimeoutSignal(1000); 14 | 15 | expect(signal).toBeDefined(); 16 | expect(abortId).toBeDefined(); 17 | expect(signal).toBeInstanceOf(AbortSignal); 18 | expect(signal.aborted).toBe(false); 19 | }); 20 | 21 | it("should create a signal that aborts after the specified timeout", () => { 22 | const timeoutMs = 5000; 23 | const { signal } = getTimeoutSignal(timeoutMs); 24 | 25 | expect(signal.aborted).toBe(false); 26 | 27 | jest.advanceTimersByTime(timeoutMs - 1); 28 | expect(signal.aborted).toBe(false); 29 | 30 | jest.advanceTimersByTime(1); 31 | expect(signal.aborted).toBe(true); 32 | }); 33 | }); 34 | 35 | describe("Test anySignal", () => { 36 | it("should return an AbortSignal", () => { 37 | const signal = anySignal(new AbortController().signal); 38 | expect(signal).toBeInstanceOf(AbortSignal); 39 | }); 40 | 41 | it("should abort when any of the input signals is aborted", () => { 42 | const controller1 = new AbortController(); 43 | const controller2 = new AbortController(); 44 | const signal = anySignal(controller1.signal, controller2.signal); 45 | 46 | expect(signal.aborted).toBe(false); 47 | controller1.abort(); 48 | expect(signal.aborted).toBe(true); 49 | }); 50 | 51 | it("should handle an array of signals", () => { 52 | const controller1 = new AbortController(); 53 | const controller2 = new AbortController(); 54 | const signal = anySignal([controller1.signal, controller2.signal]); 55 | 56 | expect(signal.aborted).toBe(false); 57 | controller2.abort(); 58 | expect(signal.aborted).toBe(true); 59 | }); 60 | 61 | it("should abort immediately if one of the input signals is already aborted", () => { 62 | const controller1 = new AbortController(); 63 | const controller2 = new AbortController(); 64 | controller1.abort(); 65 | 66 | const signal = anySignal(controller1.signal, controller2.signal); 67 | expect(signal.aborted).toBe(true); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/api/client/requests/EmbedRequest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | import * as VoyageAI from "../../index"; 6 | 7 | /** 8 | * @example 9 | * { 10 | * input: "input", 11 | * model: "model" 12 | * } 13 | */ 14 | export interface EmbedRequest { 15 | /** 16 | * A single text string, or a list of texts as a list of strings. Currently, we have two constraints on the list:
      • The maximum length of the list is 128.
      • The total number of tokens in the list is at most 320K for `voyage-2`, and 120K for `voyage-large-2`, `voyage-finance-2`, `voyage-multilingual-2`, `voyage-law-2`, and `voyage-code-2`.
        • 17 | * 18 | */ 19 | input: VoyageAI.EmbedRequestInput; 20 | /** 21 | * Name of the model. Recommended options: `voyage-2`, `voyage-large-2`, `voyage-finance-2`, `voyage-multilingual-2`, `voyage-law-2`, `voyage-code-2`. 22 | * 23 | */ 24 | model: string; 25 | /** 26 | * Type of the input text. Defaults to `null`. Other options: `query`, `document`. 27 | * 28 | */ 29 | inputType?: VoyageAI.EmbedRequestInputType; 30 | /** 31 | * Whether to truncate the input texts to fit within the context length. Defaults to `true`.
          • If `true`, over-length input texts will be truncated to fit within the context length, before vectorized by the embedding model.
          • If `false`, an error will be raised if any given text exceeds the context length.
          32 | * 33 | */ 34 | truncation?: boolean; 35 | /** 36 | * Format in which the embeddings are encoded. We support two options:
          • If not specified (defaults to `null`): the embeddings are represented as lists of floating-point numbers;
          • `base64`: the embeddings are compressed to [base64](https://docs.python.org/3/library/base64.html) encodings.
          37 | * 38 | */ 39 | encodingFormat?: "base64"; 40 | /** 41 | * The number of dimensions for resulting output embeddings. Defaults to `null`. 42 | * 43 | */ 44 | outputDimension?: number; 45 | /** 46 | * The data type for the embeddings to be returned. Defaults to `float`. Other options: `int8`, `uint8`, `binary`, `ubinary`. `float` is supported for all models. `int8`, `uint8`, `binary`, and `ubinary` are supported by `voyage-3-large` and `voyage-code-3`. Please see our guide for more details about output data types. 47 | * 48 | */ 49 | outputDtype?: VoyageAI.EmbedRequestOutputDtype; 50 | } 51 | -------------------------------------------------------------------------------- /src/api/client/requests/RerankRequest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | /** 6 | * @example 7 | * { 8 | * query: "query", 9 | * documents: ["documents"], 10 | * model: "model" 11 | * } 12 | */ 13 | export interface RerankRequest { 14 | /** 15 | * The query as a string. The query can contain a maximum of 1000 tokens for `rerank-lite-1` and 2000 tokens for `rerank-1`. 16 | * 17 | */ 18 | query: string; 19 | /** 20 | * The documents to be reranked as a list of strings.
          • The number of documents cannot exceed 1000.
          • The sum of the number of tokens in the query and the number of tokens in any single document cannot exceed 4000 for `rerank-lite-1` and 8000 for `rerank-1`.
          • he total number of tokens, defined as "the number of query tokens × the number of documents + sum of the number of tokens in all documents", cannot exceed 300K for `rerank-lite-1` and 100K for `rerank-1`. Please see our FAQ.
          21 | * 22 | */ 23 | documents: string[]; 24 | /** 25 | * Name of the model. Recommended options: `rerank-lite-1`, `rerank-1`. 26 | * 27 | */ 28 | model: string; 29 | /** 30 | * The number of most relevant documents to return. If not specified, the reranking results of all documents will be returned. 31 | * 32 | */ 33 | topK?: number; 34 | /** 35 | * Whether to return the documents in the response. Defaults to `false`.
          • If `false`, the API will return a list of {"index", "relevance_score"} where "index" refers to the index of a document within the input list.
          • If `true`, the API will return a list of {"index", "document", "relevance_score"} where "document" is the corresponding document from the input list.
          36 | * 37 | */ 38 | returnDocuments?: boolean; 39 | /** 40 | * Whether to truncate the input to satisfy the "context length limit" on the query and the documents. Defaults to `true`.
          • If `true`, the query and documents will be truncated to fit within the context length limit, before processed by the reranker model.
          • If `false`, an error will be raised when the query exceeds 1000 tokens for `rerank-lite-1` and 2000 tokens for `rerank-1`, or the sum of the number of tokens in the query and the number of tokens in any single document exceeds 4000 for `rerank-lite-1` and 8000 for `rerank-1`.
          41 | * 42 | */ 43 | truncation?: boolean; 44 | } 45 | -------------------------------------------------------------------------------- /src/core/schemas/builders/list/list.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; 2 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 3 | import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; 4 | import { getSchemaUtils } from "../schema-utils"; 5 | 6 | export function list(schema: Schema): Schema { 7 | const baseSchema: BaseSchema = { 8 | parse: (raw, opts) => 9 | validateAndTransformArray(raw, (item, index) => 10 | schema.parse(item, { 11 | ...opts, 12 | breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], 13 | }) 14 | ), 15 | json: (parsed, opts) => 16 | validateAndTransformArray(parsed, (item, index) => 17 | schema.json(item, { 18 | ...opts, 19 | breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], 20 | }) 21 | ), 22 | getType: () => SchemaType.LIST, 23 | }; 24 | 25 | return { 26 | ...maybeSkipValidation(baseSchema), 27 | ...getSchemaUtils(baseSchema), 28 | }; 29 | } 30 | 31 | function validateAndTransformArray( 32 | value: unknown, 33 | transformItem: (item: Raw, index: number) => MaybeValid 34 | ): MaybeValid { 35 | if (!Array.isArray(value)) { 36 | return { 37 | ok: false, 38 | errors: [ 39 | { 40 | message: getErrorMessageForIncorrectType(value, "list"), 41 | path: [], 42 | }, 43 | ], 44 | }; 45 | } 46 | 47 | const maybeValidItems = value.map((item, index) => transformItem(item, index)); 48 | 49 | return maybeValidItems.reduce>( 50 | (acc, item) => { 51 | if (acc.ok && item.ok) { 52 | return { 53 | ok: true, 54 | value: [...acc.value, item.value], 55 | }; 56 | } 57 | 58 | const errors: ValidationError[] = []; 59 | if (!acc.ok) { 60 | errors.push(...acc.errors); 61 | } 62 | if (!item.ok) { 63 | errors.push(...item.errors); 64 | } 65 | 66 | return { 67 | ok: false, 68 | errors, 69 | }; 70 | }, 71 | { ok: true, value: [] } 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /tests/unit/zurg/object/extend.test.ts: -------------------------------------------------------------------------------- 1 | import { boolean, object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; 2 | import { itSchema, itSchemaIdentity } from "../utils/itSchema"; 3 | 4 | describe("extend", () => { 5 | itSchemaIdentity( 6 | object({ 7 | foo: string(), 8 | }).extend( 9 | object({ 10 | bar: stringLiteral("bar"), 11 | }) 12 | ), 13 | { 14 | foo: "", 15 | bar: "bar", 16 | } as const, 17 | { 18 | title: "extended properties are included in schema", 19 | } 20 | ); 21 | 22 | itSchemaIdentity( 23 | object({ 24 | foo: string(), 25 | }) 26 | .extend( 27 | object({ 28 | bar: stringLiteral("bar"), 29 | }) 30 | ) 31 | .extend( 32 | object({ 33 | baz: boolean(), 34 | }) 35 | ), 36 | { 37 | foo: "", 38 | bar: "bar", 39 | baz: true, 40 | } as const, 41 | { 42 | title: "extensions can be extended", 43 | } 44 | ); 45 | 46 | itSchema( 47 | "converts nested object", 48 | object({ 49 | item: object({ 50 | helloWorld: property("hello_world", string()), 51 | }), 52 | }).extend( 53 | object({ 54 | goodbye: property("goodbye_raw", string()), 55 | }) 56 | ), 57 | { 58 | raw: { item: { hello_world: "yo" }, goodbye_raw: "peace" }, 59 | parsed: { item: { helloWorld: "yo" }, goodbye: "peace" }, 60 | } 61 | ); 62 | 63 | itSchema( 64 | "extensions work with raw/parsed property name conversions", 65 | object({ 66 | item: property("item_raw", string()), 67 | }).extend( 68 | object({ 69 | goodbye: property("goodbye_raw", string()), 70 | }) 71 | ), 72 | { 73 | raw: { item_raw: "hi", goodbye_raw: "peace" }, 74 | parsed: { item: "hi", goodbye: "peace" }, 75 | } 76 | ); 77 | 78 | describe("compile", () => { 79 | // eslint-disable-next-line jest/expect-expect 80 | it("doesn't compile with non-object schema", () => { 81 | () => 82 | object({ 83 | foo: string(), 84 | }) 85 | // @ts-expect-error 86 | .extend([]); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /src/api/types/MultimodalEmbedRequestInputType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by Fern from our API Definition. 3 | */ 4 | 5 | /** 6 | * Type of the input text. Defaults to `null`. Other options: `query`, `document`. 7 | *
          • When input_type is null, the embedding model directly converts your input data into numerical vectors. For retrieval/search purposes—where an input (called a "query") is used to search for relevant pieces of information (referred to as "documents")—we recommend specifying whether your inputs are intended as queries or documents by setting input_type to query or document, respectively. In these cases, Voyage prepends a prompt to your input before vectorizing it, helping the model create more effective vectors tailored for retrieval/search tasks. Since inputs can be multimodal, queries and documents can be text, images, or an interleaving of both modalities. Embeddings generated with and without the input_type argument are compatible.
          • For transparency, the following prompts are prepended to your input.
          • 8 | *
              9 | *
            • For query, the prompt is "Represent the query for retrieving supporting documents: ".
            • 10 | *
            • For document, the prompt is "Represent the query for retrieving supporting documents: ".
            • 11 | *
            12 | *
            13 | */ 14 | export type MultimodalEmbedRequestInputType = "query" | "document"; 15 | 16 | export const MultimodalEmbedRequestInputType = { 17 | Query: "query", 18 | Document: "document", 19 | } as const; 20 | -------------------------------------------------------------------------------- /src/core/schemas/builders/date/date.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, Schema, SchemaType } from "../../Schema"; 2 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 3 | import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; 4 | import { getSchemaUtils } from "../schema-utils"; 5 | 6 | // https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime 7 | const ISO_8601_REGEX = 8 | /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; 9 | 10 | export function date(): Schema { 11 | const baseSchema: BaseSchema = { 12 | parse: (raw, { breadcrumbsPrefix = [] } = {}) => { 13 | if (typeof raw !== "string") { 14 | return { 15 | ok: false, 16 | errors: [ 17 | { 18 | path: breadcrumbsPrefix, 19 | message: getErrorMessageForIncorrectType(raw, "string"), 20 | }, 21 | ], 22 | }; 23 | } 24 | if (!ISO_8601_REGEX.test(raw)) { 25 | return { 26 | ok: false, 27 | errors: [ 28 | { 29 | path: breadcrumbsPrefix, 30 | message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), 31 | }, 32 | ], 33 | }; 34 | } 35 | return { 36 | ok: true, 37 | value: new Date(raw), 38 | }; 39 | }, 40 | json: (date, { breadcrumbsPrefix = [] } = {}) => { 41 | if (date instanceof Date) { 42 | return { 43 | ok: true, 44 | value: date.toISOString(), 45 | }; 46 | } else { 47 | return { 48 | ok: false, 49 | errors: [ 50 | { 51 | path: breadcrumbsPrefix, 52 | message: getErrorMessageForIncorrectType(date, "Date object"), 53 | }, 54 | ], 55 | }; 56 | } 57 | }, 58 | getType: () => SchemaType.DATE, 59 | }; 60 | 61 | return { 62 | ...maybeSkipValidation(baseSchema), 63 | ...getSchemaUtils(baseSchema), 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/core/schemas/builders/object/types.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; 2 | import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; 3 | import { ObjectLikeUtils } from "../object-like"; 4 | import { SchemaUtils } from "../schema-utils"; 5 | import { Property } from "./property"; 6 | 7 | export type ObjectSchema = BaseObjectSchema & 8 | ObjectLikeUtils & 9 | ObjectUtils & 10 | SchemaUtils; 11 | 12 | export interface BaseObjectSchema extends BaseSchema { 13 | _getRawProperties: () => (keyof Raw)[]; 14 | _getParsedProperties: () => (keyof Parsed)[]; 15 | } 16 | 17 | export interface ObjectUtils { 18 | extend: ( 19 | schemas: ObjectSchema 20 | ) => ObjectSchema; 21 | } 22 | 23 | export type inferRawObject> = O extends ObjectSchema ? Raw : never; 24 | 25 | export type inferParsedObject> = O extends ObjectSchema 26 | ? Parsed 27 | : never; 28 | 29 | export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< 30 | inferRawObjectFromPropertySchemas, 31 | inferParsedObjectFromPropertySchemas 32 | >; 33 | 34 | export type inferRawObjectFromPropertySchemas> = 35 | addQuestionMarksToNullableProperties<{ 36 | [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; 37 | }>; 38 | 39 | export type inferParsedObjectFromPropertySchemas> = 40 | addQuestionMarksToNullableProperties<{ 41 | [K in keyof T]: inferParsedPropertySchema; 42 | }>; 43 | 44 | export type PropertySchemas = Record< 45 | ParsedKeys, 46 | Property | Schema 47 | >; 48 | 49 | export type inferRawPropertySchema

            | Schema> = P extends Property< 50 | any, 51 | infer Raw, 52 | any 53 | > 54 | ? Raw 55 | : P extends Schema 56 | ? inferRaw

            57 | : never; 58 | 59 | export type inferParsedPropertySchema

            | Schema> = P extends Property< 60 | any, 61 | any, 62 | infer Parsed 63 | > 64 | ? Parsed 65 | : P extends Schema 66 | ? inferParsed

            67 | : never; 68 | 69 | export type inferRawKey< 70 | ParsedKey extends string | number | symbol, 71 | P extends Property | Schema 72 | > = P extends Property ? Raw : ParsedKey; 73 | -------------------------------------------------------------------------------- /tests/unit/fetcher/getRequestBody.test.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIME } from "../../../src/core/runtime"; 2 | import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; 3 | 4 | if (RUNTIME.type === "browser") { 5 | require("jest-fetch-mock").enableMocks(); 6 | } 7 | 8 | describe("Test getRequestBody", () => { 9 | it("should return FormData as is in Node environment", async () => { 10 | if (RUNTIME.type === "node") { 11 | const formData = new (await import("formdata-node")).FormData(); 12 | formData.append("key", "value"); 13 | const result = await getRequestBody({ 14 | body: formData, 15 | type: "file", 16 | }); 17 | expect(result).toBe(formData); 18 | } 19 | }); 20 | 21 | it("should stringify body if not FormData in Node environment", async () => { 22 | if (RUNTIME.type === "node") { 23 | const body = { key: "value" }; 24 | const result = await getRequestBody({ 25 | body, 26 | type: "json", 27 | }); 28 | expect(result).toBe('{"key":"value"}'); 29 | } 30 | }); 31 | 32 | it("should return FormData in browser environment", async () => { 33 | if (RUNTIME.type === "browser") { 34 | const formData = new (await import("form-data")).default(); 35 | formData.append("key", "value"); 36 | const result = await getRequestBody({ 37 | body: formData, 38 | type: "file", 39 | }); 40 | expect(result).toBe(formData); 41 | } 42 | }); 43 | 44 | it("should stringify body if not FormData in browser environment", async () => { 45 | if (RUNTIME.type === "browser") { 46 | const body = { key: "value" }; 47 | const result = await getRequestBody({ 48 | body, 49 | type: "json", 50 | }); 51 | expect(result).toBe('{"key":"value"}'); 52 | } 53 | }); 54 | 55 | it("should return the Uint8Array", async () => { 56 | const input = new Uint8Array([1, 2, 3]); 57 | const result = await getRequestBody({ 58 | body: input, 59 | type: "bytes", 60 | }); 61 | expect(result).toBe(input); 62 | }); 63 | 64 | it("should return the input for content-type 'application/x-www-form-urlencoded'", async () => { 65 | const input = "key=value&another=param"; 66 | const result = await getRequestBody({ 67 | body: input, 68 | type: "other", 69 | }); 70 | expect(result).toBe(input); 71 | }); 72 | 73 | it("should JSON stringify objects", async () => { 74 | const input = { key: "value" }; 75 | const result = await getRequestBody({ 76 | body: input, 77 | type: "json", 78 | }); 79 | expect(result).toBe('{"key":"value"}'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/unit/fetcher/getResponseBody.test.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIME } from "../../../src/core/runtime"; 2 | import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; 3 | import { chooseStreamWrapper } from "../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; 4 | 5 | if (RUNTIME.type === "browser") { 6 | require("jest-fetch-mock").enableMocks(); 7 | } 8 | 9 | describe("Test getResponseBody", () => { 10 | it("should handle blob response type", async () => { 11 | const mockBlob = new Blob(["test"], { type: "text/plain" }); 12 | const mockResponse = new Response(mockBlob); 13 | const result = await getResponseBody(mockResponse, "blob"); 14 | // @ts-expect-error 15 | expect(result.constructor.name).toBe("Blob"); 16 | }); 17 | 18 | it("should handle sse response type", async () => { 19 | if (RUNTIME.type === "node") { 20 | const mockStream = new ReadableStream(); 21 | const mockResponse = new Response(mockStream); 22 | const result = await getResponseBody(mockResponse, "sse"); 23 | expect(result).toBe(mockStream); 24 | } 25 | }); 26 | 27 | it("should handle streaming response type", async () => { 28 | if (RUNTIME.type === "node") { 29 | const mockStream = new ReadableStream(); 30 | const mockResponse = new Response(mockStream); 31 | const result = await getResponseBody(mockResponse, "streaming"); 32 | // need to reinstantiate string as a result of locked state in Readable Stream after registration with Response 33 | expect(JSON.stringify(result)).toBe(JSON.stringify(await chooseStreamWrapper(new ReadableStream()))); 34 | } 35 | }); 36 | 37 | it("should handle text response type", async () => { 38 | const mockResponse = new Response("test text"); 39 | const result = await getResponseBody(mockResponse, "text"); 40 | expect(result).toBe("test text"); 41 | }); 42 | 43 | it("should handle JSON response", async () => { 44 | const mockJson = { key: "value" }; 45 | const mockResponse = new Response(JSON.stringify(mockJson)); 46 | const result = await getResponseBody(mockResponse); 47 | expect(result).toEqual(mockJson); 48 | }); 49 | 50 | it("should handle empty response", async () => { 51 | const mockResponse = new Response(""); 52 | const result = await getResponseBody(mockResponse); 53 | expect(result).toBeUndefined(); 54 | }); 55 | 56 | it("should handle non-JSON response", async () => { 57 | const mockResponse = new Response("invalid json"); 58 | const result = await getResponseBody(mockResponse); 59 | expect(result).toEqual({ 60 | ok: false, 61 | error: { 62 | reason: "non-json", 63 | statusCode: 200, 64 | rawBody: "invalid json", 65 | }, 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /tests/unit/zurg/schema-utils/getSchemaUtils.test.ts: -------------------------------------------------------------------------------- 1 | import { object, string } from "../../../../src/core/schemas/builders"; 2 | import { itSchema } from "../utils/itSchema"; 3 | 4 | describe("getSchemaUtils", () => { 5 | describe("optional()", () => { 6 | itSchema("optional fields allow original schema", string().optional(), { 7 | raw: "hello", 8 | parsed: "hello", 9 | }); 10 | 11 | itSchema("optional fields are not required", string().optional(), { 12 | raw: null, 13 | parsed: undefined, 14 | }); 15 | }); 16 | 17 | describe("transform()", () => { 18 | itSchema( 19 | "transorm and untransform run correctly", 20 | string().transform({ 21 | transform: (x) => x + "X", 22 | untransform: (x) => (x as string).slice(0, -1), 23 | }), 24 | { 25 | raw: "hello", 26 | parsed: "helloX", 27 | } 28 | ); 29 | }); 30 | 31 | describe("parseOrThrow()", () => { 32 | it("parses valid value", async () => { 33 | const value = string().parseOrThrow("hello"); 34 | expect(value).toBe("hello"); 35 | }); 36 | 37 | it("throws on invalid value", async () => { 38 | const value = () => object({ a: string(), b: string() }).parseOrThrow({ a: 24 }); 39 | expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); 40 | }); 41 | }); 42 | 43 | describe("jsonOrThrow()", () => { 44 | it("serializes valid value", async () => { 45 | const value = string().jsonOrThrow("hello"); 46 | expect(value).toBe("hello"); 47 | }); 48 | 49 | it("throws on invalid value", async () => { 50 | const value = () => object({ a: string(), b: string() }).jsonOrThrow({ a: 24 }); 51 | expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); 52 | }); 53 | }); 54 | 55 | describe("omitUndefined", () => { 56 | it("serializes undefined as null", async () => { 57 | const value = object({ 58 | a: string().optional(), 59 | b: string().optional(), 60 | }).jsonOrThrow({ 61 | a: "hello", 62 | b: undefined, 63 | }); 64 | expect(value).toEqual({ a: "hello", b: null }); 65 | }); 66 | 67 | it("omits undefined values", async () => { 68 | const value = object({ 69 | a: string().optional(), 70 | b: string().optional(), 71 | }).jsonOrThrow( 72 | { 73 | a: "hello", 74 | b: undefined, 75 | }, 76 | { 77 | omitUndefined: true, 78 | } 79 | ); 80 | expect(value).toEqual({ a: "hello" }); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/core/schemas/Schema.ts: -------------------------------------------------------------------------------- 1 | import { SchemaUtils } from "./builders"; 2 | 3 | export type Schema = BaseSchema & SchemaUtils; 4 | 5 | export type inferRaw = S extends Schema ? Raw : never; 6 | export type inferParsed = S extends Schema ? Parsed : never; 7 | 8 | export interface BaseSchema { 9 | parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; 10 | json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; 11 | getType: () => SchemaType | SchemaType; 12 | } 13 | 14 | export const SchemaType = { 15 | DATE: "date", 16 | ENUM: "enum", 17 | LIST: "list", 18 | STRING_LITERAL: "stringLiteral", 19 | BOOLEAN_LITERAL: "booleanLiteral", 20 | OBJECT: "object", 21 | ANY: "any", 22 | BOOLEAN: "boolean", 23 | NUMBER: "number", 24 | STRING: "string", 25 | UNKNOWN: "unknown", 26 | RECORD: "record", 27 | SET: "set", 28 | UNION: "union", 29 | UNDISCRIMINATED_UNION: "undiscriminatedUnion", 30 | OPTIONAL: "optional", 31 | } as const; 32 | export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; 33 | 34 | export type MaybeValid = Valid | Invalid; 35 | 36 | export interface Valid { 37 | ok: true; 38 | value: T; 39 | } 40 | 41 | export interface Invalid { 42 | ok: false; 43 | errors: ValidationError[]; 44 | } 45 | 46 | export interface ValidationError { 47 | path: string[]; 48 | message: string; 49 | } 50 | 51 | export interface SchemaOptions { 52 | /** 53 | * how to handle unrecognized keys in objects 54 | * 55 | * @default "fail" 56 | */ 57 | unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; 58 | 59 | /** 60 | * whether to fail when an unrecognized discriminant value is 61 | * encountered in a union 62 | * 63 | * @default false 64 | */ 65 | allowUnrecognizedUnionMembers?: boolean; 66 | 67 | /** 68 | * whether to fail when an unrecognized enum value is encountered 69 | * 70 | * @default false 71 | */ 72 | allowUnrecognizedEnumValues?: boolean; 73 | 74 | /** 75 | * whether to allow data that doesn't conform to the schema. 76 | * invalid data is passed through without transformation. 77 | * 78 | * when this is enabled, .parse() and .json() will always 79 | * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` 80 | * will never fail. 81 | * 82 | * @default false 83 | */ 84 | skipValidation?: boolean; 85 | 86 | /** 87 | * each validation failure contains a "path" property, which is 88 | * the breadcrumbs to the offending node in the JSON. you can supply 89 | * a prefix that is prepended to all the errors' paths. this can be 90 | * helpful for zurg's internal debug logging. 91 | */ 92 | breadcrumbsPrefix?: string[]; 93 | 94 | /** 95 | * whether to send 'null' for optional properties explicitly set to 'undefined'. 96 | */ 97 | omitUndefined?: boolean; 98 | } 99 | -------------------------------------------------------------------------------- /src/core/schemas/builders/object-like/getObjectLikeUtils.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from "../../Schema"; 2 | import { filterObject } from "../../utils/filterObject"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | import { isPlainObject } from "../../utils/isPlainObject"; 5 | import { getSchemaUtils } from "../schema-utils"; 6 | import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; 7 | 8 | export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { 9 | return { 10 | withParsedProperties: (properties) => withParsedProperties(schema, properties), 11 | }; 12 | } 13 | 14 | /** 15 | * object-like utils are defined in one file to resolve issues with circular imports 16 | */ 17 | 18 | export function withParsedProperties( 19 | objectLike: BaseSchema, 20 | properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } 21 | ): ObjectLikeSchema { 22 | const objectSchema: BaseSchema = { 23 | parse: (raw, opts) => { 24 | const parsedObject = objectLike.parse(raw, opts); 25 | if (!parsedObject.ok) { 26 | return parsedObject; 27 | } 28 | 29 | const additionalProperties = Object.entries(properties).reduce>( 30 | (processed, [key, value]) => { 31 | return { 32 | ...processed, 33 | [key]: typeof value === "function" ? value(parsedObject.value) : value, 34 | }; 35 | }, 36 | {} 37 | ); 38 | 39 | return { 40 | ok: true, 41 | value: { 42 | ...parsedObject.value, 43 | ...(additionalProperties as Properties), 44 | }, 45 | }; 46 | }, 47 | 48 | json: (parsed, opts) => { 49 | if (!isPlainObject(parsed)) { 50 | return { 51 | ok: false, 52 | errors: [ 53 | { 54 | path: opts?.breadcrumbsPrefix ?? [], 55 | message: getErrorMessageForIncorrectType(parsed, "object"), 56 | }, 57 | ], 58 | }; 59 | } 60 | 61 | // strip out added properties 62 | const addedPropertyKeys = new Set(Object.keys(properties)); 63 | const parsedWithoutAddedProperties = filterObject( 64 | parsed, 65 | Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) 66 | ); 67 | 68 | return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); 69 | }, 70 | 71 | getType: () => objectLike.getType(), 72 | }; 73 | 74 | return { 75 | ...objectSchema, 76 | ...getSchemaUtils(objectSchema), 77 | ...getObjectLikeUtils(objectSchema), 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /tests/unit/fetcher/requestWithRetries.test.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIME } from "../../../src/core/runtime"; 2 | import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; 3 | 4 | if (RUNTIME.type === "browser") { 5 | require("jest-fetch-mock").enableMocks(); 6 | } 7 | 8 | describe("Test exponential backoff", () => { 9 | let mockFetch: jest.Mock; 10 | let originalSetTimeout: typeof setTimeout; 11 | 12 | beforeEach(() => { 13 | mockFetch = jest.fn(); 14 | originalSetTimeout = global.setTimeout; 15 | jest.useFakeTimers(); 16 | }); 17 | 18 | afterEach(() => { 19 | jest.useRealTimers(); 20 | global.setTimeout = originalSetTimeout; 21 | }); 22 | 23 | it("should retry on 408, 409, 429, 500+", async () => { 24 | mockFetch 25 | .mockResolvedValueOnce(new Response("", { status: 408 })) 26 | .mockResolvedValueOnce(new Response("", { status: 409 })) 27 | .mockResolvedValueOnce(new Response("", { status: 429 })) 28 | .mockResolvedValueOnce(new Response("", { status: 500 })) 29 | .mockResolvedValueOnce(new Response("", { status: 502 })) 30 | .mockResolvedValueOnce(new Response("", { status: 200 })) 31 | .mockResolvedValueOnce(new Response("", { status: 408 })); 32 | 33 | const responsePromise = requestWithRetries(() => mockFetch(), 10); 34 | 35 | await jest.advanceTimersByTimeAsync(10000); 36 | const response = await responsePromise; 37 | 38 | expect(mockFetch).toHaveBeenCalledTimes(6); 39 | expect(response.status).toBe(200); 40 | }); 41 | 42 | it("should retry max 3 times", async () => { 43 | mockFetch 44 | .mockResolvedValueOnce(new Response("", { status: 408 })) 45 | .mockResolvedValueOnce(new Response("", { status: 409 })) 46 | .mockResolvedValueOnce(new Response("", { status: 429 })) 47 | .mockResolvedValueOnce(new Response("", { status: 429 })); 48 | 49 | const responsePromise = requestWithRetries(() => mockFetch(), 3); 50 | 51 | await jest.advanceTimersByTimeAsync(10000); 52 | const response = await responsePromise; 53 | 54 | expect(mockFetch).toHaveBeenCalledTimes(4); 55 | expect(response.status).toBe(429); 56 | }); 57 | it("should not retry on 200", async () => { 58 | mockFetch 59 | .mockResolvedValueOnce(new Response("", { status: 200 })) 60 | .mockResolvedValueOnce(new Response("", { status: 409 })); 61 | 62 | const responsePromise = requestWithRetries(() => mockFetch(), 3); 63 | 64 | await jest.advanceTimersByTimeAsync(10000); 65 | const response = await responsePromise; 66 | 67 | expect(mockFetch).toHaveBeenCalledTimes(1); 68 | expect(response.status).toBe(200); 69 | }); 70 | 71 | it("should retry with exponential backoff timing", async () => { 72 | mockFetch.mockResolvedValue(new Response("", { status: 500 })); 73 | const maxRetries = 7; 74 | const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); 75 | expect(mockFetch).toHaveBeenCalledTimes(1); 76 | 77 | const delays = [1, 2, 4, 8, 16, 32, 64]; 78 | for (let i = 0; i < delays.length; i++) { 79 | await jest.advanceTimersByTimeAsync(delays[i] as number); 80 | expect(mockFetch).toHaveBeenCalledTimes(Math.min(i + 2, maxRetries + 1)); 81 | } 82 | const response = await responsePromise; 83 | expect(response.status).toBe(500); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts: -------------------------------------------------------------------------------- 1 | import type { Readable, Writable } from "readable-stream"; 2 | import { EventCallback, StreamWrapper } from "./chooseStreamWrapper"; 3 | 4 | export class NodePre18StreamWrapper implements StreamWrapper { 5 | private readableStream: Readable; 6 | private encoding: string | undefined; 7 | 8 | constructor(readableStream: Readable) { 9 | this.readableStream = readableStream; 10 | } 11 | 12 | public on(event: string, callback: EventCallback): void { 13 | this.readableStream.on(event, callback); 14 | } 15 | 16 | public off(event: string, callback: EventCallback): void { 17 | this.readableStream.off(event, callback); 18 | } 19 | 20 | public pipe(dest: Writable): Writable { 21 | this.readableStream.pipe(dest); 22 | return dest; 23 | } 24 | 25 | public pipeTo(dest: Writable): Writable { 26 | return this.pipe(dest); 27 | } 28 | 29 | public unpipe(dest?: Writable): void { 30 | if (dest) { 31 | this.readableStream.unpipe(dest); 32 | } else { 33 | this.readableStream.unpipe(); 34 | } 35 | } 36 | 37 | public destroy(error?: Error): void { 38 | this.readableStream.destroy(error); 39 | } 40 | 41 | public pause(): void { 42 | this.readableStream.pause(); 43 | } 44 | 45 | public resume(): void { 46 | this.readableStream.resume(); 47 | } 48 | 49 | public get isPaused(): boolean { 50 | return this.readableStream.isPaused(); 51 | } 52 | 53 | public async read(): Promise { 54 | return new Promise((resolve, reject) => { 55 | const chunk = this.readableStream.read(); 56 | if (chunk) { 57 | resolve(chunk); 58 | } else { 59 | this.readableStream.once("readable", () => { 60 | const chunk = this.readableStream.read(); 61 | resolve(chunk); 62 | }); 63 | this.readableStream.once("error", reject); 64 | } 65 | }); 66 | } 67 | 68 | public setEncoding(encoding?: string): void { 69 | this.readableStream.setEncoding(encoding as BufferEncoding); 70 | this.encoding = encoding; 71 | } 72 | 73 | public async text(): Promise { 74 | const chunks: Uint8Array[] = []; 75 | const encoder = new TextEncoder(); 76 | this.readableStream.setEncoding((this.encoding || "utf-8") as BufferEncoding); 77 | 78 | for await (const chunk of this.readableStream) { 79 | chunks.push(encoder.encode(chunk)); 80 | } 81 | 82 | const decoder = new TextDecoder(this.encoding || "utf-8"); 83 | return decoder.decode(Buffer.concat(chunks)); 84 | } 85 | 86 | public async json(): Promise { 87 | const text = await this.text(); 88 | return JSON.parse(text); 89 | } 90 | 91 | public [Symbol.asyncIterator](): AsyncIterableIterator { 92 | const readableStream = this.readableStream; 93 | const iterator = readableStream[Symbol.asyncIterator](); 94 | 95 | // Create and return an async iterator that yields buffers 96 | return { 97 | async next(): Promise> { 98 | const { value, done } = await iterator.next(); 99 | return { value: value as Buffer, done }; 100 | }, 101 | [Symbol.asyncIterator]() { 102 | return this; 103 | }, 104 | }; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/unit/zurg/union/union.test.ts: -------------------------------------------------------------------------------- 1 | import { boolean, discriminant, number, object, string, union } from "../../../../src/core/schemas/builders"; 2 | import { itSchema, itSchemaIdentity } from "../utils/itSchema"; 3 | import { itValidate } from "../utils/itValidate"; 4 | 5 | describe("union", () => { 6 | itSchemaIdentity( 7 | union("type", { 8 | lion: object({ 9 | meows: boolean(), 10 | }), 11 | giraffe: object({ 12 | heightInInches: number(), 13 | }), 14 | }), 15 | { type: "lion", meows: true }, 16 | { title: "doesn't transform discriminant when it's a string" } 17 | ); 18 | 19 | itSchema( 20 | "transforms discriminant when it's a discriminant()", 21 | union(discriminant("type", "_type"), { 22 | lion: object({ meows: boolean() }), 23 | giraffe: object({ heightInInches: number() }), 24 | }), 25 | { 26 | raw: { _type: "lion", meows: true }, 27 | parsed: { type: "lion", meows: true }, 28 | } 29 | ); 30 | 31 | describe("allowUnrecognizedUnionMembers", () => { 32 | itSchema( 33 | "transforms discriminant & passes through values when discriminant value is unrecognized", 34 | union(discriminant("type", "_type"), { 35 | lion: object({ meows: boolean() }), 36 | giraffe: object({ heightInInches: number() }), 37 | }), 38 | { 39 | // @ts-expect-error 40 | raw: { _type: "moose", isAMoose: true }, 41 | // @ts-expect-error 42 | parsed: { type: "moose", isAMoose: true }, 43 | opts: { 44 | allowUnrecognizedUnionMembers: true, 45 | }, 46 | } 47 | ); 48 | }); 49 | 50 | describe("withParsedProperties", () => { 51 | it("Added property is included on parsed object", async () => { 52 | const schema = union("type", { 53 | lion: object({}), 54 | tiger: object({ value: string() }), 55 | }).withParsedProperties({ 56 | printType: (parsed) => () => parsed.type, 57 | }); 58 | 59 | const parsed = await schema.parse({ type: "lion" }); 60 | if (!parsed.ok) { 61 | throw new Error("Failed to parse"); 62 | } 63 | expect(parsed.value.printType()).toBe("lion"); 64 | }); 65 | }); 66 | 67 | itValidate( 68 | "non-object", 69 | union("type", { 70 | lion: object({}), 71 | tiger: object({ value: string() }), 72 | }), 73 | [], 74 | [ 75 | { 76 | path: [], 77 | message: "Expected object. Received list.", 78 | }, 79 | ] 80 | ); 81 | 82 | itValidate( 83 | "missing discriminant", 84 | union("type", { 85 | lion: object({}), 86 | tiger: object({ value: string() }), 87 | }), 88 | {}, 89 | [ 90 | { 91 | path: [], 92 | message: 'Missing discriminant ("type")', 93 | }, 94 | ] 95 | ); 96 | 97 | itValidate( 98 | "unrecognized discriminant value", 99 | union("type", { 100 | lion: object({}), 101 | tiger: object({ value: string() }), 102 | }), 103 | { 104 | type: "bear", 105 | }, 106 | [ 107 | { 108 | path: ["type"], 109 | message: 'Expected enum. Received "bear".', 110 | }, 111 | ] 112 | ); 113 | }); 114 | -------------------------------------------------------------------------------- /src/core/schemas/builders/schema-utils/getSchemaUtils.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; 2 | import { JsonError } from "./JsonError"; 3 | import { ParseError } from "./ParseError"; 4 | 5 | export interface SchemaUtils { 6 | optional: () => Schema; 7 | transform: (transformer: SchemaTransformer) => Schema; 8 | parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; 9 | jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; 10 | } 11 | 12 | export interface SchemaTransformer { 13 | transform: (parsed: Parsed) => Transformed; 14 | untransform: (transformed: any) => Parsed; 15 | } 16 | 17 | export function getSchemaUtils(schema: BaseSchema): SchemaUtils { 18 | return { 19 | optional: () => optional(schema), 20 | transform: (transformer) => transform(schema, transformer), 21 | parseOrThrow: (raw, opts) => { 22 | const parsed = schema.parse(raw, opts); 23 | if (parsed.ok) { 24 | return parsed.value; 25 | } 26 | throw new ParseError(parsed.errors); 27 | }, 28 | jsonOrThrow: (parsed, opts) => { 29 | const raw = schema.json(parsed, opts); 30 | if (raw.ok) { 31 | return raw.value; 32 | } 33 | throw new JsonError(raw.errors); 34 | }, 35 | }; 36 | } 37 | 38 | /** 39 | * schema utils are defined in one file to resolve issues with circular imports 40 | */ 41 | 42 | export function optional( 43 | schema: BaseSchema 44 | ): Schema { 45 | const baseSchema: BaseSchema = { 46 | parse: (raw, opts) => { 47 | if (raw == null) { 48 | return { 49 | ok: true, 50 | value: undefined, 51 | }; 52 | } 53 | return schema.parse(raw, opts); 54 | }, 55 | json: (parsed, opts) => { 56 | if (opts?.omitUndefined && parsed === undefined) { 57 | return { 58 | ok: true, 59 | value: undefined, 60 | }; 61 | } 62 | if (parsed == null) { 63 | return { 64 | ok: true, 65 | value: null, 66 | }; 67 | } 68 | return schema.json(parsed, opts); 69 | }, 70 | getType: () => SchemaType.OPTIONAL, 71 | }; 72 | 73 | return { 74 | ...baseSchema, 75 | ...getSchemaUtils(baseSchema), 76 | }; 77 | } 78 | 79 | export function transform( 80 | schema: BaseSchema, 81 | transformer: SchemaTransformer 82 | ): Schema { 83 | const baseSchema: BaseSchema = { 84 | parse: (raw, opts) => { 85 | const parsed = schema.parse(raw, opts); 86 | if (!parsed.ok) { 87 | return parsed; 88 | } 89 | return { 90 | ok: true, 91 | value: transformer.transform(parsed.value), 92 | }; 93 | }, 94 | json: (transformed, opts) => { 95 | const parsed = transformer.untransform(transformed); 96 | return schema.json(parsed, opts); 97 | }, 98 | getType: () => schema.getType(), 99 | }; 100 | 101 | return { 102 | ...baseSchema, 103 | ...getSchemaUtils(baseSchema), 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /src/core/runtime/runtime.ts: -------------------------------------------------------------------------------- 1 | interface DenoGlobal { 2 | version: { 3 | deno: string; 4 | }; 5 | } 6 | 7 | interface BunGlobal { 8 | version: string; 9 | } 10 | 11 | declare const Deno: DenoGlobal; 12 | declare const Bun: BunGlobal; 13 | 14 | /** 15 | * A constant that indicates whether the environment the code is running is a Web Browser. 16 | */ 17 | const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; 18 | 19 | /** 20 | * A constant that indicates whether the environment the code is running is a Web Worker. 21 | */ 22 | const isWebWorker = 23 | typeof self === "object" && 24 | // @ts-ignore 25 | typeof self?.importScripts === "function" && 26 | (self.constructor?.name === "DedicatedWorkerGlobalScope" || 27 | self.constructor?.name === "ServiceWorkerGlobalScope" || 28 | self.constructor?.name === "SharedWorkerGlobalScope"); 29 | 30 | /** 31 | * A constant that indicates whether the environment the code is running is Deno. 32 | */ 33 | const isDeno = 34 | typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; 35 | 36 | /** 37 | * A constant that indicates whether the environment the code is running is Bun.sh. 38 | */ 39 | const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; 40 | 41 | /** 42 | * A constant that indicates whether the environment the code is running is Node.JS. 43 | */ 44 | const isNode = 45 | typeof process !== "undefined" && 46 | Boolean(process.version) && 47 | Boolean(process.versions?.node) && 48 | // Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions 49 | !isDeno && 50 | !isBun; 51 | 52 | /** 53 | * A constant that indicates whether the environment the code is running is in React-Native. 54 | * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js 55 | */ 56 | const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; 57 | 58 | /** 59 | * A constant that indicates whether the environment the code is running is Cloudflare. 60 | * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent 61 | */ 62 | const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; 63 | 64 | /** 65 | * A constant that indicates which environment and version the SDK is running in. 66 | */ 67 | export const RUNTIME: Runtime = evaluateRuntime(); 68 | 69 | export interface Runtime { 70 | type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd"; 71 | version?: string; 72 | parsedVersion?: number; 73 | } 74 | 75 | function evaluateRuntime(): Runtime { 76 | if (isBrowser) { 77 | return { 78 | type: "browser", 79 | version: window.navigator.userAgent, 80 | }; 81 | } 82 | 83 | if (isCloudflare) { 84 | return { 85 | type: "workerd", 86 | }; 87 | } 88 | 89 | if (isWebWorker) { 90 | return { 91 | type: "web-worker", 92 | }; 93 | } 94 | 95 | if (isDeno) { 96 | return { 97 | type: "deno", 98 | version: Deno.version.deno, 99 | }; 100 | } 101 | 102 | if (isBun) { 103 | return { 104 | type: "bun", 105 | version: Bun.version, 106 | }; 107 | } 108 | 109 | if (isNode) { 110 | return { 111 | type: "node", 112 | version: process.versions.node, 113 | parsedVersion: Number(process.versions.node.split(".")[0]), 114 | }; 115 | } 116 | 117 | if (isReactNative) { 118 | return { 119 | type: "react-native", 120 | }; 121 | } 122 | 123 | return { 124 | type: "unknown", 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /tests/custom/auth.test.ts: -------------------------------------------------------------------------------- 1 | import { BasicAuth, BearerToken } from "../../src/core/auth"; 2 | import { getHeader } from "../../src/core/fetcher"; 3 | import { Base64 } from "js-base64"; 4 | 5 | describe("BasicAuth tests", () => { 6 | test("toAuthorizationHeader - undefined", async () => { 7 | const result = BasicAuth.toAuthorizationHeader(undefined); 8 | expect(result).toBeUndefined(); 9 | }); 10 | 11 | test("toAuthorizationHeader - simple", async () => { 12 | const result = BasicAuth.toAuthorizationHeader({ username: "foo", password: "bar" }); 13 | const expected = "Basic Zm9vOmJhcg=="; 14 | expect(result).toBe(expected); 15 | }); 16 | 17 | it("should handle special characters in username and password", () => { 18 | const basicAuth = { username: "user@example.com", password: "p@ssw0rd!" } 19 | const expectedToken = Base64.encode("user@example.com:p@ssw0rd!") 20 | expect(BasicAuth.toAuthorizationHeader(basicAuth)).toBe(`Basic ${expectedToken}`) 21 | }) 22 | 23 | test("fromAuthorizationHeader - bad input, test", async () => { 24 | expect(() => { 25 | const result = BasicAuth.fromAuthorizationHeader("Basic dGVzdA=="); 26 | }).toThrow(Error); 27 | }); 28 | 29 | test("fromAuthorizationHeader - bad input, prefix", async () => { 30 | expect(() => { 31 | const result = BasicAuth.fromAuthorizationHeader("Something dGVzdA=="); 32 | }).toThrow(Error); 33 | }); 34 | 35 | test("fromAuthorizationHeader - simple", async () => { 36 | const result = BasicAuth.fromAuthorizationHeader("Basic Zm9vOmJhcg=="); 37 | expect(result.username).toBe("foo"); 38 | expect(result.password).toBe("bar"); 39 | }); 40 | }); 41 | 42 | describe("BearerToken tests", () => { 43 | test("toAuthorizationHeader - undefined", async () => { 44 | const result = BearerToken.toAuthorizationHeader(undefined); 45 | expect(result).toBeUndefined(); 46 | }); 47 | 48 | test("toAuthorizationHeader - simple", async () => { 49 | const result = BearerToken.toAuthorizationHeader("dGVzdA=="); 50 | const expected = "Bearer dGVzdA=="; 51 | expect(result).toBe(expected); 52 | }); 53 | 54 | test("fromAuthorizationHeader - bad simple", async () => { 55 | const result = BearerToken.fromAuthorizationHeader("Bearer dGVzdA=="); 56 | expect(result).toBe("dGVzdA=="); 57 | }); 58 | 59 | test("fromAuthorizationHeader - simple", async () => { 60 | const result = BearerToken.fromAuthorizationHeader("Anything Zm9vOmJhcg=="); 61 | expect(result).toBe("Anything Zm9vOmJhcg=="); // This should throw an exception instead imo 62 | }); 63 | 64 | test("fromAuthorizationHeader - simple", async () => { 65 | const result = BearerToken.fromAuthorizationHeader("test"); 66 | expect(result).toBe("test"); // This should throw an exception instead imo 67 | }); 68 | 69 | test("toAuthorizationHeader - with empty string", () => { 70 | const result = BearerToken.toAuthorizationHeader("") 71 | expect(result).toBe("Bearer ") // should throw an error - empty bearer token?? 72 | }) 73 | 74 | test("fromAuthorizationHeader - with extra spaces", () => { 75 | const result = BearerToken.fromAuthorizationHeader("Bearer myToken ") 76 | expect(result).toBe("myToken") 77 | }) 78 | }); 79 | 80 | describe("getHeader tests", () => { 81 | test("getHeader - case insensitive match", () => { 82 | const headers = { 83 | "Content-Type": "application/json", 84 | "X-Custom-Header": "custom value" 85 | } 86 | const result = getHeader(headers, "content-type") 87 | expect(result).toBe("application/json") 88 | }) 89 | 90 | test("getHeader - exact match", () => { 91 | const headers = { 92 | "Authorization": "Bearer token123", 93 | "X-API-Key": "apikey456" 94 | } 95 | const result = getHeader(headers, "X-API-Key") 96 | expect(result).toBe("apikey456") 97 | }) 98 | 99 | test("getHeader - header not found", () => { 100 | const headers = { 101 | "Content-Type": "application/json" 102 | } 103 | const result = getHeader(headers, "Authorization") 104 | expect(result).toBeUndefined() 105 | }) 106 | 107 | test("getHeader - empty headers object", () => { 108 | const headers = {} 109 | const result = getHeader(headers, "Content-Type") 110 | expect(result).toBeUndefined() 111 | }) 112 | 113 | test("getHeader - multiple headers with same case-insensitive name", () => { 114 | const headers = { 115 | "X-Custom-Header": "value1", 116 | "x-custom-header": "value2" 117 | } 118 | const result = getHeader(headers, "X-Custom-Header") 119 | expect(result).toBe("value1") 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /src/core/fetcher/Fetcher.ts: -------------------------------------------------------------------------------- 1 | import { APIResponse } from "./APIResponse"; 2 | import { createRequestUrl } from "./createRequestUrl"; 3 | import { getFetchFn } from "./getFetchFn"; 4 | import { getRequestBody } from "./getRequestBody"; 5 | import { getResponseBody } from "./getResponseBody"; 6 | import { makeRequest } from "./makeRequest"; 7 | import { requestWithRetries } from "./requestWithRetries"; 8 | 9 | export type FetchFunction = (args: Fetcher.Args) => Promise>; 10 | 11 | export declare namespace Fetcher { 12 | export interface Args { 13 | url: string; 14 | method: string; 15 | contentType?: string; 16 | headers?: Record; 17 | queryParameters?: Record; 18 | body?: unknown; 19 | timeoutMs?: number; 20 | maxRetries?: number; 21 | withCredentials?: boolean; 22 | abortSignal?: AbortSignal; 23 | requestType?: "json" | "file" | "bytes"; 24 | responseType?: "json" | "blob" | "sse" | "streaming" | "text"; 25 | duplex?: "half"; 26 | } 27 | 28 | export type Error = FailedStatusCodeError | NonJsonError | TimeoutError | UnknownError; 29 | 30 | export interface FailedStatusCodeError { 31 | reason: "status-code"; 32 | statusCode: number; 33 | body: unknown; 34 | } 35 | 36 | export interface NonJsonError { 37 | reason: "non-json"; 38 | statusCode: number; 39 | rawBody: string; 40 | } 41 | 42 | export interface TimeoutError { 43 | reason: "timeout"; 44 | } 45 | 46 | export interface UnknownError { 47 | reason: "unknown"; 48 | errorMessage: string; 49 | } 50 | } 51 | 52 | export async function fetcherImpl(args: Fetcher.Args): Promise> { 53 | const headers: Record = {}; 54 | if (args.body !== undefined && args.contentType != null) { 55 | headers["Content-Type"] = args.contentType; 56 | } 57 | 58 | if (args.headers != null) { 59 | for (const [key, value] of Object.entries(args.headers)) { 60 | if (value != null) { 61 | headers[key] = value; 62 | } 63 | } 64 | } 65 | 66 | const url = createRequestUrl(args.url, args.queryParameters); 67 | let requestBody: BodyInit | undefined = await getRequestBody({ 68 | body: args.body, 69 | type: args.requestType === "json" ? "json" : "other", 70 | }); 71 | const fetchFn = await getFetchFn(); 72 | 73 | try { 74 | const response = await requestWithRetries( 75 | async () => 76 | makeRequest( 77 | fetchFn, 78 | url, 79 | args.method, 80 | headers, 81 | requestBody, 82 | args.timeoutMs, 83 | args.abortSignal, 84 | args.withCredentials, 85 | args.duplex 86 | ), 87 | args.maxRetries 88 | ); 89 | let responseBody = await getResponseBody(response, args.responseType); 90 | 91 | if (response.status >= 200 && response.status < 400) { 92 | return { 93 | ok: true, 94 | body: responseBody as R, 95 | headers: response.headers, 96 | }; 97 | } else { 98 | return { 99 | ok: false, 100 | error: { 101 | reason: "status-code", 102 | statusCode: response.status, 103 | body: responseBody, 104 | }, 105 | }; 106 | } 107 | } catch (error) { 108 | if (args.abortSignal != null && args.abortSignal.aborted) { 109 | return { 110 | ok: false, 111 | error: { 112 | reason: "unknown", 113 | errorMessage: "The user aborted a request", 114 | }, 115 | }; 116 | } else if (error instanceof Error && error.name === "AbortError") { 117 | return { 118 | ok: false, 119 | error: { 120 | reason: "timeout", 121 | }, 122 | }; 123 | } else if (error instanceof Error) { 124 | return { 125 | ok: false, 126 | error: { 127 | reason: "unknown", 128 | errorMessage: error.message, 129 | }, 130 | }; 131 | } 132 | 133 | return { 134 | ok: false, 135 | error: { 136 | reason: "unknown", 137 | errorMessage: JSON.stringify(error), 138 | }, 139 | }; 140 | } 141 | } 142 | 143 | export const fetcher: FetchFunction = fetcherImpl; 144 | -------------------------------------------------------------------------------- /tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts: -------------------------------------------------------------------------------- 1 | import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; 2 | 3 | describe("NodePre18StreamWrapper", () => { 4 | it("should set encoding to utf-8", async () => { 5 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 6 | const stream = new NodePre18StreamWrapper(rawStream); 7 | const setEncodingSpy = jest.spyOn(stream, "setEncoding"); 8 | 9 | stream.setEncoding("utf-8"); 10 | 11 | expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); 12 | }); 13 | 14 | it("should register an event listener for readable", async () => { 15 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 16 | const stream = new NodePre18StreamWrapper(rawStream); 17 | const onSpy = jest.spyOn(stream, "on"); 18 | 19 | stream.on("readable", () => {}); 20 | 21 | expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); 22 | }); 23 | 24 | it("should remove an event listener for data", async () => { 25 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 26 | const stream = new NodePre18StreamWrapper(rawStream); 27 | const offSpy = jest.spyOn(stream, "off"); 28 | 29 | const fn = () => {}; 30 | stream.on("data", fn); 31 | stream.off("data", fn); 32 | 33 | expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); 34 | }); 35 | 36 | it("should write to dest when calling pipe to node writable stream", async () => { 37 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 38 | const stream = new NodePre18StreamWrapper(rawStream); 39 | const dest = new (await import("readable-stream")).Writable({ 40 | write(chunk, encoding, callback) { 41 | expect(chunk.toString()).toEqual("test"); 42 | callback(); 43 | }, 44 | }); 45 | 46 | stream.pipe(dest); 47 | }); 48 | 49 | it("should write nothing when calling pipe and unpipe", async () => { 50 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 51 | const stream = new NodePre18StreamWrapper(rawStream); 52 | const buffer: Uint8Array[] = []; 53 | const dest = new (await import("readable-stream")).Writable({ 54 | write(chunk, encoding, callback) { 55 | buffer.push(chunk); 56 | callback(); 57 | }, 58 | }); 59 | stream.pipe(dest); 60 | stream.unpipe(); 61 | 62 | expect(buffer).toEqual([]); 63 | }); 64 | 65 | it("should destroy the stream", async () => { 66 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 67 | const stream = new NodePre18StreamWrapper(rawStream); 68 | const destroySpy = jest.spyOn(stream, "destroy"); 69 | 70 | stream.destroy(); 71 | 72 | expect(destroySpy).toHaveBeenCalledWith(); 73 | }); 74 | 75 | it("should pause the stream and resume", async () => { 76 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 77 | const stream = new NodePre18StreamWrapper(rawStream); 78 | const pauseSpy = jest.spyOn(stream, "pause"); 79 | 80 | stream.pause(); 81 | expect(stream.isPaused).toBe(true); 82 | stream.resume(); 83 | expect(stream.isPaused).toBe(false); 84 | 85 | expect(pauseSpy).toHaveBeenCalledWith(); 86 | }); 87 | 88 | it("should read the stream", async () => { 89 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 90 | const stream = new NodePre18StreamWrapper(rawStream); 91 | 92 | expect(await stream.read()).toEqual("test"); 93 | expect(await stream.read()).toEqual("test"); 94 | }); 95 | 96 | it("should read the stream as text", async () => { 97 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 98 | const stream = new NodePre18StreamWrapper(rawStream); 99 | 100 | const data = await stream.text(); 101 | 102 | expect(data).toEqual("testtest"); 103 | }); 104 | 105 | it("should read the stream as json", async () => { 106 | const rawStream = (await import("readable-stream")).Readable.from([JSON.stringify({ test: "test" })]); 107 | const stream = new NodePre18StreamWrapper(rawStream); 108 | 109 | const data = await stream.json(); 110 | 111 | expect(data).toEqual({ test: "test" }); 112 | }); 113 | 114 | it("should allow use with async iteratable stream", async () => { 115 | const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); 116 | let data = ""; 117 | const stream = new NodePre18StreamWrapper(rawStream); 118 | for await (const chunk of stream) { 119 | data += chunk; 120 | } 121 | 122 | expect(data).toEqual("testtest"); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /src/core/schemas/builders/record/record.ts: -------------------------------------------------------------------------------- 1 | import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; 2 | import { entries } from "../../utils/entries"; 3 | import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; 4 | import { isPlainObject } from "../../utils/isPlainObject"; 5 | import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; 6 | import { getSchemaUtils } from "../schema-utils"; 7 | import { BaseRecordSchema, RecordSchema } from "./types"; 8 | 9 | export function record( 10 | keySchema: Schema, 11 | valueSchema: Schema 12 | ): RecordSchema { 13 | const baseSchema: BaseRecordSchema = { 14 | parse: (raw, opts) => { 15 | return validateAndTransformRecord({ 16 | value: raw, 17 | isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, 18 | transformKey: (key) => 19 | keySchema.parse(key, { 20 | ...opts, 21 | breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], 22 | }), 23 | transformValue: (value, key) => 24 | valueSchema.parse(value, { 25 | ...opts, 26 | breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], 27 | }), 28 | breadcrumbsPrefix: opts?.breadcrumbsPrefix, 29 | }); 30 | }, 31 | json: (parsed, opts) => { 32 | return validateAndTransformRecord({ 33 | value: parsed, 34 | isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, 35 | transformKey: (key) => 36 | keySchema.json(key, { 37 | ...opts, 38 | breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], 39 | }), 40 | transformValue: (value, key) => 41 | valueSchema.json(value, { 42 | ...opts, 43 | breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], 44 | }), 45 | breadcrumbsPrefix: opts?.breadcrumbsPrefix, 46 | }); 47 | }, 48 | getType: () => SchemaType.RECORD, 49 | }; 50 | 51 | return { 52 | ...maybeSkipValidation(baseSchema), 53 | ...getSchemaUtils(baseSchema), 54 | }; 55 | } 56 | 57 | function validateAndTransformRecord({ 58 | value, 59 | isKeyNumeric, 60 | transformKey, 61 | transformValue, 62 | breadcrumbsPrefix = [], 63 | }: { 64 | value: unknown; 65 | isKeyNumeric: boolean; 66 | transformKey: (key: string | number) => MaybeValid; 67 | transformValue: (value: unknown, key: string | number) => MaybeValid; 68 | breadcrumbsPrefix: string[] | undefined; 69 | }): MaybeValid> { 70 | if (!isPlainObject(value)) { 71 | return { 72 | ok: false, 73 | errors: [ 74 | { 75 | path: breadcrumbsPrefix, 76 | message: getErrorMessageForIncorrectType(value, "object"), 77 | }, 78 | ], 79 | }; 80 | } 81 | 82 | return entries(value).reduce>>( 83 | (accPromise, [stringKey, value]) => { 84 | // skip nullish keys 85 | if (value == null) { 86 | return accPromise; 87 | } 88 | 89 | const acc = accPromise; 90 | 91 | let key: string | number = stringKey; 92 | if (isKeyNumeric) { 93 | const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; 94 | if (!isNaN(numberKey)) { 95 | key = numberKey; 96 | } 97 | } 98 | const transformedKey = transformKey(key); 99 | 100 | const transformedValue = transformValue(value, key); 101 | 102 | if (acc.ok && transformedKey.ok && transformedValue.ok) { 103 | return { 104 | ok: true, 105 | value: { 106 | ...acc.value, 107 | [transformedKey.value]: transformedValue.value, 108 | }, 109 | }; 110 | } 111 | 112 | const errors: ValidationError[] = []; 113 | if (!acc.ok) { 114 | errors.push(...acc.errors); 115 | } 116 | if (!transformedKey.ok) { 117 | errors.push(...transformedKey.errors); 118 | } 119 | if (!transformedValue.ok) { 120 | errors.push(...transformedValue.errors); 121 | } 122 | 123 | return { 124 | ok: false, 125 | errors, 126 | }; 127 | }, 128 | { ok: true, value: {} as Record } 129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Voyage TypeScript Library 2 | 3 | [![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=https%3A%2F%2Fgithub.com%2Fvoyage-ai%2Ftypescript-sdk) 4 | [![npm shield](https://img.shields.io/npm/v/voyageai)](https://www.npmjs.com/package/voyageai) 5 | 6 | The Voyage TypeScript library provides convenient access to the Voyage APIs from TypeScript. 7 | 8 | ## Table of Contents 9 | 10 | - [Documentation](#documentation) 11 | - [Installation](#installation) 12 | - [Batch Request](#batch-request) 13 | - [Usage](#usage) 14 | - [Request and Response Types](#request-and-response-types) 15 | - [Exception Handling](#exception-handling) 16 | - [Advanced](#advanced) 17 | - [Retries](#retries) 18 | - [Timeouts](#timeouts) 19 | - [Aborting Requests](#aborting-requests) 20 | - [Runtime Compatibility](#runtime-compatibility) 21 | - [Contributing](#contributing) 22 | - [Reference](#reference) 23 | 24 | ## Documentation 25 | 26 | API reference documentation is available [here](https://docs.voyageai.com/reference/embeddings-api). 27 | 28 | ## Installation 29 | 30 | ```sh 31 | npm i -s voyageai 32 | ``` 33 | 34 | ## Batch request 35 | 36 | The SDK supports batch requests. Instantiate and use the client similarly as above: 37 | 38 | ```typescript 39 | import { VoyageAIClient } from "voyageai"; 40 | 41 | const client = new VoyageAIClient({ apiKey: "YOUR_API_KEY" }); 42 | await client.embed({ 43 | input: ["input1", "input2", "input3", "input4"], 44 | model: "model", 45 | }); 46 | ``` 47 | 48 | ## Usage 49 | 50 | Instantiate and use the client with the following: 51 | 52 | ```typescript 53 | import { VoyageAIClient } from "voyageai"; 54 | 55 | const client = new VoyageAIClient({ apiKey: "YOUR_API_KEY" }); 56 | await client.embed({ 57 | input: "input", 58 | model: "model", 59 | }); 60 | ``` 61 | 62 | ## Request and Response Types 63 | 64 | The SDK exports all request and response types as TypeScript interfaces. Simply import them with the 65 | following namespace: 66 | 67 | ```typescript 68 | import { VoyageAI } from "voyageai"; 69 | 70 | const request: VoyageAI.EmbedRequest = { 71 | ... 72 | }; 73 | ``` 74 | 75 | ## Exception Handling 76 | 77 | When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error 78 | will be thrown. 79 | 80 | ```typescript 81 | import { VoyageAIError } from "voyageai"; 82 | 83 | try { 84 | await client.embed(...); 85 | } catch (err) { 86 | if (err instanceof VoyageAIError) { 87 | console.log(err.statusCode); 88 | console.log(err.message); 89 | console.log(err.body); 90 | } 91 | } 92 | ``` 93 | 94 | ## Advanced 95 | 96 | ### Retries 97 | 98 | The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long 99 | as the request is deemed retriable and the number of retry attempts has not grown larger than the configured 100 | retry limit (default: 2). 101 | 102 | A request is deemed retriable when any of the following HTTP status codes is returned: 103 | 104 | - [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) 105 | - [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) 106 | - [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) 107 | 108 | Use the `maxRetries` request option to configure this behavior. 109 | 110 | ```typescript 111 | const response = await client.embed(..., { 112 | maxRetries: 0 // override maxRetries at the request level 113 | }); 114 | ``` 115 | 116 | ### Timeouts 117 | 118 | The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. 119 | 120 | ```typescript 121 | const response = await client.embed(..., { 122 | timeoutInSeconds: 30 // override timeout to 30s 123 | }); 124 | ``` 125 | 126 | ### Aborting Requests 127 | 128 | The SDK allows users to abort requests at any point by passing in an abort signal. 129 | 130 | ```typescript 131 | const controller = new AbortController(); 132 | const response = await client.embed(..., { 133 | abortSignal: controller.signal 134 | }); 135 | controller.abort(); // aborts the request 136 | ``` 137 | 138 | ### Runtime Compatibility 139 | 140 | The SDK defaults to `node-fetch` but will use the global fetch client if present. The SDK works in the following 141 | runtimes: 142 | 143 | - Node.js 18+ 144 | - Vercel 145 | - Cloudflare Workers 146 | - Deno v1.25+ 147 | - Bun 1.0+ 148 | - React Native 149 | 150 | ### Customizing Fetch Client 151 | 152 | The SDK provides a way for your to customize the underlying HTTP client / Fetch function. If you're running in an 153 | unsupported environment, this provides a way for you to break glass and ensure the SDK works. 154 | 155 | ```typescript 156 | import { VoyageAIClient } from "voyageai"; 157 | 158 | const client = new VoyageAIClient({ 159 | ... 160 | fetcher: // provide your implementation here 161 | }); 162 | ``` 163 | 164 | ## Contributing 165 | 166 | While we value open-source contributions to this SDK, this library is generated programmatically. 167 | Additions made directly to this library would have to be moved over to our generation code, 168 | otherwise they would be overwritten upon the next generated release. Feel free to open a PR as 169 | a proof of concept, but know that we will not be able to merge it as-is. We suggest opening 170 | an issue first to discuss with us! 171 | 172 | On the other hand, contributions to the README are always very welcome! 173 | 174 | ## Reference 175 | 176 | A full reference for this library is available [here](https://github.com/voyage-ai/typescript-sdk/blob/HEAD/./reference.md). 177 | --------------------------------------------------------------------------------