2
0

first commit

This commit is contained in:
2024-08-09 00:39:27 +02:00
commit 79688abe2e
5698 changed files with 497838 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
module.exports = {
git: {
commitMessage: "chore: SDK Release v${version}",
requireUpstream: true,
requireCommits: true,
},
};

View File

@@ -0,0 +1,67 @@
<p align="center">
<a href="https://github.com/calcom/cal.com">
<img src="https://user-images.githubusercontent.com/8019099/210054112-5955e812-a76e-4160-9ddd-58f2c72f1cce.png" alt="Logo">
</a>
<br/>
<strong>Cal.com SDK</strong>
</p>
![SDK Version](https://img.shields.io/github/package-json/v/calcom/cal.com/main?filename=packages%2Fplatform%2Fsdk%2Fpackage.json)
## Install
```bash
yarn add @calcom/sdk
```
## Usage
To use the Cal.com SDK you need to have an OAuth Client set up, which you can obtain [here](https://app.cal.com/settings/organizations/platform/oauth-clients/).
```typescript
import { Cal } from "@calcom/sdk";
const sdk = new Cal("your_client_id", {
clientSecret: "your_client_secret",
});
```
### Authenticating as a User
The SDK is also meant to be used as an authenticated user, to do that, you need to pass the `accessToken` to the `authOptions` in the SDK constructor.
```typescript
const authedSdk = new Cal("your_client_id", {
clientSecret: "your_client_secret",
accessToken: "your_user_access_token"
});
const schedule = await authedSdk.schedules.createSchedule({
availabilities: [
{
days: [1, 2, 3, 4, 5, 6],
startTime: "09:00:00",
endTime: "17:00:00",
},
],
isDefault: true,
name: "Default Schedule",
timeZone: "America/Argentina/Buenos_Aires",
});
```
You can manually refresh access tokens, or you can let the SDK handle token refreshes via the `handleRefresh` option.
To manually update an access token, you can use the following snippet:
```typescript
sdk.secrets().updateAccessToken(oauth.accessToken, oauth.refreshToken);
```
## Configuration
| Option | Required | Description |
|----------------------------|----------|-----------------------------------------------------------------------------------------------------|
| `authOptions.clientSecret` | `TRUE` | The Client Secret corresponding to the client ID passed as the first parameter. |
| `authOptions.accessToken` | `FALSE` | `Optional` Access token when authenticating as a specific user. |
| `authOptions.refreshToken` | `FALSE` | `Optional` If provided, the SDK can handle refreshing access tokens automatically when they expire. |
| `options.baseUrl` | `FALSE` | `Defaults to https://api.cal.com`. The base URI for the Cal.com platform API |
| `options.handleRefresh` | `FALSE` | Whether the SDK should handle automatic refreshes for expired access tokens. |

View File

@@ -0,0 +1,54 @@
import { CalSdk } from "../src/cal";
(async () => {
const sdk = new CalSdk(
"cltd1rit60001p51en7z0sq75",
{
accessToken:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiY2xpZW50SWQiOiJjbHRkMXJpdDYwMDAxcDUxZW43ejBzcTc1Iiwib3duZXJJZCI6OSwiaWF0IjoxNzA5NTYzNTMyfQ.tpUSk0gREAZPJA2aXnHPpVc-9Bbcj2w90kVH2Qii6RE",
clientSecret:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCBjbGllbnQiLCJwZXJtaXNzaW9ucyI6MjU1LCJyZWRpcmVjdFVyaXMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwL2F1dGgvcGxhdGZvcm0vYXV0aG9yaXplIl0sImlhdCI6MTcwOTU2MzE1N30.j8cxam5pfPG45BAMCuXt7bm3GM7JO_UnWL9wPVGcr5U",
refreshToken:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaF90b2tlbiIsImNsaWVudElkIjoiY2x0ZDFyaXQ2MDAwMXA1MWVuN3owc3E3NSIsIm93bmVySWQiOjksImlhdCI6MTcwOTU2MzUzMn0.SHMikF_9uJvqJFAiWAnI0BsRQSJCTbaAXPi1B99sWNk",
},
{
baseUrl: "http://localhost:5555/api",
handleRefresh: true,
httpRetries: {
maxAmount: 5,
},
}
);
try {
const oauth = await sdk.oauth.exchange({
authorizationCode: "cltd1wlqn0002p51ektm2byp6",
});
console.log("finalized oauth", oauth.accessToken);
// force updating
sdk.secrets().updateAccessToken(oauth.accessToken, oauth.refreshToken);
const schedule = await sdk.schedules.createSchedule({
availabilities: [
{
days: [1, 2, 3, 4, 5, 6],
startTime: "10:00:00",
endTime: "14:00:00",
},
],
isDefault: true,
name: "Default Schedule Test",
timeZone: "America/Argentina/Buenos_Aires",
});
console.log(schedule);
const deleted = await sdk.schedules.deleteSchedule(schedule.id);
console.log(deleted);
} catch (err) {
console.log("error", err);
}
})();

View File

@@ -0,0 +1,35 @@
{
"name": "@calcom/sdk",
"version": "1.0.1",
"description": "Cal.com platform SDK.",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "rollup -c --bundleConfigAsCjs",
"release": "release-it --only-version",
"generate-types": "openapi-typescript http://localhost:5555/docs-yaml -o ./src/swagger-types.d.ts"
},
"packageManager": "yarn@3.4.1",
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"openapi-typescript": "^6.7.5",
"release-it": "^17.1.1",
"rollup": "^4.12.0",
"rollup-plugin-bundle-size": "^1.0.3",
"ts-essentials": "^9.4.1",
"typescript": "^5.3.3"
},
"dependencies": {
"axios": "^1.6.7",
"axios-retry": "^4.0.0",
"lodash": "^4.17.21",
"zod": "^3.22.4"
}
}

View File

@@ -0,0 +1,30 @@
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import typescript from "@rollup/plugin-typescript";
import bundleSize from "rollup-plugin-bundle-size";
import pkg from "./package.json";
/** @type {import('rollup').RollupOptions} */
// eslint-disable-next-line import/no-anonymous-default-export
export default {
input: "src/index.ts",
output: [
{
file: pkg.main,
format: "cjs",
},
{
file: pkg.module,
format: "es",
},
],
plugins: [
json(),
commonjs(),
nodeResolve(),
typescript({ tsconfig: "./tsconfig.build.json" }),
bundleSize(),
],
};

View File

