@cosmoops/connect-sdk 0.16.1 → 0.18.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 +141 -0
- package/README.md +52 -0
- package/dist/cjs/client/components/booking-calendar.js +170 -0
- package/dist/cjs/client/components/booking-dialogs.js +124 -0
- package/dist/cjs/client/components/booking-grid.js +206 -0
- package/dist/cjs/client/index.js +15 -1
- package/dist/cjs/client/use-scheduling.js +113 -0
- package/dist/cjs/server/client.js +5 -1
- package/dist/cjs/server/handler.js +136 -0
- package/dist/cjs/server/resources/scheduling.js +153 -0
- package/dist/esm/client/components/booking-calendar.js +167 -0
- package/dist/esm/client/components/booking-dialogs.js +120 -0
- package/dist/esm/client/components/booking-grid.js +199 -0
- package/dist/esm/client/index.js +4 -0
- package/dist/esm/client/use-scheduling.js +107 -0
- package/dist/esm/server/client.js +5 -1
- package/dist/esm/server/handler.js +136 -0
- package/dist/esm/server/resources/scheduling.js +149 -0
- package/dist/types/client/components/booking-calendar.d.ts +38 -0
- package/dist/types/client/components/booking-dialogs.d.ts +33 -0
- package/dist/types/client/components/booking-grid.d.ts +102 -0
- package/dist/types/client/index.d.ts +5 -0
- package/dist/types/client/use-scheduling.d.ts +93 -0
- package/dist/types/server/client.d.ts +4 -1
- package/dist/types/server/index.d.ts +1 -0
- package/dist/types/server/resources/scheduling.d.ts +136 -0
- package/dist/types/types.d.ts +84 -0
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { BookableEventType, Booking, BookingAttendee } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The bridge's scheduling surface, as hooks.
|
|
4
|
+
*
|
|
5
|
+
* Every path here is one of the six the bridge exposes, and every one of them
|
|
6
|
+
* is resolved *for the signed-in member* - Connect takes the workspace from
|
|
7
|
+
* their session and checks their own `calendar:read` / `calendar:manage`.
|
|
8
|
+
* Nothing in this file names a workspace, and nothing should: a browser that
|
|
9
|
+
* can name a tenant is a browser that can read someone else's diary.
|
|
10
|
+
*/
|
|
11
|
+
/** What this workspace offers as bookable. Active types only. */
|
|
12
|
+
export declare function useBookableEventTypes(): {
|
|
13
|
+
eventTypes: BookableEventType[];
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
error: string | null;
|
|
16
|
+
};
|
|
17
|
+
export interface SlotQuery {
|
|
18
|
+
eventTypeId: string;
|
|
19
|
+
from: Date;
|
|
20
|
+
to: Date;
|
|
21
|
+
/**
|
|
22
|
+
* Set to the booking being moved. Without it the appointment counts as
|
|
23
|
+
* blocking its own replacement and a reschedule picker comes back empty for
|
|
24
|
+
* the one adjustment people actually make.
|
|
25
|
+
*/
|
|
26
|
+
excludeBookingId?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Free times for one appointment type.
|
|
30
|
+
*
|
|
31
|
+
* **Deliberately re-read rather than cached across a session.** A slot list is
|
|
32
|
+
* a snapshot of contention: anyone else on that calendar - a colleague, a
|
|
33
|
+
* voice agent mid-call, someone on the public booking link - can take one
|
|
34
|
+
* between the read and the click. Showing a stale one is how two people are
|
|
35
|
+
* offered the same minute. The booking call re-checks inside a transaction
|
|
36
|
+
* anyway, so a stale list costs a refusal rather than a double booking; this
|
|
37
|
+
* just means the refusal is rare.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useAvailableSlots(query: SlotQuery | null): {
|
|
40
|
+
slots: import("./index.js").BookingSlot[];
|
|
41
|
+
timezone: string | null;
|
|
42
|
+
durationMinutes: number | null;
|
|
43
|
+
isLoading: boolean;
|
|
44
|
+
error: string | null;
|
|
45
|
+
refetch: () => void;
|
|
46
|
+
};
|
|
47
|
+
export interface BookingsQuery {
|
|
48
|
+
from: Date;
|
|
49
|
+
to: Date;
|
|
50
|
+
/** Only appointments this member is hosting. Omit for the whole workspace. */
|
|
51
|
+
hostMemberId?: string;
|
|
52
|
+
limit?: number;
|
|
53
|
+
}
|
|
54
|
+
/** One page of the diary, ascending - a forward view, not a history. */
|
|
55
|
+
export declare function useBookings(query: BookingsQuery): {
|
|
56
|
+
bookings: Booking[];
|
|
57
|
+
hasMore: boolean;
|
|
58
|
+
isLoading: boolean;
|
|
59
|
+
error: string | null;
|
|
60
|
+
path: string;
|
|
61
|
+
refetch: () => void;
|
|
62
|
+
};
|
|
63
|
+
export interface BookingActionsState {
|
|
64
|
+
isPending: boolean;
|
|
65
|
+
/** The last refusal, written to be shown to the person who clicked. */
|
|
66
|
+
error: string | null;
|
|
67
|
+
clearError(): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Book, move and cancel.
|
|
71
|
+
*
|
|
72
|
+
* **None of these is optimistic**, and that is not an oversight. Whether a
|
|
73
|
+
* slot is still free is a question only the server can answer, inside the
|
|
74
|
+
* transaction that decides it - two people can be holding the same minute
|
|
75
|
+
* right now. Painting a booking as made and withdrawing it is exactly the lie
|
|
76
|
+
* the two-phase hold exists to prevent.
|
|
77
|
+
*/
|
|
78
|
+
export declare function useBookingActions(): BookingActionsState & {
|
|
79
|
+
createBooking(input: {
|
|
80
|
+
eventTypeId: string;
|
|
81
|
+
startAt: string;
|
|
82
|
+
attendee: BookingAttendee;
|
|
83
|
+
notes?: string | null;
|
|
84
|
+
}): Promise<Booking | null>;
|
|
85
|
+
rescheduleBooking(input: {
|
|
86
|
+
bookingId: string;
|
|
87
|
+
startAt: string;
|
|
88
|
+
}): Promise<Booking | null>;
|
|
89
|
+
cancelBooking(input: {
|
|
90
|
+
bookingId: string;
|
|
91
|
+
reason?: string | null;
|
|
92
|
+
}): Promise<Booking | null>;
|
|
93
|
+
};
|
|
@@ -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.
|
|
115
|
+
export declare const SDK_VERSION = "0.18.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, RescheduleBookingInput, } 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,136 @@
|
|
|
1
|
+
import { type TransportOptions } from "../http.js";
|
|
2
|
+
import type { BookableEventType, Booking, RescheduledBooking, BookingAttendee, BookingSlots, BookingStatus, ListBookingsPage } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Present on every method here.
|
|
5
|
+
*
|
|
6
|
+
* With `asUser`, Connect derives the workspace from **that member's session**
|
|
7
|
+
* and ignores `workspaceId`, then checks their own `calendar:read` /
|
|
8
|
+
* `calendar:manage`. Without it the call is your server acting for a workspace
|
|
9
|
+
* you name, gated only on your application being installed there. The bridge
|
|
10
|
+
* always sends it - a browser must never be trusted to name a tenant, and an
|
|
11
|
+
* end user of your app is not automatically allowed to book on their
|
|
12
|
+
* employer's calendar.
|
|
13
|
+
*/
|
|
14
|
+
export interface ActingAsOption {
|
|
15
|
+
/** The signed-in member's opaque Connect session token. */
|
|
16
|
+
asUser?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface FindSlotsInput extends ActingAsOption {
|
|
19
|
+
workspaceId: string;
|
|
20
|
+
eventTypeId: string;
|
|
21
|
+
/** ISO-8601 instant, or a `Date`. */
|
|
22
|
+
from: string | Date;
|
|
23
|
+
to: string | Date;
|
|
24
|
+
/**
|
|
25
|
+
* A booking to treat as if it were not there - **set this when offering
|
|
26
|
+
* times to move an existing appointment to.**
|
|
27
|
+
*
|
|
28
|
+
* Without it the appointment counts as blocking its own replacement, so the
|
|
29
|
+
* adjustment people actually make - same day, half an hour later - comes
|
|
30
|
+
* back with nothing available.
|
|
31
|
+
*/
|
|
32
|
+
excludeBookingId?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RescheduleBookingInput extends ActingAsOption {
|
|
35
|
+
workspaceId: string;
|
|
36
|
+
bookingId: string;
|
|
37
|
+
/** The new time, from a `findSlots` result, passed back unchanged. */
|
|
38
|
+
startAt: string | Date;
|
|
39
|
+
/** Replaces the original note when given; it carries over otherwise. */
|
|
40
|
+
notes?: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface ListBookingsInput extends ActingAsOption {
|
|
43
|
+
workspaceId: string;
|
|
44
|
+
status?: BookingStatus;
|
|
45
|
+
/** A Connect workspace member id - only bookings that committed them. */
|
|
46
|
+
hostMemberId?: string;
|
|
47
|
+
from?: string | Date;
|
|
48
|
+
to?: string | Date;
|
|
49
|
+
/** `asc` for a forward diary, `desc` (the default) for a history. */
|
|
50
|
+
order?: "asc" | "desc";
|
|
51
|
+
cursor?: string;
|
|
52
|
+
limit?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface CreateBookingInput extends ActingAsOption {
|
|
55
|
+
workspaceId: string;
|
|
56
|
+
eventTypeId: string;
|
|
57
|
+
/**
|
|
58
|
+
* The `startAt` from a `findSlots` result, passed back unchanged.
|
|
59
|
+
*
|
|
60
|
+
* Re-deriving it from a local wall time is how an integration books an hour
|
|
61
|
+
* that was never offered - the slot list is computed in the *business's*
|
|
62
|
+
* timezone, which is rarely the one your process is running in.
|
|
63
|
+
*/
|
|
64
|
+
startAt: string | Date;
|
|
65
|
+
attendee: BookingAttendee;
|
|
66
|
+
notes?: string | null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A subscribing workspace's own calendar.
|
|
70
|
+
*
|
|
71
|
+
* **There is one calendar, not one per application.** A booking made here
|
|
72
|
+
* appears on the workspace's own screen immediately, and a slot their
|
|
73
|
+
* receptionist just took is gone from your next `findSlots`. That is the
|
|
74
|
+
* reason to read it through Connect rather than keeping a copy: two calendars
|
|
75
|
+
* double-book, and nothing reconciles them afterwards.
|
|
76
|
+
*
|
|
77
|
+
* Every method needs a live subscription to your application in the workspace
|
|
78
|
+
* you name. The key says which application is calling; the subscription is
|
|
79
|
+
* what makes you welcome in a given tenant.
|
|
80
|
+
*/
|
|
81
|
+
export declare class SchedulingResource {
|
|
82
|
+
private readonly transport;
|
|
83
|
+
constructor(transport: TransportOptions);
|
|
84
|
+
/** What this workspace offers as bookable. Active appointment types only. */
|
|
85
|
+
listEventTypes(workspaceId: string, options?: ActingAsOption): Promise<BookableEventType[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Free times for one appointment type over one window.
|
|
88
|
+
*
|
|
89
|
+
* **Advisory by construction.** Between this answer and `createBooking`,
|
|
90
|
+
* anyone else on that calendar can take any of these - a member, a voice
|
|
91
|
+
* agent mid-call, someone on the workspace's public booking link. That is
|
|
92
|
+
* why `createBooking` can answer `409`, and why that is an ordinary outcome
|
|
93
|
+
* rather than a fault.
|
|
94
|
+
*
|
|
95
|
+
* The window is clamped silently to now and to the appointment type's own
|
|
96
|
+
* booking horizon, so asking for a year of a sixty-day type returns sixty
|
|
97
|
+
* days rather than an error.
|
|
98
|
+
*/
|
|
99
|
+
findSlots(input: FindSlotsInput): Promise<BookingSlots>;
|
|
100
|
+
/** One page of the workspace's diary. */
|
|
101
|
+
listBookings(input: ListBookingsInput): Promise<ListBookingsPage>;
|
|
102
|
+
/**
|
|
103
|
+
* Takes one of the times `findSlots` offered.
|
|
104
|
+
*
|
|
105
|
+
* Holds and confirms in one call - you already have the attendee, so there
|
|
106
|
+
* is no half-finished state for your integration to remember. The slot is
|
|
107
|
+
* re-checked inside a transaction, so a time taken since you read it throws
|
|
108
|
+
* a `ConnectError` with `code: "conflict"` rather than double-booking.
|
|
109
|
+
*/
|
|
110
|
+
createBooking(input: CreateBookingInput): Promise<Booking>;
|
|
111
|
+
/**
|
|
112
|
+
* Moves an appointment to a different time.
|
|
113
|
+
*
|
|
114
|
+
* **The original is retired, not edited.** It stays on the diary as
|
|
115
|
+
* `rescheduled` with `rescheduledToId` pointing at the replacement, and both
|
|
116
|
+
* come back here - so a booking id you cached still resolves, and still says
|
|
117
|
+
* where the appointment went.
|
|
118
|
+
*
|
|
119
|
+
* A refused move changes nothing: the new slot is claimed before the old one
|
|
120
|
+
* is released, so a `conflict` leaves the customer with the appointment they
|
|
121
|
+
* already had. Re-read the slots (with `excludeBookingId`) and offer another.
|
|
122
|
+
*/
|
|
123
|
+
rescheduleBooking(input: RescheduleBookingInput): Promise<RescheduledBooking>;
|
|
124
|
+
/**
|
|
125
|
+
* Frees the slot and records why.
|
|
126
|
+
*
|
|
127
|
+
* Not a deletion: the booking stays on the diary as `cancelled` with the
|
|
128
|
+
* reason, which is what makes a late cancellation something anyone can look
|
|
129
|
+
* up afterwards.
|
|
130
|
+
*/
|
|
131
|
+
cancelBooking(input: ActingAsOption & {
|
|
132
|
+
workspaceId: string;
|
|
133
|
+
bookingId: string;
|
|
134
|
+
reason?: string | null;
|
|
135
|
+
}): Promise<Booking>;
|
|
136
|
+
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1140,3 +1140,87 @@ 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
|
+
/**
|
|
1206
|
+
* Set when `status` is `rescheduled` - the booking that replaced this one.
|
|
1207
|
+
*
|
|
1208
|
+
* Rescheduling retires a booking rather than moving its `startAt`, so an id
|
|
1209
|
+
* you cached still resolves and still tells you where the appointment went.
|
|
1210
|
+
*/
|
|
1211
|
+
rescheduledToId: string | null;
|
|
1212
|
+
createdAt: string;
|
|
1213
|
+
}
|
|
1214
|
+
export interface RescheduledBooking {
|
|
1215
|
+
/** The new appointment. This is the live one. */
|
|
1216
|
+
booking: Booking;
|
|
1217
|
+
/** The retired original, now `rescheduled` and pointing at `booking`. */
|
|
1218
|
+
previous: Booking;
|
|
1219
|
+
}
|
|
1220
|
+
export interface ListBookingsPage {
|
|
1221
|
+
bookings: Booking[];
|
|
1222
|
+
/** Pass back as `cursor`. A cursor belongs to one set of filters. */
|
|
1223
|
+
nextCursor: string | null;
|
|
1224
|
+
hasMore: boolean;
|
|
1225
|
+
totalCount: number;
|
|
1226
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|