@proveanything/smartlinks 2.0.70 → 2.1.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/dist/api/asset.d.ts +5 -0
- package/dist/api/asset.js +11 -0
- package/dist/api/bookings.d.ts +89 -0
- package/dist/api/bookings.js +179 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/docs/API_SUMMARY.md +463 -1
- package/dist/docs/app-objects.md +7 -0
- package/dist/docs/assets.md +63 -2
- package/dist/docs/bookings.md +351 -0
- package/dist/docs/overview.md +1 -0
- package/dist/docs/server-functions.md +29 -1
- package/dist/http.js +3 -0
- package/dist/openapi.yaml +3825 -2774
- package/dist/testing/bookingsMemory.d.ts +75 -0
- package/dist/testing/bookingsMemory.js +413 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/index.js +31 -10
- package/dist/types/appManifest.d.ts +55 -0
- package/dist/types/asset.d.ts +25 -1
- package/dist/types/bookings.d.ts +301 -0
- package/dist/types/bookings.js +3 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/freeTimes.d.ts +57 -0
- package/dist/utils/freeTimes.js +206 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/docs/API_SUMMARY.md +463 -1
- package/docs/app-objects.md +7 -0
- package/docs/assets.md +63 -2
- package/docs/bookings.md +351 -0
- package/docs/overview.md +1 -0
- package/docs/server-functions.md +29 -1
- package/openapi.yaml +3825 -2774
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import type { AppDataDeclaration, AppHeadlessDeclaration } from './headless.js';
|
|
4
4
|
import type { RealtimeChannelDeclaration } from './realtime.js';
|
|
5
5
|
import type { AppManifestAdminCards } from './adminCards.js';
|
|
6
|
+
import type { PaginatedResponse } from './appObjects.js';
|
|
7
|
+
import type { Booking, BookingSlot, BookingAllocation, CreateBookingInput, UpdateBookingInput, MoveBookingInput, BookingListQueryParams, CreateSlotInput, UpdateSlotInput, SlotListQueryParams, CreateBlockInput, BlockListQueryParams, FreeBusyQuery, FreeBusyResponse } from './bookings.js';
|
|
6
8
|
/**
|
|
7
9
|
* A bundle (widget or container) as returned by the collection widgets endpoint.
|
|
8
10
|
*
|
|
@@ -416,7 +418,60 @@ export interface ServerFunctionInteractionEvent {
|
|
|
416
418
|
batchId?: string;
|
|
417
419
|
eventType?: string;
|
|
418
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* `ctx.sl.bookings` — THIS app's own bookings, slots and blocks (docs/bookings.md, "In a server
|
|
423
|
+
* function"). Gated by `sl:bookings:read` / `sl:bookings:write` only. Writes are admin-side: any
|
|
424
|
+
* status, the admin zone, any contact — the app's `publicCreate.bookings` rules don't apply, because
|
|
425
|
+
* the function IS the app's rules (hours, queue tokens, payment). The platform's guarantees still do:
|
|
426
|
+
* a clash fails with an Error carrying `code` (SLOT_FULL, RESOURCE_TAKEN, PER_CONTACT_LIMIT,
|
|
427
|
+
* HOLD_EXPIRED, …), `status` and `details`.
|
|
428
|
+
*/
|
|
429
|
+
export interface ServerFunctionBookings {
|
|
430
|
+
/** Book; pass `idempotencyKey` (e.g. the payment intent id) so a retry returns the first booking (`replayed: true`). */
|
|
431
|
+
create(input: CreateBookingInput): Promise<Booking & {
|
|
432
|
+
replayed: boolean;
|
|
433
|
+
}>;
|
|
434
|
+
get(bookingId: string): Promise<Booking | null>;
|
|
435
|
+
query(params?: BookingListQueryParams): Promise<PaginatedResponse<Booking>>;
|
|
436
|
+
/** The signed-in caller's own bookings (empty when anonymous). */
|
|
437
|
+
mine(params?: BookingListQueryParams): Promise<PaginatedResponse<Booking>>;
|
|
438
|
+
update(bookingId: string, fields: UpdateBookingInput): Promise<Booking>;
|
|
439
|
+
/** held / pending / waitlisted → confirmed (idempotent). */
|
|
440
|
+
confirm(bookingId: string): Promise<Booking>;
|
|
441
|
+
/** Cancel and give the places back (idempotent). */
|
|
442
|
+
cancel(bookingId: string, opts?: {
|
|
443
|
+
reason?: string;
|
|
444
|
+
}): Promise<Booking>;
|
|
445
|
+
/** All-or-nothing move to new claims. */
|
|
446
|
+
move(bookingId: string, input: MoveBookingInput): Promise<Booking>;
|
|
447
|
+
extendHold(bookingId: string, holdMinutes: number): Promise<Booking>;
|
|
448
|
+
complete(bookingId: string): Promise<Booking>;
|
|
449
|
+
noShow(bookingId: string): Promise<Booking>;
|
|
450
|
+
/** Busy intervals per resource — feed them to `utils.freeTimes` with your schedule. */
|
|
451
|
+
freebusy(q: FreeBusyQuery): Promise<FreeBusyResponse>;
|
|
452
|
+
slots: {
|
|
453
|
+
create(input: CreateSlotInput): Promise<BookingSlot>;
|
|
454
|
+
createMany(inputs: CreateSlotInput[], opts?: {
|
|
455
|
+
seriesId?: string;
|
|
456
|
+
}): Promise<BookingSlot[]>;
|
|
457
|
+
get(slotId: string): Promise<BookingSlot | null>;
|
|
458
|
+
query(params?: SlotListQueryParams): Promise<PaginatedResponse<BookingSlot>>;
|
|
459
|
+
update(slotId: string, fields: UpdateSlotInput): Promise<BookingSlot>;
|
|
460
|
+
delete(slotId: string): Promise<{
|
|
461
|
+
success: boolean;
|
|
462
|
+
}>;
|
|
463
|
+
};
|
|
464
|
+
blocks: {
|
|
465
|
+
create(input: CreateBlockInput): Promise<BookingAllocation>;
|
|
466
|
+
query(params?: BlockListQueryParams): Promise<PaginatedResponse<BookingAllocation>>;
|
|
467
|
+
delete(blockId: string): Promise<{
|
|
468
|
+
success: boolean;
|
|
469
|
+
}>;
|
|
470
|
+
};
|
|
471
|
+
}
|
|
419
472
|
export interface ServerFunctionSl {
|
|
473
|
+
/** THIS app's bookings, slots and blocks (`sl:bookings:read` / `sl:bookings:write`). */
|
|
474
|
+
bookings: ServerFunctionBookings;
|
|
420
475
|
/**
|
|
421
476
|
* THIS app's records (own namespace): create / update / upsert / delete / get / query / listTypes,
|
|
422
477
|
* plus `claim(fields)` — a race-safe UNIQUE create. The record names a unique key — `singletonPer`
|
package/dist/types/asset.d.ts
CHANGED
|
@@ -39,8 +39,26 @@ export interface Asset {
|
|
|
39
39
|
proofId: string | null;
|
|
40
40
|
/** App that owns this asset, e.g. 'homepage' */
|
|
41
41
|
appId: string | null;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Address of the asset's current **file**. Versioned: it changes when the file is replaced (the old one moves
|
|
44
|
+
* to `versions[]`). For static content, use `assetUrl` instead.
|
|
45
|
+
*/
|
|
43
46
|
url: string;
|
|
47
|
+
/**
|
|
48
|
+
* The **asset's** own address: `https://cdn.smartlinks.app/sites/{collectionId}/a/{assetId}`.
|
|
49
|
+
*
|
|
50
|
+
* It's the address of the asset (the named thing, e.g. "Hero image"), not of one file. It always serves the
|
|
51
|
+
* asset's current file: replace the file and everything using `assetUrl` shows the new one, with no redeploy
|
|
52
|
+
* and no extra API call (the CDN copy is cleared; browsers re-check within a minute). Deleting the asset takes
|
|
53
|
+
* the address down; restoring it brings it back.
|
|
54
|
+
*
|
|
55
|
+
* Use it in static content: a built site's HTML and CSS, emails, banners. Product and collection images are
|
|
56
|
+
* different: they are references in the record (`AssetRef`), and a new image is a record change.
|
|
57
|
+
*
|
|
58
|
+
* `null` for an outside link, and for an asset made before asset addresses existed until it's given one
|
|
59
|
+
* ({@link asset.ensureAssetUrl}).
|
|
60
|
+
*/
|
|
61
|
+
assetUrl: string | null;
|
|
44
62
|
/**
|
|
45
63
|
* CDN URL of the WebP thumbnail (max 512px longest edge, no crop).
|
|
46
64
|
* Always .webp — null until thumbnail generation has run.
|
|
@@ -336,6 +354,12 @@ export interface CreateResumableUploadOptions {
|
|
|
336
354
|
* uploads. When provided, the public resumable route is used.
|
|
337
355
|
*/
|
|
338
356
|
token?: string;
|
|
357
|
+
/**
|
|
358
|
+
* Replace this asset's file instead of creating a new asset (admin). The id and `assetUrl` stay the same,
|
|
359
|
+
* so everything using `assetUrl` shows the new file; the previous file goes into `versions[]`. Any size —
|
|
360
|
+
* use this to replace a large video.
|
|
361
|
+
*/
|
|
362
|
+
replaceAssetId?: string;
|
|
339
363
|
}
|
|
340
364
|
export interface ResumableStartOptions {
|
|
341
365
|
/** Progress callback (0-100), driven by bytes confirmed by storage. */
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import type { PaginatedResponse } from './appObjects.js';
|
|
2
|
+
export type BookingStatus = 'held' | 'pending' | 'confirmed' | 'completed' | 'cancelled' | 'no_show' | 'waitlisted';
|
|
3
|
+
export type BookingVisibility = 'public' | 'owner' | 'admin';
|
|
4
|
+
/** A claim on a resource's time: seats on a slot (`capacity`) or exclusive time (`exclusive`). */
|
|
5
|
+
export interface BookingAllocation {
|
|
6
|
+
id: string;
|
|
7
|
+
bookingId: string | null;
|
|
8
|
+
slotId: string | null;
|
|
9
|
+
resourceType: string;
|
|
10
|
+
resourceId: string;
|
|
11
|
+
/** The time actually blocked — buffers included. */
|
|
12
|
+
startsAt: string;
|
|
13
|
+
endsAt: string;
|
|
14
|
+
quantity: number;
|
|
15
|
+
mode: 'exclusive' | 'capacity';
|
|
16
|
+
active: boolean;
|
|
17
|
+
sourceSystem?: string | null;
|
|
18
|
+
data: Record<string, any>;
|
|
19
|
+
}
|
|
20
|
+
export interface Booking {
|
|
21
|
+
id: string;
|
|
22
|
+
collectionId: string;
|
|
23
|
+
appId: string;
|
|
24
|
+
visibility: BookingVisibility;
|
|
25
|
+
/** Admin / owner callers only. */
|
|
26
|
+
contactId?: string;
|
|
27
|
+
status: BookingStatus;
|
|
28
|
+
/** The customer-visible time (buffers excluded). */
|
|
29
|
+
startsAt: string;
|
|
30
|
+
endsAt: string;
|
|
31
|
+
timezone?: string | null;
|
|
32
|
+
quantity: number;
|
|
33
|
+
holdExpiresAt?: string | null;
|
|
34
|
+
/** Platform flags, e.g. 'hold_expired'. */
|
|
35
|
+
flags: string[];
|
|
36
|
+
/** 'admin' | 'owner' | 'hold_expired' | an app-defined reason. */
|
|
37
|
+
cancelReason?: string | null;
|
|
38
|
+
idempotencyKey?: string | null;
|
|
39
|
+
seriesId?: string | null;
|
|
40
|
+
ref?: string | null;
|
|
41
|
+
customId?: string | null;
|
|
42
|
+
sourceSystem?: string | null;
|
|
43
|
+
productId?: string | null;
|
|
44
|
+
proofId?: string;
|
|
45
|
+
parentType?: string | null;
|
|
46
|
+
parentId?: string | null;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
cancelledAt?: string | null;
|
|
50
|
+
data: Record<string, any>;
|
|
51
|
+
/** Owner + admin only. */
|
|
52
|
+
owner?: Record<string, any>;
|
|
53
|
+
/** Admin only. */
|
|
54
|
+
admin?: Record<string, any>;
|
|
55
|
+
allocations: BookingAllocation[];
|
|
56
|
+
/**
|
|
57
|
+
* Anonymous public creates only, when the app enables `publicCreate.bookings.anonymous.edit.editToken`.
|
|
58
|
+
* Returned ONCE — store it to let the guest view / cancel / move / confirm their booking.
|
|
59
|
+
*/
|
|
60
|
+
editToken?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface BookingSlot {
|
|
63
|
+
id: string;
|
|
64
|
+
collectionId: string;
|
|
65
|
+
appId: string;
|
|
66
|
+
visibility: BookingVisibility;
|
|
67
|
+
resourceType?: string | null;
|
|
68
|
+
resourceId?: string | null;
|
|
69
|
+
startsAt: string;
|
|
70
|
+
endsAt: string;
|
|
71
|
+
timezone?: string | null;
|
|
72
|
+
capacity: number;
|
|
73
|
+
booked: number;
|
|
74
|
+
/** capacity - booked */
|
|
75
|
+
remaining: number;
|
|
76
|
+
perContactLimit?: number | null;
|
|
77
|
+
status: 'open' | 'closed' | 'cancelled';
|
|
78
|
+
seriesId?: string | null;
|
|
79
|
+
ref?: string | null;
|
|
80
|
+
customId?: string | null;
|
|
81
|
+
sourceSystem?: string | null;
|
|
82
|
+
productId?: string | null;
|
|
83
|
+
createdAt: string;
|
|
84
|
+
updatedAt: string;
|
|
85
|
+
data: Record<string, any>;
|
|
86
|
+
owner?: Record<string, any>;
|
|
87
|
+
admin?: Record<string, any>;
|
|
88
|
+
}
|
|
89
|
+
/** One claim in a booking request: seats on a slot, or exclusive time on a resource. */
|
|
90
|
+
export type BookingClaimInput = {
|
|
91
|
+
slotId: string;
|
|
92
|
+
quantity?: number;
|
|
93
|
+
} | {
|
|
94
|
+
resourceType: string;
|
|
95
|
+
resourceId: string;
|
|
96
|
+
/** Defaults to the booking's startsAt / endsAt. */
|
|
97
|
+
startsAt?: string | Date;
|
|
98
|
+
endsAt?: string | Date;
|
|
99
|
+
/** Admin only (public bookings take buffers from the app's rules). Minutes. */
|
|
100
|
+
bufferBefore?: number;
|
|
101
|
+
bufferAfter?: number;
|
|
102
|
+
};
|
|
103
|
+
export interface CreateBookingInput {
|
|
104
|
+
/** Seats on one slot … */
|
|
105
|
+
slotId?: string;
|
|
106
|
+
quantity?: number;
|
|
107
|
+
/** … or exclusive time on one resource … */
|
|
108
|
+
resourceType?: string;
|
|
109
|
+
resourceId?: string;
|
|
110
|
+
resource?: {
|
|
111
|
+
type: string;
|
|
112
|
+
id: string;
|
|
113
|
+
};
|
|
114
|
+
startsAt?: string | Date;
|
|
115
|
+
endsAt?: string | Date;
|
|
116
|
+
/** … or several claims at once (all-or-nothing): a stylist and a chair, two tables. */
|
|
117
|
+
allocations?: BookingClaimInput[];
|
|
118
|
+
/** Admin only — public bookings get the status the app's rules enforce. Default 'confirmed'. */
|
|
119
|
+
status?: 'held' | 'pending' | 'confirmed' | 'waitlisted';
|
|
120
|
+
/** For status 'held' (admin: default 15, max 1440). */
|
|
121
|
+
holdMinutes?: number;
|
|
122
|
+
/** Admin only: blocked time around an exclusive claim, in minutes. */
|
|
123
|
+
bufferBefore?: number;
|
|
124
|
+
bufferAfter?: number;
|
|
125
|
+
/** A retried create with the same key returns the first booking instead of booking twice. */
|
|
126
|
+
idempotencyKey?: string;
|
|
127
|
+
/** Admin only — public bookings use the signed-in caller's contact. */
|
|
128
|
+
contactId?: string;
|
|
129
|
+
timezone?: string;
|
|
130
|
+
visibility?: BookingVisibility;
|
|
131
|
+
ref?: string;
|
|
132
|
+
customId?: string;
|
|
133
|
+
sourceSystem?: string;
|
|
134
|
+
productId?: string;
|
|
135
|
+
proofId?: string;
|
|
136
|
+
parentType?: string;
|
|
137
|
+
parentId?: string;
|
|
138
|
+
seriesId?: string;
|
|
139
|
+
data?: Record<string, any>;
|
|
140
|
+
owner?: Record<string, any>;
|
|
141
|
+
/** Admin only. */
|
|
142
|
+
admin?: Record<string, any>;
|
|
143
|
+
}
|
|
144
|
+
export interface UpdateBookingInput {
|
|
145
|
+
data?: Record<string, any>;
|
|
146
|
+
owner?: Record<string, any>;
|
|
147
|
+
admin?: Record<string, any>;
|
|
148
|
+
visibility?: BookingVisibility;
|
|
149
|
+
ref?: string;
|
|
150
|
+
timezone?: string;
|
|
151
|
+
productId?: string;
|
|
152
|
+
proofId?: string;
|
|
153
|
+
contactId?: string;
|
|
154
|
+
sourceSystem?: string;
|
|
155
|
+
customId?: string;
|
|
156
|
+
flags?: string[];
|
|
157
|
+
}
|
|
158
|
+
export interface MoveBookingInput {
|
|
159
|
+
slotId?: string;
|
|
160
|
+
resourceType?: string;
|
|
161
|
+
resourceId?: string;
|
|
162
|
+
resource?: {
|
|
163
|
+
type: string;
|
|
164
|
+
id: string;
|
|
165
|
+
};
|
|
166
|
+
startsAt?: string | Date;
|
|
167
|
+
endsAt?: string | Date;
|
|
168
|
+
allocations?: BookingClaimInput[];
|
|
169
|
+
bufferBefore?: number;
|
|
170
|
+
bufferAfter?: number;
|
|
171
|
+
}
|
|
172
|
+
export interface BookingListQueryParams {
|
|
173
|
+
status?: string;
|
|
174
|
+
contactId?: string;
|
|
175
|
+
slotId?: string;
|
|
176
|
+
resourceType?: string;
|
|
177
|
+
resourceId?: string;
|
|
178
|
+
/** Bookings overlapping [from, to). */
|
|
179
|
+
from?: string | Date;
|
|
180
|
+
to?: string | Date;
|
|
181
|
+
ref?: string;
|
|
182
|
+
customId?: string;
|
|
183
|
+
sourceSystem?: string;
|
|
184
|
+
productId?: string;
|
|
185
|
+
seriesId?: string;
|
|
186
|
+
/** Default 'starts_at:asc'. */
|
|
187
|
+
sort?: string;
|
|
188
|
+
limit?: number;
|
|
189
|
+
offset?: number;
|
|
190
|
+
}
|
|
191
|
+
export interface CreateSlotInput {
|
|
192
|
+
startsAt: string | Date;
|
|
193
|
+
endsAt: string | Date;
|
|
194
|
+
capacity: number;
|
|
195
|
+
perContactLimit?: number;
|
|
196
|
+
/** What the slot runs on (an instructor, a room) … */
|
|
197
|
+
resourceType?: string;
|
|
198
|
+
resourceId?: string;
|
|
199
|
+
/** … and whether the slot blocks that resource's time (no double-booking across slots). */
|
|
200
|
+
blockResource?: boolean;
|
|
201
|
+
status?: 'open' | 'closed' | 'cancelled';
|
|
202
|
+
timezone?: string;
|
|
203
|
+
visibility?: BookingVisibility;
|
|
204
|
+
seriesId?: string;
|
|
205
|
+
ref?: string;
|
|
206
|
+
customId?: string;
|
|
207
|
+
sourceSystem?: string;
|
|
208
|
+
productId?: string;
|
|
209
|
+
data?: Record<string, any>;
|
|
210
|
+
owner?: Record<string, any>;
|
|
211
|
+
admin?: Record<string, any>;
|
|
212
|
+
}
|
|
213
|
+
export type UpdateSlotInput = Partial<Omit<CreateSlotInput, 'blockResource' | 'seriesId' | 'resourceType' | 'resourceId'>>;
|
|
214
|
+
export interface SlotListQueryParams {
|
|
215
|
+
status?: string;
|
|
216
|
+
/** Only slots with places left. */
|
|
217
|
+
available?: boolean;
|
|
218
|
+
from?: string | Date;
|
|
219
|
+
to?: string | Date;
|
|
220
|
+
resourceType?: string;
|
|
221
|
+
resourceId?: string;
|
|
222
|
+
productId?: string;
|
|
223
|
+
seriesId?: string;
|
|
224
|
+
ref?: string;
|
|
225
|
+
customId?: string;
|
|
226
|
+
sort?: string;
|
|
227
|
+
limit?: number;
|
|
228
|
+
offset?: number;
|
|
229
|
+
}
|
|
230
|
+
export interface CreateBlockInput {
|
|
231
|
+
resourceType: string;
|
|
232
|
+
resourceId: string;
|
|
233
|
+
startsAt: string | Date;
|
|
234
|
+
endsAt: string | Date;
|
|
235
|
+
sourceSystem?: string;
|
|
236
|
+
data?: Record<string, any>;
|
|
237
|
+
}
|
|
238
|
+
export interface BlockListQueryParams {
|
|
239
|
+
resourceType?: string;
|
|
240
|
+
resourceId?: string;
|
|
241
|
+
from?: string | Date;
|
|
242
|
+
to?: string | Date;
|
|
243
|
+
limit?: number;
|
|
244
|
+
offset?: number;
|
|
245
|
+
}
|
|
246
|
+
/** A resource reference: `{ type, id }` or the string form `'record:staff-1'`. */
|
|
247
|
+
export type BookingResourceRef = {
|
|
248
|
+
type: string;
|
|
249
|
+
id: string;
|
|
250
|
+
} | string;
|
|
251
|
+
export interface FreeBusyQuery {
|
|
252
|
+
resources: BookingResourceRef[];
|
|
253
|
+
from: string | Date;
|
|
254
|
+
to: string | Date;
|
|
255
|
+
}
|
|
256
|
+
export interface FreeBusyInterval {
|
|
257
|
+
start: string;
|
|
258
|
+
end: string;
|
|
259
|
+
}
|
|
260
|
+
export interface FreeBusyResponse {
|
|
261
|
+
from: string;
|
|
262
|
+
to: string;
|
|
263
|
+
resources: Array<{
|
|
264
|
+
resourceType: string;
|
|
265
|
+
resourceId: string;
|
|
266
|
+
busy: FreeBusyInterval[];
|
|
267
|
+
}>;
|
|
268
|
+
}
|
|
269
|
+
/** Public calls on a booking made by an anonymous guest: pass the edit token returned at create. */
|
|
270
|
+
export interface BookingAccessOptions {
|
|
271
|
+
editToken?: string;
|
|
272
|
+
}
|
|
273
|
+
export type BookingList = PaginatedResponse<Booking>;
|
|
274
|
+
export type SlotList = PaginatedResponse<BookingSlot>;
|
|
275
|
+
export type BlockList = PaginatedResponse<BookingAllocation>;
|
|
276
|
+
/** Error codes a booking call can fail with (HTTP 409 unless noted). */
|
|
277
|
+
export type BookingErrorCode = 'SLOT_FULL' | 'SLOT_CLOSED' | 'RESOURCE_TAKEN' | 'PER_CONTACT_LIMIT' | 'QUANTITY_LIMIT' | 'HOLD_EXPIRED' | 'INVALID_STATE' | 'SLOT_OVERBOOKED' | 'SLOT_HAS_BOOKINGS' | 'TOO_SOON' | 'TOO_FAR_AHEAD' | 'IN_THE_PAST' | 'DURATION_NOT_ALLOWED' | 'OUTSIDE_SCHEDULE' | 'NO_SCHEDULE' | 'SCHEDULE_INVALID' | 'CUTOFF_PASSED' | 'FORBIDDEN' | 'NOT_FOUND' | 'BAD_REQUEST';
|
|
278
|
+
export interface ScheduleHours {
|
|
279
|
+
/** 'HH:MM' (24h) */
|
|
280
|
+
start: string;
|
|
281
|
+
/** 'HH:MM' (24h); '24:00' allowed. Hours never cross midnight — split them. */
|
|
282
|
+
end: string;
|
|
283
|
+
}
|
|
284
|
+
export type ScheduleDay = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
285
|
+
export interface ScheduleOverride {
|
|
286
|
+
/** One date (YYYY-MM-DD) … */
|
|
287
|
+
date?: string;
|
|
288
|
+
/** … or an inclusive range. */
|
|
289
|
+
from?: string;
|
|
290
|
+
to?: string;
|
|
291
|
+
/** [] = closed */
|
|
292
|
+
hours: ScheduleHours[];
|
|
293
|
+
}
|
|
294
|
+
export interface StandardSchedule {
|
|
295
|
+
/** IANA timezone, e.g. 'Europe/London'. */
|
|
296
|
+
timezone: string;
|
|
297
|
+
/** A missing day is closed. */
|
|
298
|
+
weekly?: Partial<Record<ScheduleDay, ScheduleHours[]>>;
|
|
299
|
+
/** Dated exceptions; the last matching one wins over `weekly`. */
|
|
300
|
+
overrides?: ScheduleOverride[];
|
|
301
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from "./iframeResponder.js";
|
|
|
37
37
|
export * from "./ai.js";
|
|
38
38
|
export * from "./appManifest.js";
|
|
39
39
|
export * from "./appObjects.js";
|
|
40
|
+
export * from "./bookings.js";
|
|
40
41
|
export * from "./loyalty.js";
|
|
41
42
|
export * from "./translations.js";
|
|
42
43
|
export * from "./integrations.js";
|
package/dist/types/index.js
CHANGED
|
@@ -39,6 +39,7 @@ export * from "./iframeResponder.js";
|
|
|
39
39
|
export * from "./ai.js";
|
|
40
40
|
export * from "./appManifest.js";
|
|
41
41
|
export * from "./appObjects.js";
|
|
42
|
+
export * from "./bookings.js";
|
|
42
43
|
export * from "./loyalty.js";
|
|
43
44
|
export * from "./translations.js";
|
|
44
45
|
export * from "./integrations.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { StandardSchedule, FreeBusyInterval } from '../types/bookings.js';
|
|
2
|
+
export interface TimeInterval {
|
|
3
|
+
/** epoch ms (UTC) */
|
|
4
|
+
start: number;
|
|
5
|
+
/** epoch ms (UTC) */
|
|
6
|
+
end: number;
|
|
7
|
+
}
|
|
8
|
+
/** Throws a readable Error when `schedule` isn't the standard shape. */
|
|
9
|
+
export declare function validateSchedule(schedule: StandardSchedule): true;
|
|
10
|
+
/** The UTC instant (ms) of local date `ymd` + `minutes` past midnight in `tz`. */
|
|
11
|
+
export declare function zonedToUtc(ymd: string, minutes: number, tz: string): number;
|
|
12
|
+
/** The schedule's open hours overlapping [from, to), as merged UTC intervals. */
|
|
13
|
+
export declare function openIntervals(schedule: StandardSchedule, from: string | number | Date, to: string | number | Date): TimeInterval[];
|
|
14
|
+
/** True when [start, end) lies entirely inside the schedule's open hours. */
|
|
15
|
+
export declare function withinSchedule(schedule: StandardSchedule, start: string | number | Date, end: string | number | Date): boolean;
|
|
16
|
+
/** `open` minus `busy` (both lists of intervals). */
|
|
17
|
+
export declare function subtractIntervals(open: TimeInterval[], busy: TimeInterval[]): TimeInterval[];
|
|
18
|
+
export interface FreeTimesOptions {
|
|
19
|
+
/** Open hours: a standard schedule, or open intervals you've worked out yourself. */
|
|
20
|
+
hours: StandardSchedule | TimeInterval[];
|
|
21
|
+
/** Busy intervals, e.g. `resources[i].busy` from bookings.freebusy (ISO strings or ms). */
|
|
22
|
+
busy?: Array<FreeBusyInterval | TimeInterval>;
|
|
23
|
+
from: string | number | Date;
|
|
24
|
+
to: string | number | Date;
|
|
25
|
+
/** Appointment length, minutes. */
|
|
26
|
+
duration: number;
|
|
27
|
+
/** Minutes between candidate start times. Default: `duration`. */
|
|
28
|
+
step?: number;
|
|
29
|
+
/** Blocked time around each appointment, minutes (must also fit in a free gap). */
|
|
30
|
+
bufferBefore?: number;
|
|
31
|
+
bufferAfter?: number;
|
|
32
|
+
/** No start sooner than this many minutes from `now`. */
|
|
33
|
+
minNotice?: number;
|
|
34
|
+
/** "Now", for notice (default Date.now()). */
|
|
35
|
+
now?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Whether buffers must also fall inside opening hours. Default false: a buffer may run past
|
|
38
|
+
* closing (cleaning up after the last appointment) but never into busy time.
|
|
39
|
+
*/
|
|
40
|
+
buffersWithinHours?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface FreeTimeSlot {
|
|
43
|
+
/** ISO start of the appointment (buffers excluded). */
|
|
44
|
+
start: string;
|
|
45
|
+
end: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Bookable start times: open hours minus busy time, stepped at `step`, each long enough for
|
|
49
|
+
* `duration` (+ buffers against busy time), not sooner than `minNotice`.
|
|
50
|
+
*
|
|
51
|
+
* const fb = await bookings.freebusy(collectionId, appId, { resources: ['record:sam'], from, to }, true)
|
|
52
|
+
* const times = utils.freeTimes({ hours: samsSchedule, busy: fb.resources[0].busy, from, to, duration: 30 })
|
|
53
|
+
*
|
|
54
|
+
* Start times are aligned to `step` from local midnight in the schedule's timezone (so 30-minute
|
|
55
|
+
* steps land on :00 and :30), or from each open interval's start when you pass intervals.
|
|
56
|
+
*/
|
|
57
|
+
export declare function freeTimes(opts: FreeTimesOptions): FreeTimeSlot[];
|