@@ -0,0 +1,78 @@
import axios from "axios";
import axiosRetry from "axios-retry";
import { Bookings } from "./endpoints/bookings";
import { Events } from "./endpoints/events";
import { EventTypes } from "./endpoints/events/event-types";
import { ManagedUsers } from "./endpoints/managed-users";
import { OAuthFlow } from "./endpoints/oauth-flow";
import { Schedules } from "./endpoints/schedules";
import { Slots } from "./endpoints/slots";
import { SdkInitializationError } from "./lib/errors/sdk-initialization-error";
import { HttpCaller } from "./lib/http-caller";
import { SdkSecrets } from "./lib/sdk-secrets";
import type { CalSdkConstructorOptions, SdkAuthOptions } from "./types";
export class CalSdk {
public httpCaller: HttpCaller;
slots: Slots;
bookings: Bookings;
events: Events;
oauth: OAuthFlow;
eventTypes: EventTypes;
schedules: Schedules;
users: ManagedUsers;
private readonly _secrets: SdkSecrets;
constructor(
public readonly clientId: string,
protected readonly authOptions: SdkAuthOptions,
protected readonly options: CalSdkConstructorOptions = {
baseUrl: "https://api.cal.com/", // don't set api version here as endpoints may have version-neutral or specific values.
}
) {
if (!authOptions.accessToken && !authOptions.clientSecret) {
throw new SdkInitializationError("Either 'accessToken' or 'clientSecret' are required in authOptions");
}
this.httpCaller = new HttpCaller(this.clientId, this._createAxiosClientBase());
this._secrets = new SdkSecrets(
authOptions.clientSecret ?? "",
authOptions.accessToken ?? "",
authOptions.refreshToken ?? "",
this.httpCaller
);
// avoid cyclic referencing.
this.httpCaller.secrets = this._secrets;
this.slots = new Slots(this);
this.bookings = new Bookings(this);
this.events = new Events(this);
this.oauth = new OAuthFlow(this);
this.eventTypes = new EventTypes(this);
this.schedules = new Schedules(this);
this.users = new ManagedUsers(this);
}
private _createAxiosClientBase() {
const axiosClient = axios.create({
baseURL: this.options.baseUrl,
});
// implement retry logic with an exponential back-off delay
axiosRetry(axiosClient, {
retries: this.options.httpRetries?.maxAmount ?? 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: axiosRetry.isNetworkOrIdempotentRequestError,
});
return axiosClient;
}
public secrets() {
return this._secrets;
}
}

View File

@@ -0,0 +1,8 @@
import type { CalSdk } from "../../cal";
import { EndpointHandler } from "../endpoint-handler";
export class Bookings extends EndpointHandler {
constructor(private readonly sdk: CalSdk) {
super("bookings", sdk);
}
}

View File

@@ -0,0 +1,34 @@
import type { AxiosRequestConfig } from "axios";
// eslint-disable-next-line no-restricted-imports
import merge from "lodash/merge";
import { assert } from "ts-essentials";
import type { CalSdk } from "../cal";
export abstract class EndpointHandler {
protected constructor(private readonly key: string, private readonly calSdk: CalSdk) {}
withForAtomParam(forAtom: boolean, config?: AxiosRequestConfig<unknown>) {
if (!forAtom) return config;
return merge(config, {
params: {
for: "atom",
},
});
}
protected assertAccessToken(methodName: string) {
assert(
this.calSdk.secrets().isAccessTokenSet(),
`Access token must be set to call the ${this.key}/${methodName} function.`
);
}
protected assertClientSecret(methodName: string) {
assert(
!!this.calSdk.secrets().getClientSecret(),
`Client secret must be set to use the ${this.key}/${methodName} function.`
);
}
}

View File

@@ -0,0 +1,38 @@
import type { CalSdk } from "../../../cal";
import { Endpoints } from "../../../lib/endpoints";
import type { BasicPlatformResponse } from "../../../types";
import { EndpointHandler } from "../../endpoint-handler";
import type { CreateEventTypeArgs, EventType, GetEventTypeByIdArgs } from "./types";
export class EventTypes extends EndpointHandler {
constructor(private readonly sdk: CalSdk) {
super("event-types", sdk);
}
async createEventType(args: CreateEventTypeArgs): Promise<EventType> {
this.assertAccessToken("createEventType");
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<EventType>>(
Endpoints.CREATE_EVENT_TYPE,
{
body: args,
}
);
return data;
}
async getEventType(args: GetEventTypeByIdArgs): Promise<EventType> {
this.assertAccessToken("getEventType");
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<EventType>>(
Endpoints.GET_EVENT_TYPE_BY_ID,
{
urlParams: [args.id],
config: this.withForAtomParam(args.forAtom ?? false),
}
);
return data;
}
}

View File

@@ -0,0 +1,65 @@
import { z } from "zod";
const CreateEventTypesSchema = z.object({
length: z.number().min(1),
slug: z.string(),
title: z.string(),
});
const GetEventTypeByIdSchema = z.object({
id: z.string(),
forAtom: z.boolean().optional(),
});
export type CreateEventTypeArgs = z.infer<typeof CreateEventTypesSchema>;
export type GetEventTypeByIdArgs = z.infer<typeof GetEventTypeByIdSchema>;
export type EventType = {
id: string;
title: string;
slug: string;
description: string | null;
position: number;
locations: string[] | null;
length: number;
offsetStart: number;
hidden: boolean;
userId: number;
profileId: number | null;
teamId: number | null;
eventName: string | null;
parentId: number | null;
bookingFields: unknown | null;
timeZone: string | null;
periodType: "UNLIMITED" | "ROLLING" | "RANGE";
periodStartDate: string | null;
periodEndDate: string | null;
periodDays: number | null;
periodCountCalendarDays: boolean | null;
lockTimeZoneToggleOnBookingPage: boolean;
requiresConfirmation: boolean;
requiresBookerEmailVerification: boolean;
recurringEvent?: boolean;
disableGuests: boolean;
hideCalendarNotes: boolean;
minimumBookingNotice: number;
beforeEventBuffer: number;
afterEventBuffer: number;
seatsPerTimeSlot: number | null;
onlyShowFirstAvailableSlot: boolean;
seatsShowAttendees: boolean;
seatsShowAvailabilityCount: boolean;
schedulingType: unknown | null;
scheduleId: number | null;
price: number;
currency: "usd";
slotInterval: number | null;
metadata: Record<string, unknown> | null;
successRedirectUrl: string | null;
bookingLimits: number | null;
durationLimits: number | null;
isInstantEvent: boolean;
instantMeetingExpiryTimeOffsetInSeconds: number;
assignAllTeamMembers: boolean;
useEventTypeDestinationCalendarEmail: boolean;
};

View File

