diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 834b9e0..96b4bee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,14 +2,15 @@ name: Release on: push: - branches: - - main + branches: + - main + - next permissions: contents: write issues: write pull-requests: write - + jobs: release: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 76add87..f06235c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules -dist \ No newline at end of file +dist diff --git a/README.md b/README.md index 1a31a25..b127564 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,14 @@ ```ts import { createClient } from "@tursodatabase/api"; +// Personal account client const turso = createClient({ - org: "", // Your personal account or organization slug + token: "...", +}); + +// Organization account client +const turso = createClient({ + org: "...", // Your organization slug token: "...", }); ``` @@ -51,9 +57,9 @@ const token = await turso.groups.createToken("default", { const token = await turso.groups.createToken("default", { permissions: { read_attach: { - databases: ["db1", "db2"] - } - } + databases: ["db1", "db2"], + }, + }, }); const token = await turso.groups.rotateTokens("default"); ``` @@ -112,9 +118,9 @@ const token = await turso.databases.createToken("my-db", { const token = await turso.databases.createToken("my-db", { permissions: { read_attach: { - databases: ["db1", "db2"] - } - } + databases: ["db1", "db2"], + }, + }, }); const token = await turso.databases.rotateTokens("my-db"); diff --git a/release.config.cjs b/release.config.cjs index aa87816..8cfb9e4 100644 --- a/release.config.cjs +++ b/release.config.cjs @@ -1,3 +1,3 @@ module.exports = { - branches: ["main"], -}; \ No newline at end of file + branches: ["main", { name: "next", prerelease: true }], +}; diff --git a/src/api-token.ts b/src/api-token.ts index 7169800..48488df 100644 --- a/src/api-token.ts +++ b/src/api-token.ts @@ -1,5 +1,4 @@ -import { TursoConfig } from "./config"; -import { TursoClient } from "./client"; +import { TursoClient, type TursoConfig } from "./client"; export interface ApiToken { id: string; @@ -24,7 +23,7 @@ export class ApiTokenClient { async list(): Promise { const response = await TursoClient.request<{ tokens: ApiToken[] }>( - "auth/api-tokens", + `auth/api-tokens`, this.config ); @@ -60,7 +59,7 @@ export class ApiTokenClient { async validate(token: string): Promise { const response = await TursoClient.request<{ exp: number }>( - "auth/api-tokens/validate", + `auth/api-tokens/validate`, this.config, { headers: { diff --git a/src/client.test.ts b/src/client.test.ts index fe23836..6789701 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -3,24 +3,20 @@ import { TursoClient, TursoClientError, createClient } from "./client"; describe("TursoClient", () => { it("should throw an error if no API token is provided", () => { - const config = { org: "turso" }; - // @ts-expect-error - expect(() => new TursoClient(config)).toThrow( + expect(() => new TursoClient({ org: "turso" })).toThrow( "You must provide an API token" ); }); it("should create an instance of TursoClient", () => { - const config = { org: "turso", token: "abc" }; - const client = new TursoClient(config); + const client = new TursoClient({ org: "turso", token: "abc" }); expect(client).toBeInstanceOf(TursoClient); }); it("should throw an error message that will match with API's error message", async () => { - const config = { org: "turso", token: "abc" }; - const client = new TursoClient(config); + const client = new TursoClient({ org: "turso", token: "abc" }); const error = await client.databases .get("databaseName") diff --git a/src/client.ts b/src/client.ts index bf194e3..40cbbf3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,10 +1,11 @@ -import { TursoConfig } from "./config"; import { ApiTokenClient } from "./api-token"; import { OrganizationClient } from "./organization"; import { LocationClient } from "./location"; import { GroupClient } from "./group"; import { DatabaseClient } from "./database"; +export const TURSO_API_URL = "https://api.turso.tech/v1/"; + interface ApiErrorResponse { error: string; } @@ -22,6 +23,26 @@ export class TursoClientError extends Error { } } +/** + * Configuration interface for Turso API client. + */ +export interface TursoConfig { + /** + * Organization identifier. + */ + org?: string; + + /** + * API token for authentication. + */ + token: string; + + /** + * Base URL for the API. Optional and defaults to "https://api.turso.tech/v1/". + */ + baseUrl?: string; +} + export class TursoClient { private config: TursoConfig; public apiTokens: ApiTokenClient; @@ -36,7 +57,7 @@ export class TursoClient { } this.config = { - baseUrl: "https://api.turso.tech/v1/", + baseUrl: TURSO_API_URL, ...config, }; @@ -47,6 +68,14 @@ export class TursoClient { this.databases = new DatabaseClient(this.config); } + /** + * Makes an API request. + * @param url - The endpoint URL. + * @param config - The Turso configuration. + * @param options - The request options. + * @returns The API response. + * @throws TursoClientError if the request fails. + */ static async request( url: string, config: TursoConfig, @@ -58,6 +87,7 @@ export class TursoClient { ...options.headers, Authorization: `Bearer ${config.token}`, "User-Agent": "@tursodatabase/api", + ...(config.org && { "x-turso-organization": config.org }), }, }); @@ -75,6 +105,11 @@ export class TursoClient { } } +/** + * Creates a new TursoClient instance. + * @param config - The Turso configuration. + * @returns The TursoClient instance. + */ export function createClient(config: TursoConfig): TursoClient { return new TursoClient(config); } diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index 755a535..0000000 --- a/src/config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface TursoConfig { - org: string; - token: string; - baseUrl?: string; -} diff --git a/src/database.ts b/src/database.ts index c1a68ce..7d6f03f 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,6 +1,5 @@ +import { TursoClient, type TursoConfig } from "./client"; import { LocationKeys } from "./location"; -import { TursoConfig } from "./config"; -import { TursoClient } from "./client"; export interface Database { name: string; @@ -101,7 +100,7 @@ export class DatabaseClient { async list(): Promise { const response = await TursoClient.request<{ databases: ApiDatabaseResponse[]; - }>(`organizations/${this.config.org}/databases`, this.config); + }>(`databases`, this.config); return (response.databases ?? []).map((db) => this.formatResponse(db)); } @@ -109,7 +108,7 @@ export class DatabaseClient { async get(dbName: string): Promise { const response = await TursoClient.request<{ database: ApiDatabaseResponse; - }>(`organizations/${this.config.org}/databases/${dbName}`, this.config); + }>(`databases/${dbName}`, this.config); return this.formatResponse(response.database); } @@ -146,7 +145,7 @@ export class DatabaseClient { const response = await TursoClient.request<{ database: ApiCreateDatabaseResponse; - }>(`organizations/${this.config.org}/databases`, this.config, { + }>(`databases`, this.config, { method: "POST", headers: { "content-type": "application/json", @@ -162,7 +161,7 @@ export class DatabaseClient { async updateVersion(dbName: string): Promise { return await TursoClient.request( - `organizations/${this.config.org}/databases/${dbName}/update`, + `databases/${dbName}/update`, this.config, { method: "POST", @@ -172,7 +171,7 @@ export class DatabaseClient { async delete(dbName: string) { const response = await TursoClient.request( - `organizations/${this.config.org}/databases/${dbName}`, + `databases/${dbName}`, this.config, { method: "DELETE", @@ -185,10 +184,7 @@ export class DatabaseClient { async listInstances(dbName: string): Promise { const response = await TursoClient.request<{ instances: DatabaseInstance[]; - }>( - `organizations/${this.config.org}/databases/${dbName}/instances`, - this.config - ); + }>(`databases/${dbName}/instances`, this.config); return response.instances ?? []; } @@ -199,10 +195,7 @@ export class DatabaseClient { ): Promise { const response = await TursoClient.request<{ instance: DatabaseInstance; - }>( - `organizations/${this.config.org}/databases/${dbName}/instances/${instanceName}`, - this.config - ); + }>(`databases/${dbName}/instances/${instanceName}`, this.config); return response.instance ?? null; } @@ -228,7 +221,7 @@ export class DatabaseClient { } const response = await TursoClient.request( - `organizations/${this.config.org}/databases/${dbName}/auth/tokens?${queryParams}`, + `databases/${dbName}/auth/tokens?${queryParams}`, this.config, { method: "POST", @@ -247,7 +240,7 @@ export class DatabaseClient { async rotateTokens(dbName: string): Promise { return await TursoClient.request( - `organizations/${this.config.org}/databases/${dbName}/auth/rotate`, + `databases/${dbName}/auth/rotate`, this.config, { method: "POST", @@ -273,10 +266,7 @@ export class DatabaseClient { database: DatabaseUsage; instances: InstanceUsages; total: TotalUsage; - }>( - `organizations/${this.config.org}/databases/${dbName}/usage?${queryParams}`, - this.config - ); + }>(`databases/${dbName}/usage?${queryParams}`, this.config); return response.database; } diff --git a/src/group.ts b/src/group.ts index 83dd273..59bba61 100644 --- a/src/group.ts +++ b/src/group.ts @@ -1,6 +1,5 @@ -import { TursoConfig } from "./config"; +import { TursoClient, type TursoConfig } from "./client"; import { LocationKeys } from "./location"; -import { TursoClient } from "./client"; import type { Database } from "./database"; export interface Group { @@ -30,7 +29,7 @@ export class GroupClient { async list(): Promise { const response = await TursoClient.request<{ groups: Group[] }>( - `organizations/${this.config.org}/groups`, + `groups`, this.config ); @@ -39,7 +38,7 @@ export class GroupClient { async get(name: string): Promise { const response = await TursoClient.request<{ group: Group }>( - `organizations/${this.config.org}/groups/${name}`, + `groups/${name}`, this.config ); @@ -52,7 +51,7 @@ export class GroupClient { options?: { extensions?: Array | "all" } ): Promise { const response = await TursoClient.request<{ group: Group }>( - `organizations/${this.config.org}/groups`, + `groups`, this.config, { method: "POST", @@ -72,7 +71,7 @@ export class GroupClient { async delete(name: string): Promise { const response = await TursoClient.request<{ group: Group }>( - `organizations/${this.config.org}/groups/${name}`, + `groups/${name}`, this.config, { method: "DELETE", @@ -87,7 +86,7 @@ export class GroupClient { location: keyof LocationKeys ): Promise { const response = await TursoClient.request<{ group: Group }>( - `organizations/${this.config.org}/groups/${groupName}/locations/${location}`, + `groups/${groupName}/locations/${location}`, this.config, { method: "POST", @@ -102,7 +101,7 @@ export class GroupClient { location: keyof LocationKeys ): Promise { const response = await TursoClient.request<{ group: Group }>( - `organizations/${this.config.org}/groups/${groupName}/locations/${location}`, + `groups/${groupName}/locations/${location}`, this.config, { method: "DELETE", @@ -133,7 +132,7 @@ export class GroupClient { } const response = await TursoClient.request( - `organizations/${this.config.org}/groups/${groupName}/auth/tokens?${queryParams}`, + `groups/${groupName}/auth/tokens?${queryParams}`, this.config, { method: "POST", @@ -152,7 +151,7 @@ export class GroupClient { async rotateTokens(groupName: string): Promise { return await TursoClient.request( - `organizations/${this.config.org}/groups/${groupName}/auth/rotate`, + `groups/${groupName}/auth/rotate`, this.config, { method: "POST", diff --git a/src/index.ts b/src/index.ts index 5144281..189bd5d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,32 +2,9 @@ import "whatwg-fetch"; export { createClient } from "./client"; -export type { - ApiToken, - ApiTokenWithJWT, - RevokedApiToken, - ApiTokenValidation, -} from "./api-token"; -export type { TursoClientError } from "./client"; -export type { TursoConfig } from "./config"; -export type { - Database, - CreatedDatabase, - DatabaseUsage, - InstanceUsages, - TotalUsage, - DatabaseInstance, - DeletedDatabase, - DatabaseToken, -} from "./database"; -export type { Group, ExtensionType, GroupToken } from "./group"; -export type { LocationKeys, Location, ClosestLocation } from "./location"; -export type { - Organization, - OrganizationMember, - OrganizationInvite, - Invoice, - OrganizationMemberRole, - OrganizationAddedMember, - OrganizationRemovedMember, -} from "./organization"; +export * from "./api-token"; +export * from "./client"; +export * from "./database"; +export * from "./group"; +export * from "./location"; +export * from "./organization"; diff --git a/src/location.ts b/src/location.ts index afcb03b..10599d8 100644 --- a/src/location.ts +++ b/src/location.ts @@ -1,5 +1,4 @@ -import { TursoConfig } from "./config"; -import { TursoClient } from "./client"; +import { TursoClient, type TursoConfig } from "./client"; export type LocationKeys = { ams: string; @@ -51,7 +50,7 @@ export class LocationClient { async list(): Promise { const response = await TursoClient.request<{ locations: LocationKeys; - }>("locations", this.config); + }>(`locations`, this.config); if (!response.locations) { return []; diff --git a/src/organization.ts b/src/organization.ts index accb1ac..648bf5c 100644 --- a/src/organization.ts +++ b/src/organization.ts @@ -1,5 +1,4 @@ -import { TursoConfig } from "./config"; -import { TursoClient } from "./client"; +import { TursoClient, type TursoConfig } from "./client"; export interface Organization { name: string; @@ -10,8 +9,10 @@ export interface Organization { blocked_writes: boolean; } +export type OrganizationMemberRole = "admin" | "member"; + export interface OrganizationMember { - role: "owner" | "admin" | "member"; + role: "owner" | OrganizationMemberRole; username: string; email: string; } @@ -37,8 +38,6 @@ export interface Invoice { invoice_pdf: string; } -export type OrganizationMemberRole = "admin" | "member"; - export interface OrganizationAddedMember { member: string; role: OrganizationMemberRole; @@ -54,7 +53,7 @@ export class OrganizationClient { async list(): Promise { const response = await TursoClient.request<{ organizations: Organization[]; - }>("organizations", this.config); + }>(``, this.config); return response.organizations ?? []; } @@ -62,7 +61,7 @@ export class OrganizationClient { async update(options: { overages: boolean }): Promise { const response = await TursoClient.request<{ organization: Organization; - }>(`organizations/${this.config.org}`, this.config, { + }>(``, this.config, { method: "PATCH", body: JSON.stringify(options), }); @@ -71,19 +70,15 @@ export class OrganizationClient { } async delete(): Promise { - return TursoClient.request( - `organizations/${this.config.org}`, - this.config, - { - method: "DELETE", - } - ); + return TursoClient.request(``, this.config, { + method: "DELETE", + }); } async members(): Promise { const response = await TursoClient.request<{ members: OrganizationMember[]; - }>(`organizations/${this.config.org}/members`, this.config); + }>(`members`, this.config); return response.members ?? []; } @@ -92,27 +87,19 @@ export class OrganizationClient { username: string, role?: "admin" | "member" ): Promise { - return TursoClient.request( - `organizations/${this.config.org}/members/${username}`, - this.config, - { - method: "POST", - body: JSON.stringify({ - member: username, - role: role ? role : "member", - }), - } - ); + return TursoClient.request(`members/${username}`, this.config, { + method: "POST", + body: JSON.stringify({ + member: username, + role: role ? role : "member", + }), + }); } async removeMember(username: string): Promise { - return TursoClient.request( - `organizations/${this.config.org}/members/${username}`, - this.config, - { - method: "DELETE", - } - ); + return TursoClient.request(`members/${username}`, this.config, { + method: "DELETE", + }); } async inviteUser( @@ -120,7 +107,7 @@ export class OrganizationClient { role?: OrganizationMemberRole ): Promise { const response = await TursoClient.request<{ invited: OrganizationInvite }>( - `organizations/${this.config.org}/invites`, + `invites`, this.config, { method: "POST", @@ -132,19 +119,15 @@ export class OrganizationClient { } async deleteInvite(email: string): Promise { - return TursoClient.request( - `organizations/${this.config.org}/invites/${email}`, - this.config, - { - method: "DELETE", - } - ); + return TursoClient.request(`invites/${email}`, this.config, { + method: "DELETE", + }); } async invoices(): Promise { const response = await TursoClient.request<{ invoices: Invoice[]; - }>(`organizations/${this.config.org}/invoices`, this.config); + }>(`invoices`, this.config); return response.invoices ?? []; }