@cosmoops/connect-sdk 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,65 @@
3
3
  `@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
4
4
  versions may carry breaking changes** and patch versions never do.
5
5
 
6
+ ## 0.17.0
7
+
8
+ ### Added
9
+
10
+ - **`connect.scheduling.*` - a subscribing workspace's own calendar.**
11
+
12
+ ```ts
13
+ const eventTypes = await connect.scheduling.listEventTypes(workspaceId);
14
+ const { slots, timezone } = await connect.scheduling.findSlots({
15
+ workspaceId,
16
+ eventTypeId: eventTypes[0].id,
17
+ from: new Date(),
18
+ to: nextWeek,
19
+ });
20
+ const booking = await connect.scheduling.createBooking({
21
+ workspaceId,
22
+ eventTypeId: eventTypes[0].id,
23
+ startAt: slots[0].startAt,
24
+ attendee: { name: "Priya", phone: "+919000000001" },
25
+ });
26
+ ```
27
+
28
+ Five methods - `listEventTypes`, `findSlots`, `listBookings`,
29
+ `createBooking`, `cancelBooking` - against the same calendar the workspace's
30
+ own team configures and its voice agent books into. A booking you make shows
31
+ up on their screen immediately, and a slot their receptionist just took is
32
+ gone from your next `findSlots`. That is the reason to read it through
33
+ Connect rather than keeping a copy: two calendars double-book, and nothing
34
+ reconciles them afterwards.
35
+
36
+ Needs a key scoped to `calendar:read` (and `calendar:write` to book), **and**
37
+ a live subscription to your application in the workspace you name. The key
38
+ says which application is calling; the subscription is what makes you welcome
39
+ in a given tenant. A workspace that has not installed your application
40
+ answers `403` with the same message as an unknown workspace id.
41
+
42
+ Two things to hold onto when integrating:
43
+
44
+ - **`findSlots` is advisory.** Between reading it and booking, anyone else on
45
+ that calendar can take a slot - a member, a voice agent mid-call, someone on
46
+ the workspace's public booking link. `createBooking` re-checks inside a
47
+ transaction and throws a `ConnectError` with `code: "conflict"` rather than
48
+ double-booking. Handle that as an ordinary outcome and offer the next time;
49
+ it is not an error condition.
50
+ - **`timezone` is the business's, not the viewer's.** It is what to render a
51
+ slot's wall time in. A slot shown in the viewer's zone is an hour the
52
+ business may not be open.
53
+
54
+ - New exported types: `BookableEventType`, `BookingSlot`, `BookingSlots`,
55
+ `Booking`, `BookingAttendee`, `BookingStatus`, `BookingSource`,
56
+ `ListBookingsPage`, and the `SchedulingResource` input types from
57
+ `@cosmoops/connect-sdk/server`.
58
+
59
+ - `ConnectError.code` can now be `"conflict"` - today only from
60
+ `createBooking`. Nothing that switched on the previous codes stops
61
+ compiling: `code` is `string`, deliberately, so a new one reaches a partner
62
+ as a case to handle rather than a type error on a published package they
63
+ cannot patch.
64
+
6
65
  ## 0.16.1
7
66
 
8
67
  ### Fixed
package/README.md CHANGED
@@ -342,6 +342,7 @@ try {
342
342
  | `entitlements` | `getReadiness` · `getSubscription` · `hasFeature` |
343
343
  | `billing` | `listPlans` · `getAddonUsage` · `consumeAddon` |
344
344
  | `drive` | `upload` · `getFileUrl` · `getPreviewUrl` |
345
+ | `scheduling` | `listEventTypes` · `findSlots` · `listBookings` · `createBooking` · `cancelBooking` |
345
346
  | `ai` | `chat` · `extract` |
346
347
  | `otp` | `send` · `verify` |
347
348
  | `logs` | `write` |
@@ -349,6 +350,39 @@ try {
349
350
  | `sessions` | `list` · `revoke` · `revokeOthers` |
350
351
  | `integrations` | `list` · `initiateConnect` · `disconnect` · `execute` |
351
352
 
353
+ ### Booking on a workspace's calendar
354
+
355
+ One calendar per workspace - the same one their team configures, and the same
356
+ one a voice agent books into. Needs `calendar:read` (plus `calendar:write` to
357
+ book) and a live subscription to your application in that workspace.
358
+
359
+ ```ts
360
+ const eventTypes = await client.scheduling.listEventTypes(workspaceId);
361
+ const { slots, timezone } = await client.scheduling.findSlots({
362
+ workspaceId,
363
+ eventTypeId: eventTypes[0].id,
364
+ from: new Date(),
365
+ to: nextWeek,
366
+ });
367
+
368
+ try {
369
+ await client.scheduling.createBooking({
370
+ workspaceId,
371
+ eventTypeId: eventTypes[0].id,
372
+ startAt: slots[0].startAt, // exactly as findSlots gave it
373
+ attendee: { name, phone },
374
+ });
375
+ } catch (error) {
376
+ if (error instanceof ConnectError && error.code === "conflict") {
377
+ // Someone took it between the two calls. Re-read and offer the next one -
378
+ // this is an ordinary outcome on a shared calendar, not a failure.
379
+ }
380
+ }
381
+ ```
382
+
383
+ `timezone` is the **business's** zone, not your user's - render the wall time in
384
+ it, or you will offer an hour they are not open.
385
+
352
386
  ### Attributing a log to a user
353
387
 
354
388
  ```ts