@@ -0,0 +1,21 @@
import type { CalSdk } from "../../cal";
import { Endpoints } from "../../lib/endpoints";
import type { BasicPlatformResponse } from "../../types";
import { EndpointHandler } from "../endpoint-handler";
import type { Event, GetPublicEventArgs } from "./types";
export class Events extends EndpointHandler {
constructor(private readonly sdk: CalSdk) {
super("events", sdk);
}
async getPublicEvent(input: GetPublicEventArgs): Promise<Event> {
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Event>>(Endpoints.GET_PUBLIC_EVENT, {
config: {
params: input,
},
});
return data;
}
}

View File

@@ -0,0 +1,83 @@
import { z } from "zod";
const GetPublicEventSchema = z.object({
username: z.string().transform((s) => s.toLowerCase()),
eventSlug: z.string(),
isTeamEvent: z.boolean().optional(),
org: z.string().optional(),
});
export type GetPublicEventArgs = z.infer<typeof GetPublicEventSchema>;
type User = {
// todo expand
};
type TeamParent = {
slug: string;
name: string;
};
type Team = {
parentId: string;
metadata: Record<string, unknown>;
brandColor: string;
darkBrandColor: string;
slug: string;
name: string;
logo: string;
theme: string;
parent: TeamParent;
};
type WorkflowStep = {
// todo expand
};
type Workflow = {
steps: WorkflowStep[];
};
export type Event = {
id: string;
title: string;
description: string;
eventName: string;
slug: string;
isInstantEvent: boolean;
instantMeetingExpiryTimeOffsetInSeconds: number;
aiPhoneCallConfig: {
eventTypeId: number;
enabled: boolean;
generalPrompt: string;
beginMessage: string | null;
yourPhoneNumber: string;
numberToCall: string;
guestName: string;
guestEmail: string;
guestCompany: string;
};
schedulingType: string;
length: number;
locations: string[]; // Define more specifically if possible
customInputs: unknown; // Define more specifically if possible
disableGuests: boolean;
lockTimeZoneToggleOnBookingPage: boolean;
requiresConfirmation: boolean;
requiresBookerEmailVerification: boolean;
recurringEvent: boolean;
price: number;
currency: string;
seatsPerTimeSlot: number;
seatsShowAvailabilityCount: boolean;
bookingFields: unknown; // Define more specifically if possible
team: Team;
successRedirectUrl: string;
workflows: Workflow[];
hosts: {
user: User;
}[];
owner: User;
hidden: boolean;
assignAllTeamMembers: boolean;
};

View File

@@ -0,0 +1,54 @@
import type { CalSdk } from "../../cal";
import { Endpoints } from "../../lib/endpoints";
import type { BasicPlatformResponse, PaginationOptions } from "../../types";
import { EndpointHandler } from "../endpoint-handler";
import type { CreateUserArgs, CreateUserResponse, User } from "./types";
export class ManagedUsers extends EndpointHandler {
constructor(private readonly sdk: CalSdk) {
super("users", sdk);
}
async getManagedUsers(pagination?: PaginationOptions): Promise<User> {
this.assertClientSecret("getManagedUsers");
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<User>>(
Endpoints.GET_MANAGED_USERS_BY_CLIENT_ID,
{
urlParams: [this.sdk.clientId],
config: {
params: pagination,
},
}
);
return data;
}
async createManagedUser(input: CreateUserArgs): Promise<CreateUserResponse> {
this.assertClientSecret("createManagedUser");
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<CreateUserResponse>>(
Endpoints.CREATE_MANAGED_USER,
{
urlParams: [this.sdk.clientId],
body: input,
}
);
return data;
}
async getManagedUser(userId: number): Promise<User> {
this.assertClientSecret("getManagedUser");
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<User>>(
Endpoints.GET_MANAGED_USER_BY_ID,
{
urlParams: [this.sdk.clientId, userId.toString()],
}
);
return data;
}
}

View File

@@ -0,0 +1,23 @@
import { z } from "zod";
export type User = {
id: number;
email: string;
username: string;
};
const CreateUserSchema = z.object({
email: z.string(),
name: z.string().optional(),
timeFormat: z.number().optional(),
isWeekStart: z.string().optional(),
timeZone: z.string().optional(),
});
export type CreateUserArgs = z.infer<typeof CreateUserSchema>;
export type CreateUserResponse = {
user: User;
accessToken: string;
refreshToken: string;
};

View File

@@ -0,0 +1,32 @@
import type { CalSdk } from "../../cal";
import { Endpoints } from "../../lib/endpoints";
import type { BasicPlatformResponse } from "../../types";
import { EndpointHandler } from "../endpoint-handler";
import type { ExchangeCodeParams, ExchangeCodeResponse } from "./types";
export class OAuthFlow extends EndpointHandler {
constructor(private readonly sdk: CalSdk) {
super("oauth", sdk);
}
async exchange(params: ExchangeCodeParams): Promise<ExchangeCodeResponse> {
this.assertClientSecret("exchange");
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<ExchangeCodeResponse>>(
Endpoints.EXCHANGE_OAUTH_AUTH_TOKEN,
{
urlParams: [this.sdk.clientId],
body: {
clientSecret: this.sdk.secrets().getClientSecret(),
},
config: {
headers: {
Authorization: `Bearer ${params.authorizationCode}`,
},
},
}
);
return data;
}
}

View File

@@ -0,0 +1,12 @@
import { z } from "zod";
const ExchangeCodeSchema = z.object({
authorizationCode: z.string(),
});
export type ExchangeCodeParams = z.infer<typeof ExchangeCodeSchema>;
export type ExchangeCodeResponse = {
accessToken: string;
refreshToken: string;
};

View File

