@ordinatio/scheduling-api 0.1.0 → 0.2.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 +29 -0
- package/README.md +87 -1
- package/dist/{chunk-IAQBLF5F.mjs → chunk-MQO3VRMU.mjs} +59 -1
- package/dist/index.d.mts +63 -2
- package/dist/index.d.ts +63 -2
- package/dist/index.js +991 -229
- package/dist/index.mjs +776 -59
- package/dist/schemas/index.d.mts +55 -1
- package/dist/schemas/index.d.ts +55 -1
- package/dist/schemas/index.js +65 -0
- package/dist/schemas/index.mjs +15 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 (2026-10-28)
|
|
4
|
+
|
|
5
|
+
- **Organization brand.** Set `host.brand` to turn it on. Public route: `GET /brand` (what a visitor may see, never an email
|
|
6
|
+
address). Staff routes: `GET`, `PUT` and `DELETE /admin/brand`. A brand with any problem is a 400 listing every problem with its
|
|
7
|
+
field. `host.brand.isSendingDomainVerified(domain, organizationId)` decides which domains an organization may send from; without
|
|
8
|
+
it no sending address is accepted. Queued messages carry the brand fields, and the manual follow-up and reminder texts sign with the
|
|
9
|
+
brand name. With `host.brand` unset the routes answer 501 and messages are unchanged.
|
|
10
|
+
- **Calendar-file event ids** now end in `@ordinatio-scheduling` instead of the name of the business the package was first built for.
|
|
11
|
+
Set `host.icalUidDomain()` to keep an id that calendars have already subscribed to.
|
|
12
|
+
- **Data export and erasure.** Set `host.privacy` to turn them on. Staff routes: `POST /admin/privacy/export` (a JSON file of
|
|
13
|
+
everything held about one person), `POST /admin/privacy/erase` (a preview, or the erasure with `"confirm": true`; 409 while the
|
|
14
|
+
person has upcoming bookings or an open waitlist offer) and `GET /admin/privacy/requests` (the log). The person is named in the
|
|
15
|
+
body, not the address. With `host.privacy` unset the routes answer 501.
|
|
16
|
+
- **Routing forms.** Set `host.routingForms` to turn them on. Public routes: `GET /routing-forms?slug=` (the questions, never
|
|
17
|
+
the rules) and `POST /routing-forms/route` (the answers; the answer is where to send the client, or the form's fallback
|
|
18
|
+
message). Staff routes: `GET` and `POST /admin/routing-forms`, `GET`, `PUT` and `DELETE /admin/routing-forms/[id]`, and
|
|
19
|
+
`POST /admin/routing-forms/[id]/test` (try sample answers, even on a form that is switched off). A form with any problem is
|
|
20
|
+
refused with a 400 that lists every problem with its place. With `host.routingForms` unset the routes answer 501.
|
|
21
|
+
- **Waitlist.** Set `host.waitlist` to turn it on. Public routes: `POST /waitlist` (join a full slot or day; the answer
|
|
22
|
+
holds the client's own `token`), `DELETE /waitlist?token=` (leave), `GET /waitlist/offer?token=` (what was offered),
|
|
23
|
+
`POST /waitlist/offer/accept` (take it; this books through the normal booking route). Staff routes: `GET /admin/waitlist`,
|
|
24
|
+
`DELETE /admin/waitlist/[id]`. Worker route: `POST /internal/waitlist/sweep`. A cancelled, rejected or moved booking
|
|
25
|
+
offers its slot to the first person in line. `host.waitlist.notify` sends the client their link; the
|
|
26
|
+
`waitlist.offered` webhook announces it too. With `host.waitlist` unset the routes answer 501.
|
|
27
|
+
- A client who leaves, or staff who remove someone, while they hold an offer passes the slot to the next person. A failed
|
|
28
|
+
accept leaves the offer open (busy or server error), keeps the client's place (slot taken, 409), or ends it and re-offers
|
|
29
|
+
the slot (a booking rule refuses them). The sweep also settles an accept that never finished.
|
|
30
|
+
- Needs the engine release with the waitlist table.
|
|
31
|
+
|
|
3
32
|
## 0.1.0
|
|
4
33
|
|
|
5
34
|
The first release.
|
package/README.md
CHANGED
|
@@ -256,7 +256,93 @@ Events are queued for you. Sending is done by a timer you run: call `deliverDueW
|
|
|
256
256
|
`@ordinatio/scheduling` every few seconds, or have your worker `POST /internal/webhooks/deliver` (it needs
|
|
257
257
|
`host.internalAccess`). Without `webhooks` on the host the routes answer 501 and nothing is queued. In local
|
|
258
258
|
development, `webhooks: { allowInsecureTargets: true }` lets an endpoint point at `http://localhost`. The engine
|
|
259
|
-
README explains the retry schedule and how a receiver checks the signature.
|
|
259
|
+
README explains the retry schedule and how a receiver checks the signature. The events are `appointment.booked`,
|
|
260
|
+
`appointment.rescheduled`, `appointment.cancelled`, `appointment.no_show` and `waitlist.offered`; `reminder.sent` is sent when
|
|
261
|
+
the worker that sends reminders passes `webhooks: true` to `processPendingNotifications`.
|
|
262
|
+
|
|
263
|
+
## Waitlist
|
|
264
|
+
|
|
265
|
+
Set `waitlist: {}` on the host to let clients wait for a full slot or day. A cancelled, rejected or moved booking offers
|
|
266
|
+
the slot it held to the person who has waited longest.
|
|
267
|
+
|
|
268
|
+
| Route | Who | What it does |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| `POST /waitlist` | public | Joins: `{ "typeId": "...", "day": "2026-11-03", "slotStart": "...", "clientName": "...", "clientEmail": "..." }` (`slotStart`, `calendarId` and `orgSlug` are optional). Refused with 409 while the slot or day still has room. The answer holds the client's own `token`. |
|
|
271
|
+
| `DELETE /waitlist?token=` | public | Leaves the list. |
|
|
272
|
+
| `GET /waitlist/offer?token=` | public | What was offered, until when, and the name the client gave. No email or phone. |
|
|
273
|
+
| `POST /waitlist/offer/accept` | public | `{ "token": "..." }` books the offered slot through the normal booking route, so your rules, messages and calendar sync apply. 409 `SLOT_UNAVAILABLE` if someone booked it first; the client keeps their place. |
|
|
274
|
+
| `GET /admin/waitlist` | admin | The people waiting, oldest first. `?status=` filters. |
|
|
275
|
+
| `DELETE /admin/waitlist/[id]` | admin | Takes someone off. |
|
|
276
|
+
| `POST /internal/waitlist/sweep` | worker | Ends offers whose time ran out, settles an accept that never finished (booked, or back in line), and offers each freed slot to the next person. Call it every minute or so; it answers `{ lapsed, released, booked, reoffered }` (offers ended, accepts sent back in line, accepts found already booked, slots offered onward). A person sent back in line from a stuck accept is first in line again, so they may be offered the same slot. It serves the host's public database; a host with a database per organization calls `expireWaitlistOffers` and `offerFreedSlot` from the engine itself for each one. |
|
|
277
|
+
|
|
278
|
+
`host.waitlist.notify(offer)` is where you send the client their link (put `offer.token` in it and open the offer page, which
|
|
279
|
+
calls the two offer routes). The `waitlist.offered` webhook announces the same thing. `host.waitlist.offerMinutes` sets the
|
|
280
|
+
length of an offer (default 30; never past the start of the slot). An offer is first dibs, not a hold. A type that needs payment
|
|
281
|
+
or with required intake questions has no waitlist. A client who leaves, or staff who remove someone, while they hold an offer
|
|
282
|
+
passes the slot to the next person. If accepting fails because the server is busy the offer stays open; if the slot was
|
|
283
|
+
taken the client keeps their place; if a booking rule refuses them they come off the list and the slot is re-offered.
|
|
284
|
+
Joining does not check that the email belongs to the person joining, and the `waitlist.offered` webhook body contains the
|
|
285
|
+
client's private token (visible to admins in the delivery log), so protect the join page and the endpoint accordingly.
|
|
286
|
+
Without `waitlist` on the host the routes answer 501.
|
|
287
|
+
|
|
288
|
+
## Routing forms
|
|
289
|
+
|
|
290
|
+
Set `routingForms: {}` on the host to let admins build short forms that send a client to the right appointment type or
|
|
291
|
+
calendar. The engine README explains question types, operators and how a form is checked.
|
|
292
|
+
|
|
293
|
+
| Route | Who | What it does |
|
|
294
|
+
|---|---|---|
|
|
295
|
+
| `GET /routing-forms?slug=` | public | The form's questions. Never the rules or the fallback. `orgSlug` names the organization when one database holds several. |
|
|
296
|
+
| `POST /routing-forms/route` | public | `{ "slug": "...", "answers": { "service": "Suit" } }` (send `answers: {}` when every question is optional and unanswered) answers with `{ destination, message, matchedRuleId, usedFallback }`. `destination` has the type and calendar ids, slugs and names to build a booking link from; it is null when the form's fallback is a message. 400 with `details` when the answers do not fit. |
|
|
297
|
+
| `GET /admin/routing-forms`, `POST /admin/routing-forms` | admin | List the forms; save a new one. A form with any problem is refused with a 400 whose `details` list every problem with its place (`rules[1].conditions[0].questionId`). |
|
|
298
|
+
| `GET /admin/routing-forms/[id]`, `PUT`, `DELETE` | admin | Read one in full; change it (the whole result is checked again); remove it. |
|
|
299
|
+
| `POST /admin/routing-forms/[id]/test` | admin | `{ "answers": { ... } }` tries sample answers, even on a form that is switched off, and lists every rule that matched. |
|
|
300
|
+
|
|
301
|
+
A slug is unique across the organizations of one database, so a link by slug alone is not ambiguous; a host with a database per
|
|
302
|
+
organization passes `orgSlug`. The public routes answer 404 for an unknown, switched-off or ambiguous form, and the staff routes
|
|
303
|
+
answer 409 for a slug already in use in the organization (`SCHED_1202`) or the 50-form limit (`SCHED_1204`) and 404 for another organization's form. The test route's
|
|
304
|
+
answer adds `allMatchedRuleIds`, every rule the answers satisfy. `matchedRuleId` is the rule id the admin chose, so pick ids that are
|
|
305
|
+
safe to show. Routing does not stop a client from booking something else: it only suggests where to go. Without
|
|
306
|
+
`routingForms` on the host the routes answer 501.
|
|
307
|
+
|
|
308
|
+
## Organization brand
|
|
309
|
+
|
|
310
|
+
Set `brand: {}` on the host to let each organization carry a name, logo, colours, font and footer on its booking page and in its
|
|
311
|
+
messages, and a sender identity. The engine README explains what each field accepts.
|
|
312
|
+
|
|
313
|
+
| Route | Who | What it does |
|
|
314
|
+
|---|---|---|
|
|
315
|
+
| `GET /brand` | public | The brand a visitor may see: name, logo, colours, font, footer. Never an email address. `orgSlug` names the organization when one database holds several; with none named, the brand of the only organization that has one. 404 when there is none. |
|
|
316
|
+
| `GET /admin/brand`, `PUT /admin/brand`, `DELETE /admin/brand` | admin | Read the whole brand including its email settings (`{ brand: null }` when none); replace it; remove it. A brand with any problem is a 400 whose `details` list every problem with its field. |
|
|
317
|
+
|
|
318
|
+
**Sending from the organization's own domain.** `fromEmail` is accepted only if the host says it may send from that domain:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
brand: { isSendingDomainVerified: async (domain, organizationId) => myMailProvider.isVerified(domain) }
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
With no `isSendingDomainVerified`, every `fromEmail` is refused (`replyToEmail` and the rest are still fine). Your message sender then
|
|
325
|
+
decides how to use the identity (see the engine README). Messages queued when a booking is made, moved or cancelled carry the brand
|
|
326
|
+
fields for templates, and the manual follow-up and reminder texts sign with the brand name when a calendar has no staff name.
|
|
327
|
+
|
|
328
|
+
The booking widget loads `GET /brand` by itself and applies it; a colour the page sets in `theme` always wins over the brand's.
|
|
329
|
+
`icalUidDomain()` on the host sets the domain in calendar-file event ids (default `ordinatio-scheduling`); once calendars have subscribed, keep it unchanged.
|
|
330
|
+
Without `brand` on the host the routes answer 501.
|
|
331
|
+
|
|
332
|
+
## Data export and erasure
|
|
333
|
+
|
|
334
|
+
Set `privacy: {}` on the host to let an admin export or erase everything held about one person. The engine README explains what
|
|
335
|
+
each does, what it keeps, and what it does not reach. The person is named in the request body, never in the address, so an email
|
|
336
|
+
does not end up in access logs.
|
|
337
|
+
|
|
338
|
+
| Route | Who | What it does |
|
|
339
|
+
|---|---|---|
|
|
340
|
+
| `POST /admin/privacy/export` | admin | `{ "email": "..." }` and/or `{ "clientId": "..." }`. Answers with the export as a JSON file to save (`content-disposition: attachment`, never cached). |
|
|
341
|
+
| `POST /admin/privacy/erase` | admin | The same body. Without `"confirm": true` it only previews: `{ erased: false, preview: { counts, blockers, canErase, warnings } }`. With it, erases and answers `{ erased: true, counts }`. 409 (`SCHED_1301`) while the person has upcoming bookings or an open waitlist offer, with the numbers in `details.blockers`. |
|
|
342
|
+
| `GET /admin/privacy/requests` | admin | The log of exports and erasures: when, who asked, how many rows. It holds no names or addresses. |
|
|
343
|
+
|
|
344
|
+
A body that names nobody, names an invalid address, or carries other fields is a 400. Both actions are rate limited and written to
|
|
345
|
+
the host's activity feed with counts only. Without `privacy` on the host the routes answer 501.
|
|
260
346
|
|
|
261
347
|
## Serving the routes
|
|
262
348
|
|
|
@@ -376,6 +376,57 @@ var CreateWebhookEndpointSchema = z5.object({
|
|
|
376
376
|
description: optionalString(200, "Description")
|
|
377
377
|
});
|
|
378
378
|
|
|
379
|
+
// src/schemas/routing.schema.ts
|
|
380
|
+
import { z as z6 } from "zod";
|
|
381
|
+
var bounded = {
|
|
382
|
+
name: z6.string().max(200),
|
|
383
|
+
slug: z6.string().max(100),
|
|
384
|
+
description: z6.string().max(1e3).nullable().optional(),
|
|
385
|
+
isActive: z6.boolean().optional(),
|
|
386
|
+
questions: z6.array(z6.unknown()).max(30),
|
|
387
|
+
rules: z6.array(z6.unknown()).max(60),
|
|
388
|
+
fallback: z6.unknown()
|
|
389
|
+
};
|
|
390
|
+
var CreateRoutingFormSchema = z6.object({
|
|
391
|
+
...bounded,
|
|
392
|
+
fallback: z6.record(z6.string(), z6.unknown())
|
|
393
|
+
});
|
|
394
|
+
var UpdateRoutingFormSchema = z6.object({ ...bounded, fallback: z6.record(z6.string(), z6.unknown()) }).partial().refine((value) => Object.keys(value).length > 0, { message: "Send at least one field to change" });
|
|
395
|
+
var RouteClientSchema = z6.object({
|
|
396
|
+
slug: z6.string().min(1).max(100),
|
|
397
|
+
orgSlug: z6.string().min(1).max(100).optional(),
|
|
398
|
+
answers: z6.record(z6.string(), z6.unknown())
|
|
399
|
+
});
|
|
400
|
+
var TestRoutingFormSchema = z6.object({ answers: z6.record(z6.string(), z6.unknown()) });
|
|
401
|
+
|
|
402
|
+
// src/schemas/privacy.schema.ts
|
|
403
|
+
import { z as z7 } from "zod";
|
|
404
|
+
var subject = {
|
|
405
|
+
email: z7.string().max(320).optional(),
|
|
406
|
+
clientId: z7.string().max(200).optional()
|
|
407
|
+
};
|
|
408
|
+
var namesSomeone = (value) => Boolean(value.email?.trim() || value.clientId?.trim());
|
|
409
|
+
var namesSomeoneMessage = { message: "Send an email address, a client id, or both" };
|
|
410
|
+
var PrivacySubjectSchema = z7.object(subject).strict().refine(namesSomeone, namesSomeoneMessage);
|
|
411
|
+
var PrivacyEraseSchema = z7.object({ ...subject, confirm: z7.boolean().optional() }).strict().refine(namesSomeone, namesSomeoneMessage);
|
|
412
|
+
|
|
413
|
+
// src/schemas/brand.schema.ts
|
|
414
|
+
import { z as z8 } from "zod";
|
|
415
|
+
var optionalText = (max) => z8.string().max(max).nullable().optional();
|
|
416
|
+
var BrandSchema = z8.object({
|
|
417
|
+
displayName: z8.string().max(200),
|
|
418
|
+
logoUrl: optionalText(1e3),
|
|
419
|
+
primaryColor: optionalText(20),
|
|
420
|
+
accentColor: optionalText(20),
|
|
421
|
+
backgroundColor: optionalText(20),
|
|
422
|
+
textColor: optionalText(20),
|
|
423
|
+
fontFamily: optionalText(200),
|
|
424
|
+
footerText: optionalText(1e3),
|
|
425
|
+
replyToEmail: optionalText(400),
|
|
426
|
+
fromName: optionalText(200),
|
|
427
|
+
fromEmail: optionalText(400)
|
|
428
|
+
}).strict();
|
|
429
|
+
|
|
379
430
|
export {
|
|
380
431
|
IntakeFormFieldSchema,
|
|
381
432
|
CreateAppointmentTypeSchema,
|
|
@@ -407,5 +458,12 @@ export {
|
|
|
407
458
|
CreateRecurringSeriesSchema,
|
|
408
459
|
CancelRecurringScopeSchema,
|
|
409
460
|
CancelRecurringSeriesSchema,
|
|
410
|
-
CreateWebhookEndpointSchema
|
|
461
|
+
CreateWebhookEndpointSchema,
|
|
462
|
+
CreateRoutingFormSchema,
|
|
463
|
+
UpdateRoutingFormSchema,
|
|
464
|
+
RouteClientSchema,
|
|
465
|
+
TestRoutingFormSchema,
|
|
466
|
+
PrivacySubjectSchema,
|
|
467
|
+
PrivacyEraseSchema,
|
|
468
|
+
BrandSchema
|
|
411
469
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SchedulingDb, CalendarSyncAdapter, WebhookTransport, PaymentAdapter, AppointmentStatus, CalendarEventRef, CalendarEvent } from '@ordinatio/scheduling';
|
|
2
|
-
export { BookingPageConfig, BookingPageConfigSchema, BookingPageTheme, BookingPageThemeSchema, CancelRecurringScopeSchema, CancelRecurringSeriesInput, CancelRecurringSeriesSchema, CreateAppointmentCategoryInput, CreateAppointmentCategorySchema, CreateAppointmentTypeInput, CreateAppointmentTypeSchema, CreateAvailabilityRuleInput, CreateAvailabilityRuleSchema, CreateCalendarInput, CreateCalendarSchema, CreateCouponCodeInput, CreateCouponCodeSchema, CreateNotificationTemplateInput, CreateNotificationTemplateSchema, CreatePackagePurchaseInput, CreatePackagePurchaseSchema, CreateRecurringSeriesInput, CreateRecurringSeriesSchema, CreateServicePackageInput, CreateServicePackageSchema, CreateWebhookEndpointInput, CreateWebhookEndpointSchema, DEFAULT_CANCELLATION_POLICY, IntakeFormFieldSchema, NoShowPrevention, NoShowPreventionAction, NoShowPreventionActionSchema, NotificationChannelSchema, NotificationTriggerSchema, OrgCancellationPolicy, OrgCancellationPolicySchema, RecurringFrequencySchema, SLOT_GRANULARITY_DEFAULT, SLOT_GRANULARITY_OPTIONS, SlotGranularityMinutes, UpdateAppointmentCategoryInput, UpdateAppointmentCategorySchema, UpdateAppointmentTypeInput, UpdateAppointmentTypeSchema, UpdateBookingPageInput, UpdateBookingPageSchema, UpdateCalendarInput, UpdateCalendarSchema, UpdateCouponCodeInput, UpdateCouponCodeSchema, UpdateNotificationTemplateInput, UpdateNotificationTemplateSchema, UpdatePackagePurchaseInput, UpdatePackagePurchaseSchema, UpdateServicePackageInput, UpdateServicePackageSchema } from './schemas/index.mjs';
|
|
2
|
+
export { BookingPageConfig, BookingPageConfigSchema, BookingPageTheme, BookingPageThemeSchema, BrandSchema, CancelRecurringScopeSchema, CancelRecurringSeriesInput, CancelRecurringSeriesSchema, CreateAppointmentCategoryInput, CreateAppointmentCategorySchema, CreateAppointmentTypeInput, CreateAppointmentTypeSchema, CreateAvailabilityRuleInput, CreateAvailabilityRuleSchema, CreateCalendarInput, CreateCalendarSchema, CreateCouponCodeInput, CreateCouponCodeSchema, CreateNotificationTemplateInput, CreateNotificationTemplateSchema, CreatePackagePurchaseInput, CreatePackagePurchaseSchema, CreateRecurringSeriesInput, CreateRecurringSeriesSchema, CreateRoutingFormInput, CreateRoutingFormSchema, CreateServicePackageInput, CreateServicePackageSchema, CreateWebhookEndpointInput, CreateWebhookEndpointSchema, DEFAULT_CANCELLATION_POLICY, IntakeFormFieldSchema, NoShowPrevention, NoShowPreventionAction, NoShowPreventionActionSchema, NotificationChannelSchema, NotificationTriggerSchema, OrgCancellationPolicy, OrgCancellationPolicySchema, PrivacyEraseSchema, PrivacySubjectSchema, RecurringFrequencySchema, RouteClientSchema, SLOT_GRANULARITY_DEFAULT, SLOT_GRANULARITY_OPTIONS, SlotGranularityMinutes, TestRoutingFormSchema, UpdateAppointmentCategoryInput, UpdateAppointmentCategorySchema, UpdateAppointmentTypeInput, UpdateAppointmentTypeSchema, UpdateBookingPageInput, UpdateBookingPageSchema, UpdateCalendarInput, UpdateCalendarSchema, UpdateCouponCodeInput, UpdateCouponCodeSchema, UpdateNotificationTemplateInput, UpdateNotificationTemplateSchema, UpdatePackagePurchaseInput, UpdatePackagePurchaseSchema, UpdateRoutingFormInput, UpdateRoutingFormSchema, UpdateServicePackageInput, UpdateServicePackageSchema } from './schemas/index.mjs';
|
|
3
3
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
@@ -224,6 +224,45 @@ interface WebhookSettings {
|
|
|
224
224
|
/** Sends one request. Default: Node's own http client, which refuses names that resolve to private addresses. */
|
|
225
225
|
transport?: WebhookTransport;
|
|
226
226
|
}
|
|
227
|
+
/** What the host does with a waitlist offer. Switching the waitlist on is what supplies this object. */
|
|
228
|
+
interface WaitlistSettings {
|
|
229
|
+
/** How long an offer lasts, in minutes, and never past the start of the slot. Default 30. */
|
|
230
|
+
offerMinutes?: number;
|
|
231
|
+
/**
|
|
232
|
+
* Tells the client that a slot is theirs to take: send `token` in a link to the page that calls
|
|
233
|
+
* `GET /waitlist/offer` and `POST /waitlist/offer/accept`. It runs inside the request that cancelled the booking, so it
|
|
234
|
+
* should only queue the message, not wait for it to be delivered. It must not throw; a rejection is logged and the offer
|
|
235
|
+
* stays open until it expires. Without it, only the `waitlist.offered` webhook announces the offer.
|
|
236
|
+
*/
|
|
237
|
+
notify?(offer: {
|
|
238
|
+
organizationId: string;
|
|
239
|
+
token: string;
|
|
240
|
+
clientName: string;
|
|
241
|
+
clientEmail: string;
|
|
242
|
+
clientPhone: string | null;
|
|
243
|
+
typeName: string;
|
|
244
|
+
calendarName: string;
|
|
245
|
+
startTime: Date;
|
|
246
|
+
timezone: string;
|
|
247
|
+
expiresAt: Date;
|
|
248
|
+
}): Promise<void>;
|
|
249
|
+
}
|
|
250
|
+
/** Switches routing forms on. A host that supplies none has them off: the routes answer 501. */
|
|
251
|
+
type RoutingFormSettings = Record<string, never>;
|
|
252
|
+
/**
|
|
253
|
+
* Switches organization branding on: a name, logo and colours for the booking page and messages, and a sender identity.
|
|
254
|
+
* A host that supplies none has it off: the routes answer 501 and no message carries a brand.
|
|
255
|
+
*/
|
|
256
|
+
interface BrandSettings {
|
|
257
|
+
/**
|
|
258
|
+
* Says whether the host may send mail from `domain` (lower case, such as "mail.acme.com"), usually because it has been
|
|
259
|
+
* verified with the mail provider. Without it no organization can set a sending address; it can still set a reply-to.
|
|
260
|
+
* A rejection counts as "no".
|
|
261
|
+
*/
|
|
262
|
+
isSendingDomainVerified?(domain: string, organizationId: string): boolean | Promise<boolean>;
|
|
263
|
+
}
|
|
264
|
+
/** Switches data export and erasure on. A host that supplies none has them off: the routes answer 501. */
|
|
265
|
+
type PrivacySettings = Record<string, never>;
|
|
227
266
|
interface SchedulingHost {
|
|
228
267
|
/**
|
|
229
268
|
* The database for routes that have no signed-in user. Such a route reads the system database before it knows
|
|
@@ -260,6 +299,28 @@ interface SchedulingHost {
|
|
|
260
299
|
* runs on a timer. Needs the webhook tables. Default: off.
|
|
261
300
|
*/
|
|
262
301
|
webhooks?: WebhookSettings | null;
|
|
302
|
+
/**
|
|
303
|
+
* Turns on the waitlist: clients can wait for a full slot or day, and a cancelled or moved booking offers its slot
|
|
304
|
+
* to the first person in line. Needs the waitlist table. Default: off, so the routes answer 501.
|
|
305
|
+
*/
|
|
306
|
+
waitlist?: WaitlistSettings | null;
|
|
307
|
+
/**
|
|
308
|
+
* Turns on routing forms: an admin defines questions and rules, and a public route sends a client to the right
|
|
309
|
+
* appointment type or calendar. Needs the routing form table. Default: off, so the routes answer 501.
|
|
310
|
+
*/
|
|
311
|
+
routingForms?: RoutingFormSettings | null;
|
|
312
|
+
/**
|
|
313
|
+
* Turns on exporting and erasing one person's data (admin only, every request recorded). Needs the data request table.
|
|
314
|
+
* Default: off, so the routes answer 501.
|
|
315
|
+
*/
|
|
316
|
+
privacy?: PrivacySettings | null;
|
|
317
|
+
/**
|
|
318
|
+
* Turns on organization branding. Needs the brand table. Messages carry the brand only if the host's message sender
|
|
319
|
+
* also asks for it (see `processPendingNotifications`' `brand` option). Default: off, so the routes answer 501.
|
|
320
|
+
*/
|
|
321
|
+
brand?: BrandSettings | null;
|
|
322
|
+
/** The domain used after the "@" in calendar-file event ids. Keep it unchanged once calendars have subscribed. Default: ordinatio-scheduling. */
|
|
323
|
+
icalUidDomain?(): string | null | undefined;
|
|
263
324
|
/** Records what happened in the activity feed. Default: nothing is recorded. */
|
|
264
325
|
activity?: ActivityLog;
|
|
265
326
|
/** Finds the organization named by an `orgSlug` query value. Default: one organization, nothing scoped. */
|
|
@@ -608,4 +669,4 @@ declare function createNodeListener(api: {
|
|
|
608
669
|
handle(request: Request): Promise<Response>;
|
|
609
670
|
}, options: NodeListenerOptions): (incoming: IncomingMessage, outgoing: ServerResponse) => void;
|
|
610
671
|
|
|
611
|
-
export { ALL_REFUND_STATUSES, type ActivityEntry, type ActivityLog, type AppointmentEventSink, type AvailabilityCache, BALANCE_APPOINTMENT_SELECT, BALANCE_BLOCKED_APPOINTMENT_STATUS, BALANCE_REQUIRED_PAYMENT_STATUS, BALANCE_REQUIRED_REFUND_STATUS, BalanceIntentGenerationsExhaustedError, type BalancePaymentControl, BalanceRequestInProgressError, type BearerStaffResolverInput, type BookingClientResolver, type BookingPolicy, type CacheOptions, type CalendarConnectOutcome, type CalendarConnector, type CalendarConnectors, type CalendarVisibility, type HttpMethod, type InternalAccess, type NoShowPolicy, type NodeListenerOptions, type Pagination, type PublicBookingContext, type PublicBookingKind, type PublicBookingResolver, type PublicOrganization, type PublicOrganizationResolver, RETRYABLE_REFUND_STATUSES, type RateLimitTier, RefundOutcomePersistenceError, type ResolvedHost, type Route, type RouteContext, type RouteHandler, type SchedulingApi, type SchedulingApiConfig, type SchedulingCache, type SchedulingHost, type SchedulingServiceError, type SignedInPerson, type StaffContext, type StaffContextInput, type StaffDirectory, type StaffResolver, TERMINAL_SUCCESS_STATUSES, TOKEN_TTL_DAYS, UnverifiedPaymentError, type WebhookSettings, type WorkerCommands, attemptLegRefund, createBalanceIntent, createBearerStaffResolver, createMemoryAvailabilityCache, createMemoryCache, createMemoryRateLimiter, createNoBalancePaymentControl, createNoPaymentAdapter, createNodeListener, createSchedulingApi, createSingleOrganizationDirectory, createSingleOrganizationResolver, createStaffContext, decimalToCents, defaultCorsHeaders, getErrorCode, getErrorMessage, getErrorRef, isSchedulingServiceError, isTokenExpired, parsePagination, persistRefundOutcome, recordBalanceIntent, runCalendarRemoveWithoutBlocking, runCalendarSyncWithoutBlocking };
|
|
672
|
+
export { ALL_REFUND_STATUSES, type ActivityEntry, type ActivityLog, type AppointmentEventSink, type AvailabilityCache, BALANCE_APPOINTMENT_SELECT, BALANCE_BLOCKED_APPOINTMENT_STATUS, BALANCE_REQUIRED_PAYMENT_STATUS, BALANCE_REQUIRED_REFUND_STATUS, BalanceIntentGenerationsExhaustedError, type BalancePaymentControl, BalanceRequestInProgressError, type BearerStaffResolverInput, type BookingClientResolver, type BookingPolicy, type BrandSettings, type CacheOptions, type CalendarConnectOutcome, type CalendarConnector, type CalendarConnectors, type CalendarVisibility, type HttpMethod, type InternalAccess, type NoShowPolicy, type NodeListenerOptions, type Pagination, type PrivacySettings, type PublicBookingContext, type PublicBookingKind, type PublicBookingResolver, type PublicOrganization, type PublicOrganizationResolver, RETRYABLE_REFUND_STATUSES, type RateLimitTier, RefundOutcomePersistenceError, type ResolvedHost, type Route, type RouteContext, type RouteHandler, type RoutingFormSettings, type SchedulingApi, type SchedulingApiConfig, type SchedulingCache, type SchedulingHost, type SchedulingServiceError, type SignedInPerson, type StaffContext, type StaffContextInput, type StaffDirectory, type StaffResolver, TERMINAL_SUCCESS_STATUSES, TOKEN_TTL_DAYS, UnverifiedPaymentError, type WaitlistSettings, type WebhookSettings, type WorkerCommands, attemptLegRefund, createBalanceIntent, createBearerStaffResolver, createMemoryAvailabilityCache, createMemoryCache, createMemoryRateLimiter, createNoBalancePaymentControl, createNoPaymentAdapter, createNodeListener, createSchedulingApi, createSingleOrganizationDirectory, createSingleOrganizationResolver, createStaffContext, decimalToCents, defaultCorsHeaders, getErrorCode, getErrorMessage, getErrorRef, isSchedulingServiceError, isTokenExpired, parsePagination, persistRefundOutcome, recordBalanceIntent, runCalendarRemoveWithoutBlocking, runCalendarSyncWithoutBlocking };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SchedulingDb, CalendarSyncAdapter, WebhookTransport, PaymentAdapter, AppointmentStatus, CalendarEventRef, CalendarEvent } from '@ordinatio/scheduling';
|
|
2
|
-
export { BookingPageConfig, BookingPageConfigSchema, BookingPageTheme, BookingPageThemeSchema, CancelRecurringScopeSchema, CancelRecurringSeriesInput, CancelRecurringSeriesSchema, CreateAppointmentCategoryInput, CreateAppointmentCategorySchema, CreateAppointmentTypeInput, CreateAppointmentTypeSchema, CreateAvailabilityRuleInput, CreateAvailabilityRuleSchema, CreateCalendarInput, CreateCalendarSchema, CreateCouponCodeInput, CreateCouponCodeSchema, CreateNotificationTemplateInput, CreateNotificationTemplateSchema, CreatePackagePurchaseInput, CreatePackagePurchaseSchema, CreateRecurringSeriesInput, CreateRecurringSeriesSchema, CreateServicePackageInput, CreateServicePackageSchema, CreateWebhookEndpointInput, CreateWebhookEndpointSchema, DEFAULT_CANCELLATION_POLICY, IntakeFormFieldSchema, NoShowPrevention, NoShowPreventionAction, NoShowPreventionActionSchema, NotificationChannelSchema, NotificationTriggerSchema, OrgCancellationPolicy, OrgCancellationPolicySchema, RecurringFrequencySchema, SLOT_GRANULARITY_DEFAULT, SLOT_GRANULARITY_OPTIONS, SlotGranularityMinutes, UpdateAppointmentCategoryInput, UpdateAppointmentCategorySchema, UpdateAppointmentTypeInput, UpdateAppointmentTypeSchema, UpdateBookingPageInput, UpdateBookingPageSchema, UpdateCalendarInput, UpdateCalendarSchema, UpdateCouponCodeInput, UpdateCouponCodeSchema, UpdateNotificationTemplateInput, UpdateNotificationTemplateSchema, UpdatePackagePurchaseInput, UpdatePackagePurchaseSchema, UpdateServicePackageInput, UpdateServicePackageSchema } from './schemas/index.js';
|
|
2
|
+
export { BookingPageConfig, BookingPageConfigSchema, BookingPageTheme, BookingPageThemeSchema, BrandSchema, CancelRecurringScopeSchema, CancelRecurringSeriesInput, CancelRecurringSeriesSchema, CreateAppointmentCategoryInput, CreateAppointmentCategorySchema, CreateAppointmentTypeInput, CreateAppointmentTypeSchema, CreateAvailabilityRuleInput, CreateAvailabilityRuleSchema, CreateCalendarInput, CreateCalendarSchema, CreateCouponCodeInput, CreateCouponCodeSchema, CreateNotificationTemplateInput, CreateNotificationTemplateSchema, CreatePackagePurchaseInput, CreatePackagePurchaseSchema, CreateRecurringSeriesInput, CreateRecurringSeriesSchema, CreateRoutingFormInput, CreateRoutingFormSchema, CreateServicePackageInput, CreateServicePackageSchema, CreateWebhookEndpointInput, CreateWebhookEndpointSchema, DEFAULT_CANCELLATION_POLICY, IntakeFormFieldSchema, NoShowPrevention, NoShowPreventionAction, NoShowPreventionActionSchema, NotificationChannelSchema, NotificationTriggerSchema, OrgCancellationPolicy, OrgCancellationPolicySchema, PrivacyEraseSchema, PrivacySubjectSchema, RecurringFrequencySchema, RouteClientSchema, SLOT_GRANULARITY_DEFAULT, SLOT_GRANULARITY_OPTIONS, SlotGranularityMinutes, TestRoutingFormSchema, UpdateAppointmentCategoryInput, UpdateAppointmentCategorySchema, UpdateAppointmentTypeInput, UpdateAppointmentTypeSchema, UpdateBookingPageInput, UpdateBookingPageSchema, UpdateCalendarInput, UpdateCalendarSchema, UpdateCouponCodeInput, UpdateCouponCodeSchema, UpdateNotificationTemplateInput, UpdateNotificationTemplateSchema, UpdatePackagePurchaseInput, UpdatePackagePurchaseSchema, UpdateRoutingFormInput, UpdateRoutingFormSchema, UpdateServicePackageInput, UpdateServicePackageSchema } from './schemas/index.js';
|
|
3
3
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
@@ -224,6 +224,45 @@ interface WebhookSettings {
|
|
|
224
224
|
/** Sends one request. Default: Node's own http client, which refuses names that resolve to private addresses. */
|
|
225
225
|
transport?: WebhookTransport;
|
|
226
226
|
}
|
|
227
|
+
/** What the host does with a waitlist offer. Switching the waitlist on is what supplies this object. */
|
|
228
|
+
interface WaitlistSettings {
|
|
229
|
+
/** How long an offer lasts, in minutes, and never past the start of the slot. Default 30. */
|
|
230
|
+
offerMinutes?: number;
|
|
231
|
+
/**
|
|
232
|
+
* Tells the client that a slot is theirs to take: send `token` in a link to the page that calls
|
|
233
|
+
* `GET /waitlist/offer` and `POST /waitlist/offer/accept`. It runs inside the request that cancelled the booking, so it
|
|
234
|
+
* should only queue the message, not wait for it to be delivered. It must not throw; a rejection is logged and the offer
|
|
235
|
+
* stays open until it expires. Without it, only the `waitlist.offered` webhook announces the offer.
|
|
236
|
+
*/
|
|
237
|
+
notify?(offer: {
|
|
238
|
+
organizationId: string;
|
|
239
|
+
token: string;
|
|
240
|
+
clientName: string;
|
|
241
|
+
clientEmail: string;
|
|
242
|
+
clientPhone: string | null;
|
|
243
|
+
typeName: string;
|
|
244
|
+
calendarName: string;
|
|
245
|
+
startTime: Date;
|
|
246
|
+
timezone: string;
|
|
247
|
+
expiresAt: Date;
|
|
248
|
+
}): Promise<void>;
|
|
249
|
+
}
|
|
250
|
+
/** Switches routing forms on. A host that supplies none has them off: the routes answer 501. */
|
|
251
|
+
type RoutingFormSettings = Record<string, never>;
|
|
252
|
+
/**
|
|
253
|
+
* Switches organization branding on: a name, logo and colours for the booking page and messages, and a sender identity.
|
|
254
|
+
* A host that supplies none has it off: the routes answer 501 and no message carries a brand.
|
|
255
|
+
*/
|
|
256
|
+
interface BrandSettings {
|
|
257
|
+
/**
|
|
258
|
+
* Says whether the host may send mail from `domain` (lower case, such as "mail.acme.com"), usually because it has been
|
|
259
|
+
* verified with the mail provider. Without it no organization can set a sending address; it can still set a reply-to.
|
|
260
|
+
* A rejection counts as "no".
|
|
261
|
+
*/
|
|
262
|
+
isSendingDomainVerified?(domain: string, organizationId: string): boolean | Promise<boolean>;
|
|
263
|
+
}
|
|
264
|
+
/** Switches data export and erasure on. A host that supplies none has them off: the routes answer 501. */
|
|
265
|
+
type PrivacySettings = Record<string, never>;
|
|
227
266
|
interface SchedulingHost {
|
|
228
267
|
/**
|
|
229
268
|
* The database for routes that have no signed-in user. Such a route reads the system database before it knows
|
|
@@ -260,6 +299,28 @@ interface SchedulingHost {
|
|
|
260
299
|
* runs on a timer. Needs the webhook tables. Default: off.
|
|
261
300
|
*/
|
|
262
301
|
webhooks?: WebhookSettings | null;
|
|
302
|
+
/**
|
|
303
|
+
* Turns on the waitlist: clients can wait for a full slot or day, and a cancelled or moved booking offers its slot
|
|
304
|
+
* to the first person in line. Needs the waitlist table. Default: off, so the routes answer 501.
|
|
305
|
+
*/
|
|
306
|
+
waitlist?: WaitlistSettings | null;
|
|
307
|
+
/**
|
|
308
|
+
* Turns on routing forms: an admin defines questions and rules, and a public route sends a client to the right
|
|
309
|
+
* appointment type or calendar. Needs the routing form table. Default: off, so the routes answer 501.
|
|
310
|
+
*/
|
|
311
|
+
routingForms?: RoutingFormSettings | null;
|
|
312
|
+
/**
|
|
313
|
+
* Turns on exporting and erasing one person's data (admin only, every request recorded). Needs the data request table.
|
|
314
|
+
* Default: off, so the routes answer 501.
|
|
315
|
+
*/
|
|
316
|
+
privacy?: PrivacySettings | null;
|
|
317
|
+
/**
|
|
318
|
+
* Turns on organization branding. Needs the brand table. Messages carry the brand only if the host's message sender
|
|
319
|
+
* also asks for it (see `processPendingNotifications`' `brand` option). Default: off, so the routes answer 501.
|
|
320
|
+
*/
|
|
321
|
+
brand?: BrandSettings | null;
|
|
322
|
+
/** The domain used after the "@" in calendar-file event ids. Keep it unchanged once calendars have subscribed. Default: ordinatio-scheduling. */
|
|
323
|
+
icalUidDomain?(): string | null | undefined;
|
|
263
324
|
/** Records what happened in the activity feed. Default: nothing is recorded. */
|
|
264
325
|
activity?: ActivityLog;
|
|
265
326
|
/** Finds the organization named by an `orgSlug` query value. Default: one organization, nothing scoped. */
|
|
@@ -608,4 +669,4 @@ declare function createNodeListener(api: {
|
|
|
608
669
|
handle(request: Request): Promise<Response>;
|
|
609
670
|
}, options: NodeListenerOptions): (incoming: IncomingMessage, outgoing: ServerResponse) => void;
|
|
610
671
|
|
|
611
|
-
export { ALL_REFUND_STATUSES, type ActivityEntry, type ActivityLog, type AppointmentEventSink, type AvailabilityCache, BALANCE_APPOINTMENT_SELECT, BALANCE_BLOCKED_APPOINTMENT_STATUS, BALANCE_REQUIRED_PAYMENT_STATUS, BALANCE_REQUIRED_REFUND_STATUS, BalanceIntentGenerationsExhaustedError, type BalancePaymentControl, BalanceRequestInProgressError, type BearerStaffResolverInput, type BookingClientResolver, type BookingPolicy, type CacheOptions, type CalendarConnectOutcome, type CalendarConnector, type CalendarConnectors, type CalendarVisibility, type HttpMethod, type InternalAccess, type NoShowPolicy, type NodeListenerOptions, type Pagination, type PublicBookingContext, type PublicBookingKind, type PublicBookingResolver, type PublicOrganization, type PublicOrganizationResolver, RETRYABLE_REFUND_STATUSES, type RateLimitTier, RefundOutcomePersistenceError, type ResolvedHost, type Route, type RouteContext, type RouteHandler, type SchedulingApi, type SchedulingApiConfig, type SchedulingCache, type SchedulingHost, type SchedulingServiceError, type SignedInPerson, type StaffContext, type StaffContextInput, type StaffDirectory, type StaffResolver, TERMINAL_SUCCESS_STATUSES, TOKEN_TTL_DAYS, UnverifiedPaymentError, type WebhookSettings, type WorkerCommands, attemptLegRefund, createBalanceIntent, createBearerStaffResolver, createMemoryAvailabilityCache, createMemoryCache, createMemoryRateLimiter, createNoBalancePaymentControl, createNoPaymentAdapter, createNodeListener, createSchedulingApi, createSingleOrganizationDirectory, createSingleOrganizationResolver, createStaffContext, decimalToCents, defaultCorsHeaders, getErrorCode, getErrorMessage, getErrorRef, isSchedulingServiceError, isTokenExpired, parsePagination, persistRefundOutcome, recordBalanceIntent, runCalendarRemoveWithoutBlocking, runCalendarSyncWithoutBlocking };
|
|
672
|
+
export { ALL_REFUND_STATUSES, type ActivityEntry, type ActivityLog, type AppointmentEventSink, type AvailabilityCache, BALANCE_APPOINTMENT_SELECT, BALANCE_BLOCKED_APPOINTMENT_STATUS, BALANCE_REQUIRED_PAYMENT_STATUS, BALANCE_REQUIRED_REFUND_STATUS, BalanceIntentGenerationsExhaustedError, type BalancePaymentControl, BalanceRequestInProgressError, type BearerStaffResolverInput, type BookingClientResolver, type BookingPolicy, type BrandSettings, type CacheOptions, type CalendarConnectOutcome, type CalendarConnector, type CalendarConnectors, type CalendarVisibility, type HttpMethod, type InternalAccess, type NoShowPolicy, type NodeListenerOptions, type Pagination, type PrivacySettings, type PublicBookingContext, type PublicBookingKind, type PublicBookingResolver, type PublicOrganization, type PublicOrganizationResolver, RETRYABLE_REFUND_STATUSES, type RateLimitTier, RefundOutcomePersistenceError, type ResolvedHost, type Route, type RouteContext, type RouteHandler, type RoutingFormSettings, type SchedulingApi, type SchedulingApiConfig, type SchedulingCache, type SchedulingHost, type SchedulingServiceError, type SignedInPerson, type StaffContext, type StaffContextInput, type StaffDirectory, type StaffResolver, TERMINAL_SUCCESS_STATUSES, TOKEN_TTL_DAYS, UnverifiedPaymentError, type WaitlistSettings, type WebhookSettings, type WorkerCommands, attemptLegRefund, createBalanceIntent, createBearerStaffResolver, createMemoryAvailabilityCache, createMemoryCache, createMemoryRateLimiter, createNoBalancePaymentControl, createNoPaymentAdapter, createNodeListener, createSchedulingApi, createSingleOrganizationDirectory, createSingleOrganizationResolver, createStaffContext, decimalToCents, defaultCorsHeaders, getErrorCode, getErrorMessage, getErrorRef, isSchedulingServiceError, isTokenExpired, parsePagination, persistRefundOutcome, recordBalanceIntent, runCalendarRemoveWithoutBlocking, runCalendarSyncWithoutBlocking };
|