@@ -6,6 +6,7 @@ const apps_js_1 = require("./resources/apps.js");
6
6
  const ai_js_1 = require("./resources/ai.js");
7
7
  const data_js_1 = require("./resources/data.js");
8
8
  const workspaces_js_1 = require("./resources/workspaces.js");
9
+ const scheduling_js_1 = require("./resources/scheduling.js");
9
10
  const kv_js_1 = require("./resources/kv.js");
10
11
  const billing_js_1 = require("./resources/billing.js");
11
12
  const drive_js_1 = require("./resources/drive.js");
@@ -79,6 +80,8 @@ class ConnectClient {
79
80
  billing;
80
81
  /** Files in a workspace's Drive. */
81
82
  drive;
83
+ /** A subscribing workspace's own calendar - what is bookable, free, and booked. */
84
+ scheduling;
82
85
  /** RAG chat and structured extraction. */
83
86
  ai;
84
87
  /** Your application's own document store - see `DataResource`. */
@@ -127,6 +130,7 @@ class ConnectClient {
127
130
  this.entitlements = new entitlements_js_1.EntitlementsResource(transport);
128
131
  this.billing = new billing_js_1.BillingResource(transport);
129
132
  this.drive = new drive_js_1.DriveResource(transport);
133
+ this.scheduling = new scheduling_js_1.SchedulingResource(transport);
130
134
  this.ai = new ai_js_1.AiResource(transport);
131
135
  this.data = new data_js_1.DataResource(transport);
132
136
  this.kv = new kv_js_1.KvResource(transport);
@@ -142,4 +146,4 @@ class ConnectClient {
142
146
  }
143
147
  exports.ConnectClient = ConnectClient;
144
148
  /** Kept in step with `package.json` by `sdk.test.ts`. */
145
- exports.SDK_VERSION = "0.16.1";
149
+ exports.SDK_VERSION = "0.17.0";
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SchedulingResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ /**
6
+ * A subscribing workspace's own calendar.
7
+ *
8
+ * **There is one calendar, not one per application.** A booking made here
9
+ * appears on the workspace's own screen immediately, and a slot their
10
+ * receptionist just took is gone from your next `findSlots`. That is the
11
+ * reason to read it through Connect rather than keeping a copy: two calendars
12
+ * double-book, and nothing reconciles them afterwards.
13
+ *
14
+ * Every method needs a live subscription to your application in the workspace
15
+ * you name. The key says which application is calling; the subscription is
16
+ * what makes you welcome in a given tenant.
17
+ */
18
+ class SchedulingResource {
19
+ transport;
20
+ constructor(transport) {
21
+ this.transport = transport;
22
+ }
23
+ /** What this workspace offers as bookable. Active appointment types only. */
24
+ async listEventTypes(workspaceId) {
25
+ const result = await (0, http_js_1.request)(this.transport, {
26
+ method: "GET",
27
+ path: "/scheduling/event-types",
28
+ query: { workspaceId },
29
+ });
30
+ // `?? []` for the same reason `apps.list` has it - a collection every
31
+ // caller immediately maps, and a body without the key would hand them
32
+ // `undefined` past every status check in between.
33
+ return result.eventTypes ?? [];
34
+ }
35
+ /**
36
+ * Free times for one appointment type over one window.
37
+ *
38
+ * **Advisory by construction.** Between this answer and `createBooking`,
39
+ * anyone else on that calendar can take any of these - a member, a voice
40
+ * agent mid-call, someone on the workspace's public booking link. That is
41
+ * why `createBooking` can answer `409`, and why that is an ordinary outcome
42
+ * rather than a fault.
43
+ *
44
+ * The window is clamped silently to now and to the appointment type's own
45
+ * booking horizon, so asking for a year of a sixty-day type returns sixty
46
+ * days rather than an error.
47
+ */
48
+ findSlots(input) {
49
+ return (0, http_js_1.request)(this.transport, {
50
+ method: "GET",
51
+ path: "/scheduling/slots",
52
+ query: {
53
+ workspaceId: input.workspaceId,
54
+ eventTypeId: input.eventTypeId,
55
+ from: toInstant(input.from),
56
+ to: toInstant(input.to),
57
+ },
58
+ });
59
+ }
60
+ /** One page of the workspace's diary. */
61
+ listBookings(input) {
62
+ return (0, http_js_1.request)(this.transport, {
63
+ method: "GET",
64
+ path: "/scheduling/bookings",
65
+ query: {
66
+ workspaceId: input.workspaceId,
67
+ status: input.status,
68
+ hostMemberId: input.hostMemberId,
69
+ from: input.from ? toInstant(input.from) : undefined,
70
+ to: input.to ? toInstant(input.to) : undefined,
71
+ order: input.order,
72
+ cursor: input.cursor,
73
+ limit: input.limit === undefined ? undefined : String(input.limit),
74
+ },
75
+ });
76
+ }
77
+ /**
78
+ * Takes one of the times `findSlots` offered.
79
+ *
80
+ * Holds and confirms in one call - you already have the attendee, so there
81
+ * is no half-finished state for your integration to remember. The slot is
82
+ * re-checked inside a transaction, so a time taken since you read it throws
83
+ * a `ConnectError` with `code: "conflict"` rather than double-booking.
84
+ */
85
+ createBooking(input) {
86
+ return (0, http_js_1.request)(this.transport, {
87
+ method: "POST",
88
+ path: "/scheduling/bookings",
89
+ json: {
90
+ workspaceId: input.workspaceId,
91
+ eventTypeId: input.eventTypeId,
92
+ startAt: toInstant(input.startAt),
93
+ attendee: input.attendee,
94
+ notes: input.notes ?? null,
95
+ },
96
+ });
97
+ }
98
+ /**
99
+ * Frees the slot and records why.
100
+ *
101
+ * Not a deletion: the booking stays on the diary as `cancelled` with the
102
+ * reason, which is what makes a late cancellation something anyone can look
103
+ * up afterwards.
104
+ */
105
+ cancelBooking(input) {
106
+ return (0, http_js_1.request)(this.transport, {
107
+ method: "POST",
108
+ path: `/scheduling/bookings/${encodeURIComponent(input.bookingId)}/cancel`,
109
+ json: { workspaceId: input.workspaceId, reason: input.reason ?? null },
110
+ });
111
+ }
112
+ }
113
+ exports.SchedulingResource = SchedulingResource;
114
+ /**
115
+ * A `Date` or an already-ISO string, as the ISO-8601 instant the API takes.
116
+ *
117
+ * `toISOString()` rather than any local formatting: these are instants, and a
118
+ * wall time without a zone is the one input this API cannot interpret.
119
+ */
120
+ function toInstant(value) {
121
+ return typeof value === "string" ? value : value.toISOString();
122
+ }
@@ -3,6 +3,7 @@ import { AppsResource } from "./resources/apps.js";
3
3
  import { AiResource } from "./resources/ai.js";
4
4
  import { DataResource, storePassthrough } from "./resources/data.js";
5
5
  import { WorkspacesResource } from "./resources/workspaces.js";
6
+ import { SchedulingResource } from "./resources/scheduling.js";
6
7
  import { KvResource } from "./resources/kv.js";
7
8
  import { BillingResource } from "./resources/billing.js";
8
9
  import { DriveResource } from "./resources/drive.js";
@@ -76,6 +77,8 @@ export class ConnectClient {
76
77
  billing;
77
78
  /** Files in a workspace's Drive. */
78
79
  drive;
80
+ /** A subscribing workspace's own calendar - what is bookable, free, and booked. */
81
+ scheduling;
79
82
  /** RAG chat and structured extraction. */
80
83
  ai;
81
84
  /** Your application's own document store - see `DataResource`. */
@@ -124,6 +127,7 @@ export class ConnectClient {
124
127
  this.entitlements = new EntitlementsResource(transport);
125
128
  this.billing = new BillingResource(transport);
126
129
  this.drive = new DriveResource(transport);
130
+ this.scheduling = new SchedulingResource(transport);
127
131
  this.ai = new AiResource(transport);
128
132
  this.data = new DataResource(transport);
129
133
  this.kv = new KvResource(transport);
@@ -138,4 +142,4 @@ export class ConnectClient {
138
142
  }
139
143
  }
140
144
  /** Kept in step with `package.json` by `sdk.test.ts`. */
141
- export const SDK_VERSION = "0.16.1";
145
+ export const SDK_VERSION = "0.17.0";
@@ -0,0 +1,118 @@
1
+ import { request } from "../http.js";
2
+ /**
3
+ * A subscribing workspace's own calendar.
4
+ *
5
+ * **There is one calendar, not one per application.** A booking made here
6
+ * appears on the workspace's own screen immediately, and a slot their
7
+ * receptionist just took is gone from your next `findSlots`. That is the
8
+ * reason to read it through Connect rather than keeping a copy: two calendars
9
+ * double-book, and nothing reconciles them afterwards.
10
+ *
11
+ * Every method needs a live subscription to your application in the workspace
12
+ * you name. The key says which application is calling; the subscription is
13
+ * what makes you welcome in a given tenant.
14
+ */
15
+ export class SchedulingResource {
16
+ transport;
17
+ constructor(transport) {
18
+ this.transport = transport;
19
+ }
20
+ /** What this workspace offers as bookable. Active appointment types only. */
21
+ async listEventTypes(workspaceId) {
22
+ const result = await request(this.transport, {
23
+ method: "GET",
24
+ path: "/scheduling/event-types",
25
+ query: { workspaceId },
26
+ });
27
+ // `?? []` for the same reason `apps.list` has it - a collection every
28
+ // caller immediately maps, and a body without the key would hand them
29
+ // `undefined` past every status check in between.
30
+ return result.eventTypes ?? [];
31
+ }
32
+ /**
33
+ * Free times for one appointment type over one window.
34
+ *
35
+ * **Advisory by construction.** Between this answer and `createBooking`,
36
+ * anyone else on that calendar can take any of these - a member, a voice
37
+ * agent mid-call, someone on the workspace's public booking link. That is
38
+ * why `createBooking` can answer `409`, and why that is an ordinary outcome
39
+ * rather than a fault.
40
+ *
41
+ * The window is clamped silently to now and to the appointment type's own
42
+ * booking horizon, so asking for a year of a sixty-day type returns sixty
43
+ * days rather than an error.
44
+ */
45
+ findSlots(input) {
46
+ return request(this.transport, {
47
+ method: "GET",
48
+ path: "/scheduling/slots",
49
+ query: {
50
+ workspaceId: input.workspaceId,
51
+ eventTypeId: input.eventTypeId,
52
+ from: toInstant(input.from),
53
+ to: toInstant(input.to),
54
+ },
55
+ });
56
+ }
57
+ /** One page of the workspace's diary. */
58
+ listBookings(input) {
59
+ return request(this.transport, {
60
+ method: "GET",
61
+ path: "/scheduling/bookings",
62
+ query: {
63
+ workspaceId: input.workspaceId,
64
+ status: input.status,
65
+ hostMemberId: input.hostMemberId,
66
+ from: input.from ? toInstant(input.from) : undefined,
67
+ to: input.to ? toInstant(input.to) : undefined,
68
+ order: input.order,
69
+ cursor: input.cursor,
70
+ limit: input.limit === undefined ? undefined : String(input.limit),
71
+ },
72
+ });
73
+ }
74
+ /**
75
+ * Takes one of the times `findSlots` offered.
76
+ *
77
+ * Holds and confirms in one call - you already have the attendee, so there
78
+ * is no half-finished state for your integration to remember. The slot is
79
+ * re-checked inside a transaction, so a time taken since you read it throws
80
+ * a `ConnectError` with `code: "conflict"` rather than double-booking.
81
+ */
82
+ createBooking(input) {
83
+ return request(this.transport, {
84
+ method: "POST",
85
+ path: "/scheduling/bookings",
86
+ json: {
87
+ workspaceId: input.workspaceId,
88
+ eventTypeId: input.eventTypeId,
89
+ startAt: toInstant(input.startAt),
90
+ attendee: input.attendee,
91
+ notes: input.notes ?? null,
92
+ },
93
+ });
94
+ }
95
+ /**
96
+ * Frees the slot and records why.
97
+ *
98
+ * Not a deletion: the booking stays on the diary as `cancelled` with the
99
+ * reason, which is what makes a late cancellation something anyone can look
100
+ * up afterwards.
101
+ */
102
+ cancelBooking(input) {
103
+ return request(this.transport, {
104
+ method: "POST",
105
+ path: `/scheduling/bookings/${encodeURIComponent(input.bookingId)}/cancel`,
106
+ json: { workspaceId: input.workspaceId, reason: input.reason ?? null },
107
+ });
108
+ }
109
+ }
110
+ /**
111
+ * A `Date` or an already-ISO string, as the ISO-8601 instant the API takes.
112
+ *
113
+ * `toISOString()` rather than any local formatting: these are instants, and a
114
+ * wall time without a zone is the one input this API cannot interpret.
115
+ */
116
+ function toInstant(value) {
117
+ return typeof value === "string" ? value : value.toISOString();
118
+ }
@@ -4,6 +4,7 @@ import { AppsResource } from "./resources/apps.js";
4
4
  import { AiResource } from "./resources/ai.js";
5
5
  import { DataResource } from "./resources/data.js";
6
6
  import { WorkspacesResource } from "./resources/workspaces.js";
7
+ import { SchedulingResource } from "./resources/scheduling.js";
7
8
  import { KvResource } from "./resources/kv.js";
8
9
  import { BillingResource } from "./resources/billing.js";
9
10
  import { DriveResource } from "./resources/drive.js";
@@ -75,6 +76,8 @@ export declare class ConnectClient {
75
76
  readonly billing: BillingResource;
76
77
  /** Files in a workspace's Drive. */
77
78
  readonly drive: DriveResource;
79
+ /** A subscribing workspace's own calendar - what is bookable, free, and booked. */
80
+ readonly scheduling: SchedulingResource;
78
81
  /** RAG chat and structured extraction. */
79
82
  readonly ai: AiResource;
80
83
  /** Your application's own document store - see `DataResource`. */
@@ -109,4 +112,4 @@ export declare class ConnectClient {
109
112
  constructor(options: ConnectClientOptions);
110
113
  }
111
114
  /** Kept in step with `package.json` by `sdk.test.ts`. */
112
- export declare const SDK_VERSION = "0.16.1";
115
+ export declare const SDK_VERSION = "0.17.0";
@@ -27,6 +27,7 @@ export type { AnalyticsResource } from "./resources/analytics.js";
27
27
  export type { SessionsResource } from "./resources/sessions.js";
28
28
  export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./resources/integrations.js";
29
29
  export type { PlatformResource } from "./resources/platform.js";
30
+ export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, } from "./resources/scheduling.js";
30
31
  /**
31
32
  * Webhook verification and a ready-made POST route. Server-only for the
32
33
  * obvious reason: the signing secret must never reach a browser bundle.
@@ -0,0 +1,91 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { BookableEventType, Booking, BookingAttendee, BookingSlots, BookingStatus, ListBookingsPage } from "../../types.js";
3
+ export interface FindSlotsInput {
4
+ workspaceId: string;
5
+ eventTypeId: string;
6
+ /** ISO-8601 instant, or a `Date`. */
7
+ from: string | Date;
8
+ to: string | Date;
9
+ }
10
+ export interface ListBookingsInput {
11
+ workspaceId: string;
12
+ status?: BookingStatus;
13
+ /** A Connect workspace member id - only bookings that committed them. */
14
+ hostMemberId?: string;
15
+ from?: string | Date;
16
+ to?: string | Date;
17
+ /** `asc` for a forward diary, `desc` (the default) for a history. */
18
+ order?: "asc" | "desc";
19
+ cursor?: string;
20
+ limit?: number;
21
+ }
22
+ export interface CreateBookingInput {
23
+ workspaceId: string;
24
+ eventTypeId: string;
25
+ /**
26
+ * The `startAt` from a `findSlots` result, passed back unchanged.
27
+ *
28
+ * Re-deriving it from a local wall time is how an integration books an hour
29
+ * that was never offered - the slot list is computed in the *business's*
30
+ * timezone, which is rarely the one your process is running in.
31
+ */
32
+ startAt: string | Date;
33
+ attendee: BookingAttendee;
34
+ notes?: string | null;
35
+ }
36
+ /**
37
+ * A subscribing workspace's own calendar.
38
+ *
39
+ * **There is one calendar, not one per application.** A booking made here
40
+ * appears on the workspace's own screen immediately, and a slot their
41
+ * receptionist just took is gone from your next `findSlots`. That is the
42
+ * reason to read it through Connect rather than keeping a copy: two calendars
43
+ * double-book, and nothing reconciles them afterwards.
44
+ *
45
+ * Every method needs a live subscription to your application in the workspace
46
+ * you name. The key says which application is calling; the subscription is
47
+ * what makes you welcome in a given tenant.
48
+ */
49
+ export declare class SchedulingResource {
50
+ private readonly transport;
51
+ constructor(transport: TransportOptions);
52
+ /** What this workspace offers as bookable. Active appointment types only. */
53
+ listEventTypes(workspaceId: string): Promise<BookableEventType[]>;
54
+ /**
55
+ * Free times for one appointment type over one window.
56
+ *
57
+ * **Advisory by construction.** Between this answer and `createBooking`,
58
+ * anyone else on that calendar can take any of these - a member, a voice
59
+ * agent mid-call, someone on the workspace's public booking link. That is
60
+ * why `createBooking` can answer `409`, and why that is an ordinary outcome
61
+ * rather than a fault.
62
+ *
63
+ * The window is clamped silently to now and to the appointment type's own
64
+ * booking horizon, so asking for a year of a sixty-day type returns sixty
65
+ * days rather than an error.
66
+ */
67
+ findSlots(input: FindSlotsInput): Promise<BookingSlots>;
68
+ /** One page of the workspace's diary. */
69
+ listBookings(input: ListBookingsInput): Promise<ListBookingsPage>;
70
+ /**
71
+ * Takes one of the times `findSlots` offered.
72
+ *
73
+ * Holds and confirms in one call - you already have the attendee, so there
74
+ * is no half-finished state for your integration to remember. The slot is
75
+ * re-checked inside a transaction, so a time taken since you read it throws
76
+ * a `ConnectError` with `code: "conflict"` rather than double-booking.
77
+ */
78
+ createBooking(input: CreateBookingInput): Promise<Booking>;
79
+ /**
80
+ * Frees the slot and records why.
81
+ *
82
+ * Not a deletion: the booking stays on the diary as `cancelled` with the
83
+ * reason, which is what makes a late cancellation something anyone can look
84
+ * up afterwards.
85
+ */
86
+ cancelBooking(input: {
87
+ workspaceId: string;
88
+ bookingId: string;
89
+ reason?: string | null;
90
+ }): Promise<Booking>;
91
+ }
@@ -1140,3 +1140,74 @@ export interface ConnectWorkspaceMember {
1140
1140
  granted: string[];
1141
1141
  role: string | null;
1142
1142
  }
1143
+ /**
1144
+ * One bookable thing a workspace offers - "15-minute intro call", "site
1145
+ * survey".
1146
+ *
1147
+ * `locationKind` and never the location itself: the address or the meeting
1148
+ * link belongs to whoever booked, and this is a catalogue.
1149
+ */
1150
+ export interface BookableEventType {
1151
+ id: string;
1152
+ /** Stable and readable, unique within the workspace - branch on this, not `id`. */
1153
+ slug: string;
1154
+ name: string;
1155
+ description: string | null;
1156
+ durationMinutes: number;
1157
+ locationKind: "phone" | "address" | "link";
1158
+ }
1159
+ export interface BookingSlot {
1160
+ /** ISO-8601 instant. Pass this back to `createBooking` unchanged. */
1161
+ startAt: string;
1162
+ endAt: string;
1163
+ }
1164
+ export interface BookingSlots {
1165
+ /**
1166
+ * The **business's** IANA timezone, not yours and not your user's - what to
1167
+ * render a slot's wall time in. A slot shown in the viewer's zone is an hour
1168
+ * the business may not be open.
1169
+ */
1170
+ timezone: string;
1171
+ durationMinutes: number;
1172
+ slots: BookingSlot[];
1173
+ }
1174
+ export interface BookingAttendee {
1175
+ name: string;
1176
+ phone: string;
1177
+ email?: string | null;
1178
+ }
1179
+ export type BookingStatus = "hold" | "confirmed" | "cancelled" | "rescheduled" | "no_show";
1180
+ /**
1181
+ * Where a booking came from - usually the first question anyone asks about an
1182
+ * appointment they did not expect.
1183
+ *
1184
+ * `api` is your integration; `portal` is the workspace's own screen or its
1185
+ * public booking link; `agent-call` is a voice agent, mid-call.
1186
+ */
1187
+ export type BookingSource = "agent-call" | "portal" | "api";
1188
+ export interface Booking {
1189
+ id: string;
1190
+ eventTypeId: string;
1191
+ status: BookingStatus;
1192
+ /** ISO-8601 instants. */
1193
+ startAt: string;
1194
+ endAt: string;
1195
+ /** The zone the two parties agreed the time in, frozen at booking. */
1196
+ timezone: string;
1197
+ attendee: {
1198
+ name: string;
1199
+ phone: string;
1200
+ email: string | null;
1201
+ };
1202
+ notes: string | null;
1203
+ source: BookingSource;
1204
+ cancelledReason: string | null;
1205
+ createdAt: string;
1206
+ }
1207
+ export interface ListBookingsPage {
1208
+ bookings: Booking[];
1209
+ /** Pass back as `cursor`. A cursor belongs to one set of filters. */
1210
+ nextCursor: string | null;
1211
+ hasMore: boolean;
1212
+ totalCount: number;
1213
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmoops/connect-sdk",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "commonjs",