@@ -0,0 +1,106 @@
import type { CalSdk } from "../../cal";
import { Endpoints } from "../../lib/endpoints";
import type { BasicPlatformResponse } from "../../types";
import { EndpointHandler } from "../endpoint-handler";
import type {
CreateScheduleArgs,
FormattedSchedule,
GetScheduleByIdArgs,
Schedule,
SupportedTimezone,
UpdateScheduleArgs,
} from "./types";
export class Schedules extends EndpointHandler {
constructor(private readonly sdk: CalSdk) {
super("schedules", sdk);
}
async createSchedule(args: CreateScheduleArgs): Promise<Schedule> {
this.assertAccessToken("createSchedule");
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<Schedule>>(
Endpoints.CREATE_SCHEDULE,
{
body: args,
config: this.withForAtomParam(args.forAtom ?? false),
}
);
return data;
}
async getDefaultSchedule(forAtom?: boolean): Promise<Schedule | FormattedSchedule> {
this.assertAccessToken("getDefaultSchedule");
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Schedule | FormattedSchedule>>(
Endpoints.GET_DEFAULT_SCHEDULE,
{
config: this.withForAtomParam(forAtom ?? false),
}
);
return data;
}
async getSchedules(forAtom?: boolean): Promise<Schedule[] | FormattedSchedule[]> {
this.assertAccessToken("getSchedules");
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Schedule[] | FormattedSchedule[]>>(
Endpoints.GET_ALL_SCHEDULES,
{
config: this.withForAtomParam(forAtom ?? false),
}
);
return data;
}
async getScheduleById(args: GetScheduleByIdArgs): Promise<Schedule | FormattedSchedule> {
this.assertAccessToken("getScheduleById");
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Schedule | FormattedSchedule>>(
Endpoints.GET_SCHEDULE_BY_ID,
{
urlParams: [args.id.toString()],
config: this.withForAtomParam(args.forAtom ?? false),
}
);
return data;
}
async getSupportedTimezones(): Promise<SupportedTimezone[]> {
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<SupportedTimezone[]>>(
Endpoints.GET_SUPPORTED_TIMEZONES
);
return data;
}
async updateSchedule(scheduleId: number, args: UpdateScheduleArgs) {
this.assertAccessToken("updateSchedule");
const { data } = await this.sdk.httpCaller.patch<BasicPlatformResponse<Schedule>>(
Endpoints.UPDATE_SCHEDULE_BY_ID,
{
urlParams: [scheduleId.toString()],
body: args,
}
);
return data;
}
async deleteSchedule(scheduleId: number): Promise<boolean> {
this.assertAccessToken("deleteSchedule");
const { status } = await this.sdk.httpCaller.delete<BasicPlatformResponse>(
Endpoints.DELETE_SCHEDULE_BY_ID,
{
urlParams: [scheduleId.toString()],
}
);
return status === "success";
}
}

View File

@@ -0,0 +1,63 @@
import type { ElementOf } from "ts-essentials";
import { z } from "zod";
const CreateScheduleSchema = z.object({
name: z.string(),
timeZone: z.string(),
isDefault: z.boolean().default(true),
availabilities: z
.object({
days: z.number().array(),
startTime: z.string(),
endTime: z.string(),
})
.array(),
forAtom: z.boolean().optional(),
});
const GetScheduleByIdSchema = z.object({
id: z.number(),
forAtom: z.boolean().optional(),
});
const UpdateScheduleSchema = z.object({
timeZone: z.string().optional(),
name: z.string().optional(),
isDefault: z.boolean().optional(),
schedule: z
.object({
start: z.date(),
end: z.date(),
})
.array()
.optional(),
dateOverrides: z.object({
ranges: z.object({}),
}),
});
export type CreateScheduleArgs = z.infer<typeof CreateScheduleSchema>;
export type GetScheduleByIdArgs = z.infer<typeof GetScheduleByIdSchema>;
export type UpdateScheduleArgs = z.infer<typeof UpdateScheduleSchema>;
export type Schedule = CreateScheduleArgs & {
id: number;
userId: number;
};
export type FormattedSchedule = {
id: number;
name: string;
isManaged: boolean;
schedule: ElementOf<Schedule["availabilities"]>;
timeZone: string;
isDefault: boolean;
isLastSchedule: boolean;
readOnly: boolean;
};
export type SupportedTimezone = {
city: string;
timezone: string;
pop: number;
};

View File

@@ -0,0 +1,46 @@
import type { CalSdk } from "../../cal";
import { Endpoints } from "../../lib/endpoints";
import { type BasicPlatformResponse, type ResponseStatus } from "../../types";
import { EndpointHandler } from "../endpoint-handler";
import type {
AvailableSlots,
GetAvaialbleSlotsArgs,
RemoveSelectedSlotArgs,
ReserveSlotArgs,
SlotUID,
} from "./types";
export class Slots extends EndpointHandler {
constructor(private readonly sdk: CalSdk) {
super("slots", sdk);
}
async reserveSlot(args: ReserveSlotArgs): Promise<SlotUID> {
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<SlotUID>>(Endpoints.RESERVE_SLOT, {
body: args,
});
return data;
}
async removeSelectedSlot(args: RemoveSelectedSlotArgs): Promise<ResponseStatus> {
const { status } = await this.sdk.httpCaller.delete<BasicPlatformResponse>(
Endpoints.DELETE_SELECTED_SLOT,
{
config: { params: args },
}
);
return status === "success" ? "success" : "error";
}
async getAvailableSlots(args: GetAvaialbleSlotsArgs): Promise<AvailableSlots> {
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<AvailableSlots>>(
Endpoints.AVAILABLE_SLOTS,
{
config: {
params: args,
},
}
);
return data;
}
}

View File

@@ -0,0 +1,38 @@
import { z } from "zod";
export type SlotUID = string;
type Slot = Record<
SlotUID,
{
time: string;
attendees?: number;
bookingUid?: string;
}
>;
export type AvailableSlots = Slot[];
const ReserveSlotSchema = z.object({
eventTypeId: z.number(),
slotUtcStartDate: z.date(),
slotUtcEndDate: z.date(),
bookingUid: z.string().optional(),
});
const RemoveSelectedSlotSchema = z.object({
uid: z.string().optional(),
});
const GetAvailableSlotsSchema = z.object({
startTime: z.date(),
endTime: z.date(),
eventTypeId: z.number().optional(),
usernameList: z.string().array().optional(),
debug: z.boolean().optional(),
duration: z.number().optional(),
});
export type ReserveSlotArgs = z.infer<typeof ReserveSlotSchema>;
export type RemoveSelectedSlotArgs = z.infer<typeof RemoveSelectedSlotSchema>;
export type GetAvaialbleSlotsArgs = z.infer<typeof GetAvailableSlotsSchema>;

View File

@@ -0,0 +1,2 @@
export { CalSdk as Cal } from "./cal";
export * from "./types";

View File

@@ -0,0 +1,184 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ApiVersion } from "../types";
type BaseEndpointDeclaration = {
auth: "public" | "access_token" | "secret";
apiVersion: ApiVersion;
};
// Define a type for static URIs
type StaticUriEndpoint = BaseEndpointDeclaration & {
uri: string;
constructUriFromObject?: never;
constructUriFromArray?: never;
};
// Introduce a generic type for dynamic URIs, allowing for explicit parameter type declaration
type DynamicUriEndpoint = BaseEndpointDeclaration & {
uri?: never; // Ensure uri is not used here
constructUriFromArray?: (params: string[]) => string;
constructUriFromObject?: (params: Record<string, string>) => string;
};
// Create a discriminated union of the two types, incorporating generics
type EndpointDeclaration = StaticUriEndpoint | DynamicUriEndpoint;
export enum Endpoints {
RESERVE_SLOT = "RESERVE_SLOT",
DELETE_SELECTED_SLOT = "DELETE_SELECTED_SLOT",
AVAILABLE_SLOTS = "AVAILABLE_SLOTS",
GET_PUBLIC_EVENT = "GET_PUBLIC_EVENT",
EXCHANGE_OAUTH_AUTH_TOKEN = "EXCHANGE_OAUTH_AUTH_TOKEN",
REFRESH_OAUTH_TOKEN = "REFRESH_OAUTH_TOKEN",
CREATE_EVENT_TYPE = "CREATE_EVENT_TYPE",
GET_EVENT_TYPE_BY_ID = "GET_EVENT_TYPE_BY_ID",
CREATE_SCHEDULE = "CREATE_SCHEDULE",
GET_DEFAULT_SCHEDULE = "GET_DEFAULT_SCHEDULE",
GET_ALL_SCHEDULES = "GET_ALL_SCHEDULES",
GET_SCHEDULE_BY_ID = "GET_SCHEDULE_BY_ID",
GET_SUPPORTED_TIMEZONES = "GET_SUPPORTED_TIMEZONES",
UPDATE_SCHEDULE_BY_ID = "UPDATE_SCHEDULE_BY_ID",
DELETE_SCHEDULE_BY_ID = "DELETE_SCHEDULE_BY_ID",
GET_MANAGED_USERS_BY_CLIENT_ID = "GET_MANAGED_USERS_BY_CLIENT_ID",
CREATE_MANAGED_USER = "CREATE_MANAGED_USER",
GET_MANAGED_USER_BY_ID = "GET_MANAGED_USER_BY_ID",
}
const publicEndpoint = (uri: string, version = ApiVersion.NEUTRAL): EndpointDeclaration => ({
uri,
auth: "public",
apiVersion: version,
});
const constructUri = (
endpointData: {
constructUriFromArray?: (params: string[]) => string;
constructUriFromObject?: (params: Record<string, string>) => string;
},
params: string[] | Record<string, string>
) => {
if (endpointData.constructUriFromObject && isParamsRecord(params)) {
return endpointData.constructUriFromObject(params);
}
if (endpointData.constructUriFromArray && Array.isArray(params)) {
return endpointData.constructUriFromArray(params);
}
throw new Error("Invalid parameter type for dynamic endpoint.");
};
const ENDPOINTS: Record<Endpoints, EndpointDeclaration> = {
RESERVE_SLOT: publicEndpoint("slots/reserve", ApiVersion.V2),
AVAILABLE_SLOTS: publicEndpoint("slots/available", ApiVersion.V2),
DELETE_SELECTED_SLOT: publicEndpoint("slots/delete", ApiVersion.V2),
GET_PUBLIC_EVENT: publicEndpoint("events/"),
EXCHANGE_OAUTH_AUTH_TOKEN: {
auth: "public",
constructUriFromArray: ([clientId]) => `oauth/${clientId}/exchange`,
apiVersion: ApiVersion.V2,
},
REFRESH_OAUTH_TOKEN: {
auth: "public",
constructUriFromArray: ([clientId]) => `oauth/${clientId}/refresh`,
apiVersion: ApiVersion.V2,
},
CREATE_EVENT_TYPE: {
apiVersion: ApiVersion.V2,
auth: "access_token",
uri: `event-types`,
},
GET_EVENT_TYPE_BY_ID: {
apiVersion: ApiVersion.V2,
auth: "access_token",
constructUriFromArray: ([eventTypeId]) => `event-types/${eventTypeId}`,
},
CREATE_SCHEDULE: {
apiVersion: ApiVersion.V2,
auth: "access_token",
uri: "schedules",
},
GET_DEFAULT_SCHEDULE: {
apiVersion: ApiVersion.V2,
auth: "access_token",
uri: "schedules/default",
},
GET_ALL_SCHEDULES: {
apiVersion: ApiVersion.V2,
auth: "access_token",
uri: "schedules",
},
GET_SCHEDULE_BY_ID: {
apiVersion: ApiVersion.V2,
auth: "access_token",
constructUriFromArray: ([scheduleId]) => `schedules/${scheduleId}`,
},
GET_SUPPORTED_TIMEZONES: publicEndpoint("schedules/time-zones", ApiVersion.V2),
UPDATE_SCHEDULE_BY_ID: {
apiVersion: ApiVersion.V2,
auth: "access_token",
constructUriFromArray: ([scheduleId]) => `schedules/${scheduleId}`,
},
DELETE_SCHEDULE_BY_ID: {
apiVersion: ApiVersion.V2,
auth: "access_token",
constructUriFromArray: ([scheduleId]) => `schedules/${scheduleId}`,
},
GET_MANAGED_USERS_BY_CLIENT_ID: {
apiVersion: ApiVersion.V2,
auth: "secret",
constructUriFromArray: ([clientId]) => `oauth-clients/${clientId}/users`,
},
CREATE_MANAGED_USER: {
apiVersion: ApiVersion.V2,
auth: "secret",
constructUriFromArray: ([clientId]) => `oauth-clients/${clientId}/users`,
},
GET_MANAGED_USER_BY_ID: {
apiVersion: ApiVersion.V2,
auth: "secret",
constructUriFromArray: ([clientId, userId]) => `oauth-clients/${clientId}/users/${userId}`,
},
} as const;
const isParamsRecord = (params: unknown): params is Record<string, string> => {
return params !== null && typeof params === "object" && !Array.isArray(params);
};
export const getEndpointDefinition = (endpoint: Endpoints): BaseEndpointDeclaration => {
return ENDPOINTS[endpoint];
};
export const getEndpointData = (
endpoint: Endpoints,
params?: Record<string, string> | string[]
): {
uri: string;
version: ApiVersion;
auth: EndpointDeclaration["auth"];
} => {
const endpointData = ENDPOINTS[endpoint];
if (endpointData.uri) {
return {
version: endpointData.apiVersion,
uri: endpointData.uri,
auth: endpointData.auth,
};
}
if (!params) {
throw new Error(`Parameters are required for dynamic ${endpoint} endpoint.`);
}
if (
typeof endpointData.constructUriFromArray !== "function" &&
typeof endpointData.constructUriFromObject !== "function"
) {
throw new Error(`Endpoint configuration error for ${endpoint}`);
}
return {
version: endpointData.apiVersion,
uri: constructUri(endpointData, params),
auth: endpointData.auth,
};
};

View File

@@ -0,0 +1,6 @@
export class CalApiError extends Error {
constructor(public message: string, public statusCode: number) {
super(message);
this.name = "CalApiError";
}
}

View File

@@ -0,0 +1,6 @@
export class SdkInitializationError extends Error {
constructor(message: string) {
super(`Failed to initialize SDK: ${message}`);
this.name = "SdkInitializationError";
}
}

View File

@@ -0,0 +1,166 @@
import type { AxiosError, AxiosRequestConfig } from "axios";
import { type AxiosInstance } from "axios";
// eslint-disable-next-line no-restricted-imports
import merge from "lodash/merge";
import type { Endpoints } from "./endpoints";
import { getEndpointData, getEndpointDefinition } from "./endpoints";
import { CalApiError } from "./errors/cal-api-error";
import type { SdkSecrets } from "./sdk-secrets";
interface HttpCallerOptions {
shouldHandleRefresh?: boolean;
}
export const X_CAL_SECRET_KEY = "x-cal-secret-key";
type CallOptions = {
urlParams?: Record<string, string> | string[];
config?: AxiosRequestConfig<unknown>;
};
type CallOptionsWithBody<T = unknown> = CallOptions & {
body: T;
};
export class HttpCaller {
private awaitingRefresh = false;
secrets: SdkSecrets | null = null;
private requestQueue: Array<() => void> = [];
constructor(
private readonly clientId: string,
private readonly axiosClient: AxiosInstance,
private readonly options?: HttpCallerOptions
) {
if (options?.shouldHandleRefresh) {
this.setupInterceptors();
}
}
private async retryQueuedRequests() {
while (this.requestQueue.length > 0) {
const retryRequest = this.requestQueue.shift();
if (retryRequest) {
retryRequest();
}
}
}
private setupInterceptors() {
this.axiosClient.interceptors.request.use(async (config) => {
if (this.awaitingRefresh) {
// Return a new promise that resolves when the refresh is complete
// and then retries the request.
return new Promise((resolve) => {
this.requestQueue.push(() => resolve(config));
});
}
return config;
});
this.axiosClient.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
if (error.response?.status === 498) {
if (!this.awaitingRefresh) {
this.awaitingRefresh = true;
try {
await this.secrets?.refreshAccessToken(this.clientId);
await this.retryQueuedRequests();
} catch (refreshError) {
console.error("Failed to refresh token:", refreshError);
// Optionally, clear the queue on failure to prevent hanging requests
this.requestQueue = [];
} finally {
this.awaitingRefresh = false;
}
}
// Re-queue the failed request for retry after refresh
return new Promise((resolve, reject) => {
this.requestQueue.push(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.axiosClient.request(error.config!).then(resolve).catch(reject);
});
});
}
if (error.response) {
throw new CalApiError(error.response.statusText, error.response.status);
} else if (error.request) {
throw new Error("The request was made but no response was received");
} else {
throw new Error("An error occurred during the request setup");
}
}
);
}
async post<T>(endpoint: Endpoints, options: CallOptionsWithBody): Promise<T> {
const { data } = await this.axiosClient.post<T>(
this.createCallUrl(endpoint, options.urlParams),
options.body,
this.wrapConfigWithAuth(endpoint, options.config)
);
return data;
}
async get<T>(endpoint: Endpoints, options?: CallOptions): Promise<T> {
const { data } = await this.axiosClient.get<T>(
this.createCallUrl(endpoint, options?.urlParams),
this.wrapConfigWithAuth(endpoint, options?.config)
);
return data;
}
async delete<T>(endpoint: Endpoints, options?: CallOptions): Promise<T> {
const { data } = await this.axiosClient.delete<T>(
this.createCallUrl(endpoint, options?.urlParams),
this.wrapConfigWithAuth(endpoint, options?.config)
);
return data;
}
async patch<T>(endpoint: Endpoints, options: CallOptionsWithBody): Promise<T> {
const { data } = await this.axiosClient.patch<T>(
this.createCallUrl(endpoint, options.urlParams),
options.body,
this.wrapConfigWithAuth(endpoint, options.config)
);
return data;
}
private createCallUrl(endpoint: Endpoints, params?: Record<string, string> | string[]): string {
const { version, uri } = getEndpointData(endpoint, params);
return `${version}${uri}`;
}
private wrapConfigWithAuth(
endpoint: Endpoints,
config?: AxiosRequestConfig<unknown>
): AxiosRequestConfig<unknown> {
const { auth } = getEndpointDefinition(endpoint);
const headers: Record<string, unknown> = {};
const params: Record<string, unknown> = {};
if (this.secrets?.getAccessToken() && auth === "access_token") {
headers["Authorization"] = `Bearer ${this.secrets.getAccessToken()}`;
}
if (this.secrets?.getClientSecret() && auth === "secret") {
headers[X_CAL_SECRET_KEY] = this.secrets.getClientSecret();
params["clientId"] = this.clientId;
}
return merge(config, {
headers,
params,
});
}
}

View File

@@ -0,0 +1,64 @@
import type { ExchangeCodeResponse } from "../endpoints/oauth-flow/types";
import type { BasicPlatformResponse } from "../types";
import { Endpoints } from "./endpoints";
import type { HttpCaller } from "./http-caller";
import { X_CAL_SECRET_KEY } from "./http-caller";
export class SdkSecrets {
private refreshedAt: Date | null;
constructor(
private readonly clientSecret: string,
private accessToken: string,
private refreshToken: string,
private readonly httpCaller: HttpCaller
) {
this.refreshedAt = null;
}
updateAccessToken(accessToken: string, refreshToken?: string) {
this.accessToken = accessToken;
if (refreshToken) {
this.refreshToken = refreshToken;
}
}
async refreshAccessToken(clientId: string) {
const { data } = await this.httpCaller.post<BasicPlatformResponse<ExchangeCodeResponse>>(
Endpoints.REFRESH_OAUTH_TOKEN,
{
urlParams: [clientId],
body: {
refreshToken: this.refreshToken,
},
config: {
headers: {
[X_CAL_SECRET_KEY]: this.clientSecret,
},
},
}
);
this.accessToken = data.accessToken;
this.refreshToken = data.refreshToken;
this.refreshedAt = new Date();
}
public getAccessToken(): Readonly<string> {
return this.accessToken;
}
public getClientSecret(): Readonly<string> {
return this.clientSecret;
}
public isAccessTokenSet(): boolean {
return !!this.accessToken;
}
public getRefreshedAt(): Date | null {
return this.refreshedAt;
}
}

View File

@@ -0,0 +1,877 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/health": {
get: operations["AppController_getHealth"];
};
"/v2/events/public": {
get: operations["EventsController_getPublicEvent"];
};
"/v2/oauth-clients/{clientId}/users": {
post: operations["OAuthClientUsersController_createUser"];
};
"/v2/oauth-clients/{clientId}/users/{userId}": {
get: operations["OAuthClientUsersController_getUserById"];
delete: operations["OAuthClientUsersController_deleteUser"];
patch: operations["OAuthClientUsersController_updateUser"];
};
"/v2/oauth-clients": {
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
get: operations["OAuthClientsController_getOAuthClients"];
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
post: operations["OAuthClientsController_createOAuthClient"];
};
"/v2/oauth-clients/{clientId}": {
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
get: operations["OAuthClientsController_getOAuthClientById"];
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
delete: operations["OAuthClientsController_deleteOAuthClient"];
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
patch: operations["OAuthClientsController_updateOAuthClient"];
};
"/v2/oauth/{clientId}/authorize": {
/**
* Authorize an OAuth client
* @description Redirects the user to the specified 'redirect_uri' with an authorization code in query parameter if the client is authorized successfully. The code is then exchanged for access and refresh tokens via the `/exchange` endpoint.
*/
post: operations["OAuthFlowController_authorize"];
};
"/v2/oauth/{clientId}/exchange": {
/**
* Exchange authorization code for access tokens
* @description Exchanges the authorization code received from the `/authorize` endpoint for access and refresh tokens. The authorization code should be provided in the 'Authorization' header prefixed with 'Bearer '.
*/
post: operations["OAuthFlowController_exchange"];
};
"/v2/oauth/{clientId}/refresh": {
post: operations["OAuthFlowController_refreshAccessToken"];
};
"/v2/event-types": {
get: operations["EventTypesController_getEventTypes"];
post: operations["EventTypesController_createEventType"];
};
"/v2/event-types/{eventTypeId}": {
get: operations["EventTypesController_getEventType"];
};
"/v2/event-types/{username}/public": {
get: operations["EventTypesController_getPublicEventTypes"];
};
"/v2/gcal/oauth/auth-url": {
get: operations["GcalController_redirect"];
};
"/v2/gcal/oauth/save": {
get: operations["GcalController_save"];
};
"/v2/gcal/check": {
get: operations["GcalController_check"];
};
"/v2/provider/{clientId}": {
get: operations["CalProviderController_verifyClientId"];
};
"/v2/provider/{clientId}/access-token": {
get: operations["CalProviderController_verifyAccessToken"];
};
"/v2/schedules": {
get: operations["SchedulesController_getSchedules"];
post: operations["SchedulesController_createSchedule"];
};
"/v2/schedules/default": {
get: operations["SchedulesController_getDefaultSchedule"];
};
"/v2/schedules/time-zones": {
get: operations["SchedulesController_getTimeZones"];
};
"/v2/schedules/{scheduleId}": {
get: operations["SchedulesController_getSchedule"];
delete: operations["SchedulesController_deleteSchedule"];
patch: operations["SchedulesController_updateSchedule"];
};
"/v2/me": {
get: operations["MeController_getMe"];
patch: operations["MeController_updateMe"];
};
"/v2/calendars/busy-times": {
get: operations["CalendarsController_getBusyTimes"];
};
"/v2/calendars": {
get: operations["CalendarsController_getCalendars"];
};
"/v2/bookings": {
post: operations["BookingsController_createBooking"];
};
"/v2/bookings/recurring": {
post: operations["BookingsController_createRecurringBooking"];
};
"/v2/bookings/instant": {
post: operations["BookingsController_createInstantBooking"];
};
"/v2/slots/reserve": {
post: operations["SlotsController_reserveSlot"];
};
"/v2/slots/selected-slot": {
delete: operations["SlotsController_deleteSelectedSlot"];
};
"/v2/slots/available": {
get: operations["SlotsController_getAvailableSlots"];
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
CreateManagedPlatformUserInput: {
email: string;
name?: string;
timeFormat?: number;
weekStart?: string;
timeZone?: string;
};
UpdateManagedPlatformUserInput: {
email?: string;
name?: string;
timeFormat?: number;
defaultScheduleId?: number;
weekStart?: string;
timeZone?: string;
};
CreateOAuthClientInput: Record<string, never>;
DataDto: {
/** @example clsx38nbl0001vkhlwin9fmt0 */
clientId: string;
/** @example eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoib2F1dGgtY2xpZW50Iiwi */
clientSecret: string;
};
CreateOAuthClientResponseDto: {
/**
* @example success
* @enum {string}
*/
status: "success" | "error";
/**
* @example {
* "clientId": "clsx38nbl0001vkhlwin9fmt0",
* "clientSecret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoib2F1dGgtY2xpZW50Iiwi"
* }
*/
data: components["schemas"]["DataDto"];
};
PlatformOAuthClientDto: {
/** @example clsx38nbl0001vkhlwin9fmt0 */
id: string;
/** @example MyClient */
name: string;
/** @example secretValue */
secret: string;
/** @example 3 */
permissions: number;
/** @example https://example.com/logo.png */
logo?: Record<string, never>;
/**
* @example [
* "https://example.com/callback"
* ]
*/
redirectUris: string[];
/** @example 1 */
organizationId: number;
/**
* Format: date-time
* @example 2024-03-23T08:33:21.851Z
*/
createdAt: string;
};
GetOAuthClientsResponseDto: {
/**
* @example success
* @enum {string}
*/
status: "success" | "error";
data: components["schemas"]["PlatformOAuthClientDto"][];
};
GetOAuthClientResponseDto: {
/**
* @example success
* @enum {string}
*/
status: "success" | "error";
data: components["schemas"]["PlatformOAuthClientDto"];
};
UpdateOAuthClientInput: {
logo?: string;
name?: string;
/** @default [] */
redirectUris?: string[];
};
OAuthAuthorizeInput: {
redirectUri: string;
};
ExchangeAuthorizationCodeInput: {
clientSecret: string;
};
KeysDto: {
/** @example eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 */
accessToken: string;
/** @example eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 */
refreshToken: string;
};
KeysResponseDto: {
/**
* @example success
* @enum {string}
*/
status: "success" | "error";
data: components["schemas"]["KeysDto"];
};
RefreshTokenInput: {
refreshToken: string;
};
CreateEventTypeInput: {
length: number;
slug: string;
title: string;
};
CreateAvailabilityInput: {
days: number[];
/** Format: date-time */
startTime: string;
/** Format: date-time */
endTime: string;
};
CreateScheduleInput: {
name: string;
timeZone: string;
availabilities?: components["schemas"]["CreateAvailabilityInput"][];
/** @default true */
isDefault: Record<string, never>;
};
UpdateScheduleInput: Record<string, never>;
CreateBookingInput: {
end?: string;
start: string;
eventTypeId: number;
eventTypeSlug?: string;
rescheduleUid?: string;
recurringEventId?: string;
timeZone: string;
user?: string[];
language: string;
bookingUid?: string;
metadata: Record<string, never>;
hasHashedBookingLink?: boolean;
hashedLink: string | null;
seatReferenceUid?: string;
};
ReserveSlotInput: Record<string, never>;
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
AppController_getHealth: {
responses: {
200: {
content: {
"application/json": string;
};
};
};
};
EventsController_getPublicEvent: {
parameters: {
query: {
username: string;
eventSlug: string;
isTeamEvent?: boolean;
org?: string;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
OAuthClientUsersController_createUser: {
parameters: {
path: {
clientId: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateManagedPlatformUserInput"];
};
};
responses: {
201: {
content: {
"application/json": Record<string, never>;
};
};
};
};
OAuthClientUsersController_getUserById: {
parameters: {
path: {
clientId: string;
userId: number;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
OAuthClientUsersController_deleteUser: {
parameters: {
path: {
clientId: string;
userId: number;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
OAuthClientUsersController_updateUser: {
parameters: {
path: {
clientId: string;
userId: number;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdateManagedPlatformUserInput"];
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
OAuthClientsController_getOAuthClients: {
responses: {
200: {
content: {
"application/json": components["schemas"]["GetOAuthClientsResponseDto"];
};
};
};
};
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
OAuthClientsController_createOAuthClient: {
requestBody: {
content: {
"application/json": components["schemas"]["CreateOAuthClientInput"];
};
};
responses: {
/** @description Create an OAuth client */
201: {
content: {
"application/json": components["schemas"]["CreateOAuthClientResponseDto"];
};
};
};
};
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
OAuthClientsController_getOAuthClientById: {
parameters: {
path: {
clientId: string;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["GetOAuthClientResponseDto"];
};
};
};
};
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
OAuthClientsController_deleteOAuthClient: {
parameters: {
path: {
clientId: string;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["GetOAuthClientResponseDto"];
};
};
};
};
/**
* @description ⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
* Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.
*/
OAuthClientsController_updateOAuthClient: {
parameters: {
path: {
clientId: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdateOAuthClientInput"];
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["GetOAuthClientResponseDto"];
};
};
};
};
/**
* Authorize an OAuth client
* @description Redirects the user to the specified 'redirect_uri' with an authorization code in query parameter if the client is authorized successfully. The code is then exchanged for access and refresh tokens via the `/exchange` endpoint.
*/
OAuthFlowController_authorize: {
parameters: {
path: {
clientId: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["OAuthAuthorizeInput"];
};
};
responses: {
/** @description The user is redirected to the 'redirect_uri' with an authorization code in query parameter e.g. `redirectUri?code=secretcode.` */
200: {
content: never;
};
/** @description Bad request if the OAuth client is not found, if the redirect URI is invalid, or if the user has already authorized the client. */
400: {
content: never;
};
};
};
/**
* Exchange authorization code for access tokens
* @description Exchanges the authorization code received from the `/authorize` endpoint for access and refresh tokens. The authorization code should be provided in the 'Authorization' header prefixed with 'Bearer '.
*/
OAuthFlowController_exchange: {
parameters: {
header: {
Authorization: string;
};
path: {
clientId: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["ExchangeAuthorizationCodeInput"];
};
};
responses: {
/** @description Successfully exchanged authorization code for access and refresh tokens. */
200: {
content: {
"application/json": components["schemas"]["KeysResponseDto"];
};
};
/** @description Bad request if the authorization code is missing, invalid, or if the client ID and secret do not match. */
400: {
content: never;
};
};
};
OAuthFlowController_refreshAccessToken: {
parameters: {
header: {
"x-cal-secret-key": string;
};
path: {
clientId: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["RefreshTokenInput"];
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["KeysResponseDto"];
};
};
};
};
EventTypesController_getEventTypes: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
EventTypesController_createEventType: {
requestBody: {
content: {
"application/json": components["schemas"]["CreateEventTypeInput"];
};
};
responses: {
201: {
content: {
"application/json": Record<string, never>;
};
};
};
};
EventTypesController_getEventType: {
parameters: {
path: {
eventTypeId: string;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
EventTypesController_getPublicEventTypes: {
parameters: {
path: {
username: string;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
GcalController_redirect: {
parameters: {
header: {
Authorization: string;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
GcalController_save: {
parameters: {
query: {
state: string;
code: string;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
GcalController_check: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
CalProviderController_verifyClientId: {
parameters: {
path: {
clientId: string;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
CalProviderController_verifyAccessToken: {
parameters: {
path: {
clientId: string;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SchedulesController_getSchedules: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SchedulesController_createSchedule: {
requestBody: {
content: {
"application/json": components["schemas"]["CreateScheduleInput"];
};
};
responses: {
201: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SchedulesController_getDefaultSchedule: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SchedulesController_getTimeZones: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SchedulesController_getSchedule: {
parameters: {
path: {
scheduleId: number;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SchedulesController_deleteSchedule: {
parameters: {
path: {
scheduleId: number;
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SchedulesController_updateSchedule: {
requestBody: {
content: {
"application/json": components["schemas"]["UpdateScheduleInput"];
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
MeController_getMe: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
MeController_updateMe: {
requestBody: {
content: {
"application/json": components["schemas"]["UpdateManagedPlatformUserInput"];
};
};
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
CalendarsController_getBusyTimes: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
CalendarsController_getCalendars: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
BookingsController_createBooking: {
requestBody: {
content: {
"application/json": components["schemas"]["CreateBookingInput"];
};
};
responses: {
201: {
content: {
"application/json": Record<string, never>;
};
};
};
};
BookingsController_createRecurringBooking: {
requestBody: {
content: {
"application/json": string[];
};
};
responses: {
201: {
content: {
"application/json": Record<string, never>;
};
};
};
};
BookingsController_createInstantBooking: {
requestBody: {
content: {
"application/json": components["schemas"]["CreateBookingInput"];
};
};
responses: {
201: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SlotsController_reserveSlot: {
requestBody: {
content: {
"application/json": components["schemas"]["ReserveSlotInput"];
};
};
responses: {
201: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SlotsController_deleteSelectedSlot: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
SlotsController_getAvailableSlots: {
responses: {
200: {
content: {
"application/json": Record<string, never>;
};
};
};
};
}

View File

@@ -0,0 +1,38 @@
export interface CalSdkConstructorOptions {
baseUrl?: string;
handleRefresh?: boolean;
httpRetries?: CalSdkHttpRetries;
}
interface CalSdkHttpRetries {
maxAmount: number;
}
export type ResponseStatus = "success" | "error";
export interface BasicPlatformResponse<T = unknown> {
status: ResponseStatus;
data: T;
}
export enum ApiVersion {
V1 = "v1/",
V2 = "v2/",
NEUTRAL = "/",
}
export type SdkAuthOptions = {
clientSecret?: string;
accessToken?: string;
refreshToken?: string;
};
export type PaginationOptions = {
limit?: number;
skip?: number;
};
export * from "./endpoints/slots/types";
export * from "./endpoints/schedules/types";
export * from "./endpoints/events/types";
export * from "./endpoints/oauth-flow/types";

View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext"
},
"exclude": ["src/__tests__/**/*", "example/*"]
}

View File

@@ -0,0 +1,109 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}