@agent-native/scheduling 0.2.1 → 0.2.2-nightly-20260927013707
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/actions/_helpers.d.ts +0 -13
- package/dist/actions/_helpers.d.ts.map +1 -1
- package/dist/actions/_helpers.js +0 -16
- package/dist/actions/_helpers.js.map +1 -1
- package/dist/actions/create-team.js +0 -1
- package/dist/actions/create-team.js.map +1 -1
- package/dist/actions/list-bookings.js +0 -4
- package/dist/actions/list-bookings.js.map +1 -1
- package/dist/actions/refresh-busy-times.js +0 -1
- package/dist/actions/refresh-busy-times.js.map +1 -1
- package/dist/actions/revoke-private-link.js +0 -2
- package/dist/actions/revoke-private-link.js.map +1 -1
- package/dist/actions/update-profile.js +0 -5
- package/dist/actions/update-profile.js.map +1 -1
- package/dist/core/buffers.d.ts +0 -18
- package/dist/core/buffers.d.ts.map +1 -1
- package/dist/core/buffers.js +0 -6
- package/dist/core/buffers.js.map +1 -1
- package/dist/core/conflicts.d.ts +0 -4
- package/dist/core/conflicts.d.ts.map +1 -1
- package/dist/core/conflicts.js +0 -1
- package/dist/core/conflicts.js.map +1 -1
- package/dist/core/limits.d.ts +0 -8
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js.map +1 -1
- package/dist/core/round-robin.d.ts +0 -17
- package/dist/core/round-robin.d.ts.map +1 -1
- package/dist/core/round-robin.js.map +1 -1
- package/dist/core/rrule.d.ts.map +1 -1
- package/dist/core/rrule.js +0 -7
- package/dist/core/rrule.js.map +1 -1
- package/dist/core/rules.d.ts +0 -17
- package/dist/core/rules.d.ts.map +1 -1
- package/dist/core/rules.js +0 -8
- package/dist/core/rules.js.map +1 -1
- package/dist/core/slots.d.ts +0 -31
- package/dist/core/slots.d.ts.map +1 -1
- package/dist/core/slots.js +0 -9
- package/dist/core/slots.js.map +1 -1
- package/dist/core/time.d.ts +0 -24
- package/dist/core/time.d.ts.map +1 -1
- package/dist/core/time.js +0 -37
- package/dist/core/time.js.map +1 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -10
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +0 -9
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +0 -18
- package/dist/manifest.js.map +1 -1
- package/dist/react/components/SlotPicker.d.ts +0 -8
- package/dist/react/components/SlotPicker.d.ts.map +1 -1
- package/dist/react/components/SlotPicker.js.map +1 -1
- package/dist/react/components/TimezoneSelect.d.ts.map +1 -1
- package/dist/react/components/TimezoneSelect.js +0 -2
- package/dist/react/components/TimezoneSelect.js.map +1 -1
- package/dist/react/components/booking-links/AvailabilityEditor.d.ts +0 -4
- package/dist/react/components/booking-links/AvailabilityEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/AvailabilityEditor.js +0 -15
- package/dist/react/components/booking-links/AvailabilityEditor.js.map +1 -1
- package/dist/react/components/booking-links/BookingLinkCreateDialog.d.ts +0 -6
- package/dist/react/components/booking-links/BookingLinkCreateDialog.d.ts.map +1 -1
- package/dist/react/components/booking-links/BookingLinkCreateDialog.js +0 -16
- package/dist/react/components/booking-links/BookingLinkCreateDialog.js.map +1 -1
- package/dist/react/components/booking-links/ConferencingSelector.d.ts +0 -2
- package/dist/react/components/booking-links/ConferencingSelector.d.ts.map +1 -1
- package/dist/react/components/booking-links/ConferencingSelector.js +0 -24
- package/dist/react/components/booking-links/ConferencingSelector.js.map +1 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.d.ts +0 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.js +0 -14
- package/dist/react/components/booking-links/CustomFieldsEditor.js.map +1 -1
- package/dist/react/components/booking-links/DurationPicker.d.ts +0 -1
- package/dist/react/components/booking-links/DurationPicker.d.ts.map +1 -1
- package/dist/react/components/booking-links/DurationPicker.js +1 -11
- package/dist/react/components/booking-links/DurationPicker.js.map +1 -1
- package/dist/react/components/booking-links/SlugEditor.d.ts +0 -3
- package/dist/react/components/booking-links/SlugEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/SlugEditor.js +0 -11
- package/dist/react/components/booking-links/SlugEditor.js.map +1 -1
- package/dist/react/components/booking-links/index.d.ts +0 -9
- package/dist/react/components/booking-links/index.d.ts.map +1 -1
- package/dist/react/components/booking-links/index.js +0 -9
- package/dist/react/components/booking-links/index.js.map +1 -1
- package/dist/react/components/index.d.ts +0 -10
- package/dist/react/components/index.d.ts.map +1 -1
- package/dist/react/components/index.js +0 -10
- package/dist/react/components/index.js.map +1 -1
- package/dist/react/hooks/useBookingFlow.d.ts.map +1 -1
- package/dist/react/hooks/useBookingFlow.js +0 -10
- package/dist/react/hooks/useBookingFlow.js.map +1 -1
- package/dist/react/hooks/useEventType.d.ts.map +1 -1
- package/dist/react/hooks/useEventType.js +0 -4
- package/dist/react/hooks/useEventType.js.map +1 -1
- package/dist/react/hooks/useReschedule.d.ts.map +1 -1
- package/dist/react/hooks/useReschedule.js +0 -4
- package/dist/react/hooks/useReschedule.js.map +1 -1
- package/dist/react/hooks/useSlots.d.ts +0 -2
- package/dist/react/hooks/useSlots.d.ts.map +1 -1
- package/dist/react/hooks/useSlots.js +0 -8
- package/dist/react/hooks/useSlots.js.map +1 -1
- package/dist/react/hooks/useTimezone.d.ts.map +1 -1
- package/dist/react/hooks/useTimezone.js +0 -4
- package/dist/react/hooks/useTimezone.js.map +1 -1
- package/dist/react/index.d.ts +0 -5
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +0 -5
- package/dist/react/index.js.map +1 -1
- package/dist/schema/bookings.d.ts +0 -7
- package/dist/schema/bookings.d.ts.map +1 -1
- package/dist/schema/bookings.js +0 -29
- package/dist/schema/bookings.js.map +1 -1
- package/dist/schema/calendar-cache.d.ts.map +1 -1
- package/dist/schema/calendar-cache.js +0 -11
- package/dist/schema/calendar-cache.js.map +1 -1
- package/dist/schema/credentials.d.ts +0 -8
- package/dist/schema/credentials.d.ts.map +1 -1
- package/dist/schema/credentials.js +0 -28
- package/dist/schema/credentials.js.map +1 -1
- package/dist/schema/event-types.d.ts +0 -10
- package/dist/schema/event-types.d.ts.map +1 -1
- package/dist/schema/event-types.js +0 -42
- package/dist/schema/event-types.js.map +1 -1
- package/dist/schema/index.d.ts +0 -11
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +0 -11
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/routing-forms.d.ts.map +1 -1
- package/dist/schema/routing-forms.js +0 -17
- package/dist/schema/routing-forms.js.map +1 -1
- package/dist/schema/schedules.d.ts +0 -12
- package/dist/schema/schedules.d.ts.map +1 -1
- package/dist/schema/schedules.js +0 -25
- package/dist/schema/schedules.js.map +1 -1
- package/dist/schema/teams.d.ts.map +1 -1
- package/dist/schema/teams.js +0 -7
- package/dist/schema/teams.js.map +1 -1
- package/dist/schema/workflows.d.ts +0 -7
- package/dist/schema/workflows.d.ts.map +1 -1
- package/dist/schema/workflows.js +0 -19
- package/dist/schema/workflows.js.map +1 -1
- package/dist/server/availability-engine.d.ts +0 -12
- package/dist/server/availability-engine.d.ts.map +1 -1
- package/dist/server/availability-engine.js +0 -20
- package/dist/server/availability-engine.js.map +1 -1
- package/dist/server/booking-service.d.ts +0 -14
- package/dist/server/booking-service.d.ts.map +1 -1
- package/dist/server/booking-service.js +0 -8
- package/dist/server/booking-service.js.map +1 -1
- package/dist/server/bookings-repo.d.ts +0 -7
- package/dist/server/bookings-repo.d.ts.map +1 -1
- package/dist/server/bookings-repo.js +0 -3
- package/dist/server/bookings-repo.js.map +1 -1
- package/dist/server/context.d.ts +0 -16
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js.map +1 -1
- package/dist/server/db-types.d.ts +0 -11
- package/dist/server/db-types.d.ts.map +1 -1
- package/dist/server/event-types-repo.d.ts +0 -10
- package/dist/server/event-types-repo.d.ts.map +1 -1
- package/dist/server/event-types-repo.js +0 -21
- package/dist/server/event-types-repo.js.map +1 -1
- package/dist/server/hooks.d.ts.map +1 -1
- package/dist/server/hooks.js +0 -11
- package/dist/server/hooks.js.map +1 -1
- package/dist/server/ics.d.ts +0 -7
- package/dist/server/ics.d.ts.map +1 -1
- package/dist/server/ics.js.map +1 -1
- package/dist/server/providers/builtin-video.d.ts +0 -5
- package/dist/server/providers/builtin-video.d.ts.map +1 -1
- package/dist/server/providers/builtin-video.js.map +1 -1
- package/dist/server/providers/google-calendar.d.ts +0 -3
- package/dist/server/providers/google-calendar.d.ts.map +1 -1
- package/dist/server/providers/google-calendar.js +0 -2
- package/dist/server/providers/google-calendar.js.map +1 -1
- package/dist/server/providers/google-meet.d.ts +0 -7
- package/dist/server/providers/google-meet.d.ts.map +1 -1
- package/dist/server/providers/google-meet.js.map +1 -1
- package/dist/server/providers/office365.d.ts +0 -6
- package/dist/server/providers/office365.d.ts.map +1 -1
- package/dist/server/providers/office365.js.map +1 -1
- package/dist/server/providers/registry.d.ts +0 -4
- package/dist/server/providers/registry.d.ts.map +1 -1
- package/dist/server/providers/registry.js.map +1 -1
- package/dist/server/providers/teams.d.ts +0 -6
- package/dist/server/providers/teams.d.ts.map +1 -1
- package/dist/server/providers/teams.js +0 -2
- package/dist/server/providers/teams.js.map +1 -1
- package/dist/server/providers/types.d.ts +0 -27
- package/dist/server/providers/types.d.ts.map +1 -1
- package/dist/server/providers/zoom.d.ts +0 -19
- package/dist/server/providers/zoom.d.ts.map +1 -1
- package/dist/server/providers/zoom.js +0 -2
- package/dist/server/providers/zoom.js.map +1 -1
- package/dist/server/schedules-repo.d.ts.map +1 -1
- package/dist/server/schedules-repo.js +0 -4
- package/dist/server/schedules-repo.js.map +1 -1
- package/dist/server/video-oauth-state.d.ts +0 -5
- package/dist/server/video-oauth-state.d.ts.map +1 -1
- package/dist/server/video-oauth-state.js +1 -9
- package/dist/server/video-oauth-state.js.map +1 -1
- package/dist/server/video-oauth.d.ts +0 -4
- package/dist/server/video-oauth.d.ts.map +1 -1
- package/dist/server/video-oauth.js +0 -23
- package/dist/server/video-oauth.js.map +1 -1
- package/dist/shared/index.d.ts +0 -37
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +0 -4
- package/dist/shared/index.js.map +1 -1
- package/package.json +4 -4
- package/src/actions/_helpers.ts +0 -16
- package/src/actions/booking-authz.spec.ts +0 -6
- package/src/actions/create-team.ts +0 -1
- package/src/actions/event-type-authz.spec.ts +0 -11
- package/src/actions/list-bookings.ts +0 -4
- package/src/actions/refresh-busy-times.ts +0 -1
- package/src/actions/revoke-private-link.ts +0 -2
- package/src/actions/update-profile.ts +0 -5
- package/src/core/buffers.ts +0 -18
- package/src/core/conflicts.ts +0 -4
- package/src/core/limits.ts +0 -8
- package/src/core/round-robin.test.ts +0 -1
- package/src/core/round-robin.ts +0 -17
- package/src/core/rrule.ts +0 -7
- package/src/core/rules.ts +0 -17
- package/src/core/slots.test.ts +1 -11
- package/src/core/slots.ts +0 -40
- package/src/core/time.ts +0 -37
- package/src/index.ts +0 -11
- package/src/manifest.ts +0 -19
- package/src/react/components/SlotPicker.tsx +0 -8
- package/src/react/components/TimezoneSelect.tsx +0 -2
- package/src/react/components/_shadcn-shims.d.ts +0 -35
- package/src/react/components/booking-links/AvailabilityEditor.tsx +0 -15
- package/src/react/components/booking-links/BookingLinkCreateDialog.tsx +0 -22
- package/src/react/components/booking-links/ConferencingSelector.tsx +0 -26
- package/src/react/components/booking-links/CustomFieldsEditor.tsx +0 -15
- package/src/react/components/booking-links/DurationPicker.tsx +1 -12
- package/src/react/components/booking-links/SlugEditor.tsx +0 -14
- package/src/react/components/booking-links/index.ts +0 -9
- package/src/react/components/index.ts +0 -10
- package/src/react/hooks/useBookingFlow.ts +0 -10
- package/src/react/hooks/useEventType.ts +0 -4
- package/src/react/hooks/useReschedule.ts +0 -4
- package/src/react/hooks/useSlots.ts +0 -10
- package/src/react/hooks/useTimezone.ts +0 -4
- package/src/react/index.ts +0 -5
- package/src/schema/bookings.ts +0 -29
- package/src/schema/calendar-cache.ts +0 -11
- package/src/schema/credentials.ts +0 -28
- package/src/schema/event-types.ts +0 -42
- package/src/schema/index.ts +0 -11
- package/src/schema/routing-forms.ts +0 -17
- package/src/schema/schedules.ts +0 -25
- package/src/schema/teams.ts +0 -7
- package/src/schema/workflows.ts +0 -19
- package/src/server/availability-engine.test.ts +0 -1
- package/src/server/availability-engine.ts +0 -22
- package/src/server/booking-service.spec.ts +0 -15
- package/src/server/booking-service.ts +0 -22
- package/src/server/bookings-repo.ts +0 -10
- package/src/server/context.ts +0 -16
- package/src/server/db-types.ts +0 -11
- package/src/server/event-types-repo.ts +0 -31
- package/src/server/hooks.ts +0 -11
- package/src/server/ics.ts +0 -7
- package/src/server/providers/builtin-video.ts +0 -5
- package/src/server/providers/google-calendar.ts +0 -5
- package/src/server/providers/google-meet.ts +0 -7
- package/src/server/providers/office365.ts +0 -6
- package/src/server/providers/registry.ts +0 -4
- package/src/server/providers/teams.ts +0 -8
- package/src/server/providers/types.ts +0 -27
- package/src/server/providers/zoom.ts +0 -21
- package/src/server/schedules-repo.ts +0 -4
- package/src/server/video-oauth-state.ts +1 -10
- package/src/server/video-oauth.ts +0 -23
- package/src/shared/index.ts +0 -38
package/src/actions/_helpers.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for actions in this package.
|
|
3
|
-
*/
|
|
4
1
|
import { and, eq } from "drizzle-orm";
|
|
5
2
|
|
|
6
3
|
import { getSchedulingContext } from "../server/context.js";
|
|
@@ -15,13 +12,6 @@ export function currentUserEmailOrNull(): string | null {
|
|
|
15
12
|
return getSchedulingContext().getCurrentUserEmail() ?? null;
|
|
16
13
|
}
|
|
17
14
|
|
|
18
|
-
/**
|
|
19
|
-
* Verify the current user is the host of a booking. Throws "Not authorized to
|
|
20
|
-
* <action>" if not. Mirrors the `isHost` half of the `isHost || hasToken`
|
|
21
|
-
* guard used by cancel-booking/reschedule-booking, for booking actions that
|
|
22
|
-
* don't accept a public capability token (attendee/note/status mutations —
|
|
23
|
-
* host identity is the only legitimate caller for these).
|
|
24
|
-
*/
|
|
25
15
|
export function assertBookingHost(
|
|
26
16
|
booking: { hostEmail: string },
|
|
27
17
|
action: string,
|
|
@@ -56,12 +46,6 @@ export async function assertTeamAdmin(teamId: string): Promise<void> {
|
|
|
56
46
|
}
|
|
57
47
|
}
|
|
58
48
|
|
|
59
|
-
/**
|
|
60
|
-
* Verify the current user is a member of the team (any role). Read-only
|
|
61
|
-
* team resource listings should gate on this so that team IDs cannot be
|
|
62
|
-
* enumerated by guessing. Throws "Not authenticated" if there is no
|
|
63
|
-
* current user, and "Forbidden" if the user is not a member.
|
|
64
|
-
*/
|
|
65
49
|
export async function assertTeamMember(teamId: string): Promise<void> {
|
|
66
50
|
const { getDb, schema } = getSchedulingContext();
|
|
67
51
|
const email = currentUserEmail();
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-tenant write (IDOR) regression tests for booking actions that
|
|
3
|
-
* mutate — or disclose a capability token for — a booking that belongs to
|
|
4
|
-
* another host. Mirrors the `isHost` guard already proven in
|
|
5
|
-
* `cancel-booking.ts` / `reschedule-booking.ts`.
|
|
6
|
-
*/
|
|
7
1
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
8
2
|
import { tmpdir } from "node:os";
|
|
9
3
|
import { join } from "node:path";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-tenant write (IDOR) regression tests for event-type-scoped actions
|
|
3
|
-
* that mutate a resource owned by another tenant: revoking a private hashed
|
|
4
|
-
* link and duplicating an event type. Mirrors the `assertAccess("event-type",
|
|
5
|
-
* ..., <role>)` guard already used by `add-private-link.ts` /
|
|
6
|
-
* `delete-event-type.ts`.
|
|
7
|
-
*/
|
|
8
1
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
9
2
|
import { tmpdir } from "node:os";
|
|
10
3
|
import { join } from "node:path";
|
|
@@ -164,10 +157,6 @@ beforeEach(async () => {
|
|
|
164
157
|
setSchedulingContext({
|
|
165
158
|
getDb: () => db,
|
|
166
159
|
schema,
|
|
167
|
-
// Mirrors real app wiring (templates/scheduling's server plugin): the
|
|
168
|
-
// scheduling package's own "current user" and the framework's
|
|
169
|
-
// request-context ALS (used by `assertAccess`) both resolve to the same
|
|
170
|
-
// identity.
|
|
171
160
|
getCurrentUserEmail: () => getRequestUserEmail(),
|
|
172
161
|
});
|
|
173
162
|
registerShareableResource({
|
|
@@ -17,10 +17,6 @@ export default defineAction({
|
|
|
17
17
|
from: z.string().optional(),
|
|
18
18
|
to: z.string().optional(),
|
|
19
19
|
limit: z.number().optional(),
|
|
20
|
-
/**
|
|
21
|
-
* If true, narrow to bookings where you are the host. Defaults to false
|
|
22
|
-
* so org-shared bookings + bookings shared with you appear too.
|
|
23
|
-
*/
|
|
24
20
|
onlyMine: z.boolean().optional().default(false),
|
|
25
21
|
}),
|
|
26
22
|
run: async (args) => {
|
|
@@ -12,7 +12,6 @@ export default defineAction({
|
|
|
12
12
|
run: async (args) => {
|
|
13
13
|
const { getDb, schema } = getSchedulingContext();
|
|
14
14
|
const email = args.userEmail ?? currentUserEmail();
|
|
15
|
-
// Delete cache rows for all of this user's credentials
|
|
16
15
|
const creds = await getDb()
|
|
17
16
|
.select({ id: schema.schedulingCredentials.id })
|
|
18
17
|
.from(schema.schedulingCredentials)
|
|
@@ -20,8 +20,6 @@ export default defineAction({
|
|
|
20
20
|
try {
|
|
21
21
|
await assertAccess("event-type", link.eventTypeId, "editor");
|
|
22
22
|
} catch {
|
|
23
|
-
// Keep private-link hashes unprobeable: callers without access receive
|
|
24
|
-
// the same idempotent result as callers presenting an unknown hash.
|
|
25
23
|
return { ok: true };
|
|
26
24
|
}
|
|
27
25
|
await db
|
|
@@ -3,8 +3,6 @@ import { z } from "zod";
|
|
|
3
3
|
|
|
4
4
|
import { currentUserEmail } from "./_helpers.js";
|
|
5
5
|
|
|
6
|
-
// Profile data lives in consumer-provided settings (not the scheduling schema);
|
|
7
|
-
// this action delegates to the core `settings` subsystem via a well-known key.
|
|
8
6
|
export default defineAction({
|
|
9
7
|
description:
|
|
10
8
|
"Update the current user's profile (name, bio, timezone, brand colors, etc.)",
|
|
@@ -20,9 +18,6 @@ export default defineAction({
|
|
|
20
18
|
hideBranding: z.boolean().optional(),
|
|
21
19
|
}),
|
|
22
20
|
run: async (args) => {
|
|
23
|
-
// The consumer template wires a `profile-settings` settings key; we just
|
|
24
|
-
// read/merge/write it. Accessed via dynamic import to avoid a hard dep on
|
|
25
|
-
// the core settings API here (different consumers may use alternate stores).
|
|
26
21
|
const core: any = await import("@agent-native/core");
|
|
27
22
|
if (core.writeSetting) {
|
|
28
23
|
const existing = (await core.readSetting?.("profile-settings")) ?? {};
|
package/src/core/buffers.ts
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Buffer application.
|
|
3
|
-
*
|
|
4
|
-
* Buffers pad a slot with prep/cool-down time that the host needs but the
|
|
5
|
-
* attendee shouldn't see as part of the meeting. A before-event buffer
|
|
6
|
-
* means the slot reserves extra time before its displayed start; the slot
|
|
7
|
-
* itself is still shown at the true meeting time.
|
|
8
|
-
*
|
|
9
|
-
* The practical effect: a slot [10:00–10:30] with before=10 means the host
|
|
10
|
-
* is considered "busy" from 09:50 to 10:30, so it conflicts with a 09:00–09:50
|
|
11
|
-
* meeting only if THAT meeting also has an after-buffer.
|
|
12
|
-
*/
|
|
13
1
|
import type { BusyInterval } from "../shared/index.js";
|
|
14
2
|
import { addMinutes } from "./time.js";
|
|
15
3
|
|
|
16
|
-
/** Expand a busy interval to include before/after buffers. */
|
|
17
4
|
export function applyBuffers(
|
|
18
5
|
busy: BusyInterval,
|
|
19
6
|
beforeMinutes: number,
|
|
@@ -28,11 +15,6 @@ export function applyBuffers(
|
|
|
28
15
|
};
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
/**
|
|
32
|
-
* Expand a proposed slot window (start→end) to include the event type's
|
|
33
|
-
* before/after buffers. The expanded window is what collision detection
|
|
34
|
-
* uses; the displayed slot remains the unexpanded window.
|
|
35
|
-
*/
|
|
36
18
|
export function expandSlotForConflictCheck(
|
|
37
19
|
start: Date,
|
|
38
20
|
end: Date,
|
package/src/core/conflicts.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conflict detection — does a proposed interval overlap any busy interval?
|
|
3
|
-
*/
|
|
4
1
|
import type { BusyInterval } from "../shared/index.js";
|
|
5
2
|
import { overlaps } from "./time.js";
|
|
6
3
|
|
|
@@ -21,7 +18,6 @@ export function hasConflict(
|
|
|
21
18
|
return false;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
/** Merge overlapping busy intervals into a canonical, non-overlapping set. */
|
|
25
21
|
export function mergeBusy(busy: BusyInterval[]): BusyInterval[] {
|
|
26
22
|
if (busy.length === 0) return [];
|
|
27
23
|
const sorted = busy
|
package/src/core/limits.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { TZDate } from "@date-fns/tz";
|
|
2
2
|
import { startOfWeek, startOfMonth, startOfYear, format } from "date-fns";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Booking limits enforcement.
|
|
6
|
-
*
|
|
7
|
-
* Limits constrain how many bookings an event type can receive in a time
|
|
8
|
-
* window (day / week / month / year). We pass in existing booking counts
|
|
9
|
-
* per bucket and return a function that tells us whether a proposed start
|
|
10
|
-
* time would exceed any limit.
|
|
11
|
-
*/
|
|
12
4
|
import type { BookingLimits } from "../shared/index.js";
|
|
13
5
|
|
|
14
6
|
export interface BookingCounts {
|
package/src/core/round-robin.ts
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Round-robin host assignment.
|
|
3
|
-
*
|
|
4
|
-
* For a given event type, pick the host who should take the next booking.
|
|
5
|
-
*
|
|
6
|
-
* Strategies:
|
|
7
|
-
* - "lowest-recent-bookings" — host with fewest bookings in the rolling
|
|
8
|
-
* window (default 30 days) wins; tie-break by priority then weight.
|
|
9
|
-
* - "weighted" — distribute by relative weight; random weighted draw that's
|
|
10
|
-
* deterministic-per-timestamp (seeded for reproducibility in tests).
|
|
11
|
-
* - "calibrated" — weighted but scaled by no-show rate; hosts with high
|
|
12
|
-
* no-show rates receive fewer bookings until their rate recovers.
|
|
13
|
-
*
|
|
14
|
-
* Out-of-office hosts are filtered out before selection.
|
|
15
|
-
*/
|
|
16
1
|
import type { Host, RoundRobinStrategy } from "../shared/index.js";
|
|
17
2
|
|
|
18
3
|
export interface HostMetrics {
|
|
19
4
|
recentBookingCount: number;
|
|
20
|
-
/** 0–1; fraction of recent bookings that were no-shows */
|
|
21
5
|
noShowRate: number;
|
|
22
6
|
}
|
|
23
7
|
|
|
@@ -26,7 +10,6 @@ export interface AssignRoundRobinInput {
|
|
|
26
10
|
metrics: Map<string, HostMetrics>;
|
|
27
11
|
excludeEmails?: Set<string>;
|
|
28
12
|
strategy: RoundRobinStrategy;
|
|
29
|
-
/** Deterministic seed (e.g. booking timestamp) for reproducible weighted picks */
|
|
30
13
|
seed?: number;
|
|
31
14
|
}
|
|
32
15
|
|
package/src/core/rrule.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recurring event expansion.
|
|
3
|
-
*
|
|
4
|
-
* Given an RRULE string and a time range, return the list of occurrence
|
|
5
|
-
* start times. Uses `rrule` npm package under the hood.
|
|
6
|
-
*/
|
|
7
1
|
import { RRule, rrulestr } from "rrule";
|
|
8
2
|
|
|
9
3
|
export function expandRecurring(
|
|
@@ -13,7 +7,6 @@ export function expandRecurring(
|
|
|
13
7
|
rangeEnd: Date,
|
|
14
8
|
maxOccurrences = 10,
|
|
15
9
|
): Date[] {
|
|
16
|
-
// rrulestr parses RRULE:... with or without the DTSTART prefix.
|
|
17
10
|
const rule = rrulestr(rruleString, { dtstart }) as RRule;
|
|
18
11
|
const all = rule.between(rangeStart, rangeEnd, true);
|
|
19
12
|
return all.slice(0, maxOccurrences);
|
package/src/core/rules.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Availability rule evaluation.
|
|
3
|
-
*
|
|
4
|
-
* Given a Schedule (weekly rules + date overrides) and a local date in the
|
|
5
|
-
* schedule's timezone, produce the set of available intervals on that day.
|
|
6
|
-
*
|
|
7
|
-
* Date overrides win over weekly rules: an empty-intervals override blocks
|
|
8
|
-
* the day entirely; a non-empty override replaces the weekly rules for the day.
|
|
9
|
-
*/
|
|
10
1
|
import type {
|
|
11
2
|
AvailabilityInterval,
|
|
12
3
|
DateOverride,
|
|
@@ -19,10 +10,6 @@ export interface ScheduleInput {
|
|
|
19
10
|
dateOverrides: DateOverride[];
|
|
20
11
|
}
|
|
21
12
|
|
|
22
|
-
/**
|
|
23
|
-
* Return the intervals available for a given local date.
|
|
24
|
-
* Intervals are in the schedule's local HH:MM, not UTC.
|
|
25
|
-
*/
|
|
26
13
|
export function evaluateAvailabilityForDate(
|
|
27
14
|
schedule: ScheduleInput,
|
|
28
15
|
localDate: string,
|
|
@@ -34,10 +21,6 @@ export function evaluateAvailabilityForDate(
|
|
|
34
21
|
return weekly?.intervals ?? [];
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
/**
|
|
38
|
-
* Normalize an interval list: sort by start, merge overlapping/adjacent,
|
|
39
|
-
* drop zero-length.
|
|
40
|
-
*/
|
|
41
24
|
export function normalizeIntervals(
|
|
42
25
|
intervals: AvailabilityInterval[],
|
|
43
26
|
): AvailabilityInterval[] {
|
package/src/core/slots.test.ts
CHANGED
|
@@ -17,7 +17,6 @@ const utc: ScheduleInput = {
|
|
|
17
17
|
|
|
18
18
|
describe("computeAvailableSlots", () => {
|
|
19
19
|
it("enumerates 30-minute slots across a single day", () => {
|
|
20
|
-
// Mon 2026-04-06 09:00Z → 17:00Z → 16 slots of 30 min
|
|
21
20
|
const slots = computeAvailableSlots({
|
|
22
21
|
duration: 30,
|
|
23
22
|
minimumBookingNotice: 0,
|
|
@@ -50,7 +49,6 @@ describe("computeAvailableSlots", () => {
|
|
|
50
49
|
rangeEnd: new Date("2026-04-07T00:00:00Z"),
|
|
51
50
|
now: new Date("2026-04-06T10:00:00Z"),
|
|
52
51
|
});
|
|
53
|
-
// now=10:00, +3h → earliest bookable is 13:00
|
|
54
52
|
expect(slots[0].start).toBe("2026-04-06T13:00:00.000Z");
|
|
55
53
|
});
|
|
56
54
|
|
|
@@ -74,7 +72,6 @@ describe("computeAvailableSlots", () => {
|
|
|
74
72
|
s.start < "2026-04-06T11:30:00.000Z",
|
|
75
73
|
);
|
|
76
74
|
expect(busy.length).toBe(0);
|
|
77
|
-
// 11:30 slot should still be available (not overlapping)
|
|
78
75
|
expect(
|
|
79
76
|
slots.find((s) => s.start === "2026-04-06T11:30:00.000Z"),
|
|
80
77
|
).toBeTruthy();
|
|
@@ -94,15 +91,12 @@ describe("computeAvailableSlots", () => {
|
|
|
94
91
|
rangeEnd: new Date("2026-04-07T00:00:00Z"),
|
|
95
92
|
now: new Date("2026-04-06T00:00:00Z"),
|
|
96
93
|
});
|
|
97
|
-
// 10:30 slot + afterBuffer=15 → conflict with 11:00 busy
|
|
98
94
|
expect(
|
|
99
95
|
slots.find((s) => s.start === "2026-04-06T10:30:00.000Z"),
|
|
100
96
|
).toBeFalsy();
|
|
101
|
-
// 11:30 slot - beforeBuffer=15 → 11:15, conflicts with busy end at 11:30
|
|
102
97
|
expect(
|
|
103
98
|
slots.find((s) => s.start === "2026-04-06T11:30:00.000Z"),
|
|
104
99
|
).toBeFalsy();
|
|
105
|
-
// 12:00 slot - 15 → 11:45, fully clear of busy interval [11:00, 11:30)
|
|
106
100
|
expect(
|
|
107
101
|
slots.find((s) => s.start === "2026-04-06T12:00:00.000Z"),
|
|
108
102
|
).toBeTruthy();
|
|
@@ -118,7 +112,6 @@ describe("computeAvailableSlots", () => {
|
|
|
118
112
|
periodType: "unlimited",
|
|
119
113
|
schedule: utc,
|
|
120
114
|
busy: [],
|
|
121
|
-
// 2026-04-04 is a Saturday
|
|
122
115
|
rangeStart: new Date("2026-04-04T00:00:00Z"),
|
|
123
116
|
rangeEnd: new Date("2026-04-05T00:00:00Z"),
|
|
124
117
|
now: new Date("2026-04-04T00:00:00Z"),
|
|
@@ -141,7 +134,6 @@ describe("computeAvailableSlots", () => {
|
|
|
141
134
|
rangeEnd: new Date("2026-04-10T00:00:00Z"),
|
|
142
135
|
now: new Date("2026-04-06T08:00:00Z"),
|
|
143
136
|
});
|
|
144
|
-
// All slots must fall within [now, now+1d]
|
|
145
137
|
for (const s of slots) {
|
|
146
138
|
expect(new Date(s.end).getTime()).toBeLessThanOrEqual(
|
|
147
139
|
new Date("2026-04-07T08:00:00Z").getTime(),
|
|
@@ -150,7 +142,6 @@ describe("computeAvailableSlots", () => {
|
|
|
150
142
|
});
|
|
151
143
|
|
|
152
144
|
it("handles DST transition (spring forward) in America/Los_Angeles", () => {
|
|
153
|
-
// 2026-03-08 is the spring-forward date in US; 2am→3am.
|
|
154
145
|
const tz: ScheduleInput = {
|
|
155
146
|
timezone: "America/Los_Angeles",
|
|
156
147
|
weeklyAvailability: [
|
|
@@ -171,9 +162,8 @@ describe("computeAvailableSlots", () => {
|
|
|
171
162
|
rangeEnd: new Date("2026-03-09T00:00:00Z"),
|
|
172
163
|
now: new Date("2026-03-08T00:00:00Z"),
|
|
173
164
|
});
|
|
174
|
-
// 9-17 PDT on 2026-03-08 = 16:00 UTC to 00:00 UTC next day → 16 30-min slots
|
|
175
165
|
expect(slots.length).toBeGreaterThanOrEqual(15);
|
|
176
|
-
expect(slots[0].start).toBe("2026-03-08T16:00:00.000Z");
|
|
166
|
+
expect(slots[0].start).toBe("2026-03-08T16:00:00.000Z");
|
|
177
167
|
});
|
|
178
168
|
|
|
179
169
|
it("respects seats-per-time-slot", () => {
|
package/src/core/slots.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slot computation — the heart of the scheduling package.
|
|
3
|
-
*
|
|
4
|
-
* Input: an event type's config (duration, buffers, limits, period),
|
|
5
|
-
* a schedule (weekly hours + date overrides),
|
|
6
|
-
* a set of busy intervals (from external calendars + existing bookings),
|
|
7
|
-
* a time range to compute over.
|
|
8
|
-
* Output: an array of Slot objects (UTC start/end) that are available
|
|
9
|
-
* and pass every constraint.
|
|
10
|
-
*
|
|
11
|
-
* Correctness priorities, in order:
|
|
12
|
-
* 1. Never return a slot in the past (respect `now + minimumBookingNotice`)
|
|
13
|
-
* 2. Never return a slot that overlaps a busy interval (with buffers)
|
|
14
|
-
* 3. Never return a slot outside the schedule's available windows
|
|
15
|
-
* 4. Never exceed booking limits (per-day/week/month/year)
|
|
16
|
-
* 5. Respect the event type's period (rolling / range / unlimited)
|
|
17
|
-
* 6. Correctness across DST transitions
|
|
18
|
-
*/
|
|
19
1
|
import type {
|
|
20
2
|
Slot,
|
|
21
3
|
BusyInterval,
|
|
@@ -34,38 +16,25 @@ import {
|
|
|
34
16
|
} from "./time.js";
|
|
35
17
|
|
|
36
18
|
export interface ComputeSlotsInput {
|
|
37
|
-
/** Event type duration, minutes */
|
|
38
19
|
duration: number;
|
|
39
|
-
/** Minutes before event start where the event type becomes unbookable; 0 = up to now */
|
|
40
20
|
minimumBookingNotice: number;
|
|
41
|
-
/** Buffers in minutes applied to any existing busy interval and the candidate slot */
|
|
42
21
|
beforeEventBuffer: number;
|
|
43
22
|
afterEventBuffer: number;
|
|
44
|
-
/** Null = increment by duration */
|
|
45
23
|
slotInterval: number | null;
|
|
46
24
|
periodType: PeriodType;
|
|
47
25
|
periodDays?: number;
|
|
48
26
|
periodStartDate?: string;
|
|
49
27
|
periodEndDate?: string;
|
|
50
28
|
bookingLimits?: BookingLimits;
|
|
51
|
-
/** Schedule — intervals in schedule's timezone */
|
|
52
29
|
schedule: ScheduleInput;
|
|
53
|
-
/** Busy intervals in UTC (already aggregated from all sources) */
|
|
54
30
|
busy: BusyInterval[];
|
|
55
|
-
/** Existing bookings grouped by bucket key — used for limit enforcement */
|
|
56
31
|
bookingCounts?: BookingCounts;
|
|
57
|
-
/** Week start for limit-bucketing */
|
|
58
32
|
weekStartsOn?: 0 | 1;
|
|
59
|
-
/** Range to compute over, UTC */
|
|
60
33
|
rangeStart: Date;
|
|
61
34
|
rangeEnd: Date;
|
|
62
|
-
/** Current time (UTC). Defaults to now. */
|
|
63
35
|
now?: Date;
|
|
64
|
-
/** Seats per slot — if > 1, slot stays available until seats exhausted */
|
|
65
36
|
seatsPerTimeSlot?: number;
|
|
66
|
-
/** Seat count already reserved, keyed by ISO start */
|
|
67
37
|
seatsTaken?: Map<string, number>;
|
|
68
|
-
/** Timezone in which the Booker displays the slots (for limit buckets) */
|
|
69
38
|
viewerTimezone?: string;
|
|
70
39
|
}
|
|
71
40
|
|
|
@@ -82,7 +51,6 @@ export function computeAvailableSlots(input: ComputeSlotsInput): Slot[] {
|
|
|
82
51
|
const viewerTz = input.viewerTimezone ?? input.schedule.timezone;
|
|
83
52
|
const slots: Slot[] = [];
|
|
84
53
|
|
|
85
|
-
// Enumerate each local day in the schedule's timezone that the range touches.
|
|
86
54
|
const dates = localDatesInRange(
|
|
87
55
|
rangeStart,
|
|
88
56
|
rangeEnd,
|
|
@@ -114,19 +82,16 @@ export function computeAvailableSlots(input: ComputeSlotsInput): Slot[] {
|
|
|
114
82
|
while (addMinutes(slotStart, input.duration) <= dayEndUtc) {
|
|
115
83
|
const slotEnd = addMinutes(slotStart, input.duration);
|
|
116
84
|
|
|
117
|
-
// Cap to range
|
|
118
85
|
if (slotStart < rangeStart || slotEnd > rangeEnd) {
|
|
119
86
|
slotStart = addMinutes(slotStart, interval);
|
|
120
87
|
continue;
|
|
121
88
|
}
|
|
122
89
|
|
|
123
|
-
// Respect minimum notice
|
|
124
90
|
if (slotStart < minBookableTime) {
|
|
125
91
|
slotStart = addMinutes(slotStart, interval);
|
|
126
92
|
continue;
|
|
127
93
|
}
|
|
128
94
|
|
|
129
|
-
// Conflict check (expanded with buffers)
|
|
130
95
|
const expanded = expandSlotForConflictCheck(
|
|
131
96
|
slotStart,
|
|
132
97
|
slotEnd,
|
|
@@ -138,7 +103,6 @@ export function computeAvailableSlots(input: ComputeSlotsInput): Slot[] {
|
|
|
138
103
|
continue;
|
|
139
104
|
}
|
|
140
105
|
|
|
141
|
-
// Booking limits (in viewer's timezone for perDay buckets)
|
|
142
106
|
if (
|
|
143
107
|
input.bookingCounts &&
|
|
144
108
|
exceedsLimits(
|
|
@@ -153,7 +117,6 @@ export function computeAvailableSlots(input: ComputeSlotsInput): Slot[] {
|
|
|
153
117
|
continue;
|
|
154
118
|
}
|
|
155
119
|
|
|
156
|
-
// Seats
|
|
157
120
|
const seatsTaken = input.seatsTaken?.get(slotStart.toISOString()) ?? 0;
|
|
158
121
|
const seatsRemaining =
|
|
159
122
|
input.seatsPerTimeSlot != null
|
|
@@ -189,7 +152,6 @@ function capToPeriodStart(
|
|
|
189
152
|
const start = new Date(input.periodStartDate);
|
|
190
153
|
return rangeStart > start ? rangeStart : start;
|
|
191
154
|
}
|
|
192
|
-
// Rolling + unlimited don't restrict start
|
|
193
155
|
return rangeStart > now ? rangeStart : now;
|
|
194
156
|
}
|
|
195
157
|
|
|
@@ -210,8 +172,6 @@ function capToPeriodEnd(
|
|
|
210
172
|
}
|
|
211
173
|
|
|
212
174
|
function dayOfWeekForLocalDate(localDate: string, timezone: string): number {
|
|
213
|
-
// Construct a UTC date that corresponds to noon on that local date in the
|
|
214
|
-
// target timezone, then ask what day-of-week it is in that timezone.
|
|
215
175
|
const noon = zonedTimeToUtc(localDate, "12:00", timezone);
|
|
216
176
|
return getDayOfWeek(noon, timezone);
|
|
217
177
|
}
|
package/src/core/time.ts
CHANGED
|
@@ -1,43 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DST-safe time math.
|
|
3
|
-
*
|
|
4
|
-
* Rules:
|
|
5
|
-
* - Never use raw `Date` arithmetic for anything timezone-aware.
|
|
6
|
-
* - Always work in UTC internally; convert at the edges.
|
|
7
|
-
* - Date strings are ISO-8601 (UTC). Local-day strings are "YYYY-MM-DD".
|
|
8
|
-
* - Times of day are "HH:MM" (24h, no seconds).
|
|
9
|
-
* - Timezones are IANA identifiers like "America/Los_Angeles".
|
|
10
|
-
*/
|
|
11
1
|
import { TZDate } from "@date-fns/tz";
|
|
12
2
|
import { addMinutes as _addMinutes, differenceInMinutes } from "date-fns";
|
|
13
3
|
|
|
14
|
-
/** Parse an ISO 8601 string to a UTC Date. */
|
|
15
4
|
export function parseISO(iso: string): Date {
|
|
16
5
|
return new Date(iso);
|
|
17
6
|
}
|
|
18
7
|
|
|
19
|
-
/** Format a Date to an ISO 8601 UTC string ending in "Z". */
|
|
20
8
|
export function toISO(d: Date): string {
|
|
21
9
|
return d.toISOString();
|
|
22
10
|
}
|
|
23
11
|
|
|
24
|
-
/** Add minutes to a Date (UTC-safe; DST-safe because we stay in UTC). */
|
|
25
12
|
export function addMinutes(d: Date, minutes: number): Date {
|
|
26
13
|
return _addMinutes(d, minutes);
|
|
27
14
|
}
|
|
28
15
|
|
|
29
|
-
/** Minutes between two Dates (end - start). Can be negative. */
|
|
30
16
|
export function minutesBetween(start: Date, end: Date): number {
|
|
31
17
|
return differenceInMinutes(end, start);
|
|
32
18
|
}
|
|
33
19
|
|
|
34
|
-
/**
|
|
35
|
-
* Build a UTC Date from a local date + time of day + timezone.
|
|
36
|
-
*
|
|
37
|
-
* Example:
|
|
38
|
-
* zonedTimeToUtc("2026-04-04", "09:00", "America/Los_Angeles")
|
|
39
|
-
* → Date where UTC hour depends on whether PDT or PST is active.
|
|
40
|
-
*/
|
|
41
20
|
export function zonedTimeToUtc(
|
|
42
21
|
localDate: string,
|
|
43
22
|
localTime: string,
|
|
@@ -45,13 +24,10 @@ export function zonedTimeToUtc(
|
|
|
45
24
|
): Date {
|
|
46
25
|
const [y, m, d] = localDate.split("-").map(Number);
|
|
47
26
|
const [h, mm] = localTime.split(":").map(Number);
|
|
48
|
-
// TZDate constructor takes local-in-timezone components and produces a
|
|
49
|
-
// Date whose UTC value is correct for that wall-clock time in `timezone`.
|
|
50
27
|
const tz = new TZDate(y, (m ?? 1) - 1, d ?? 1, h ?? 0, mm ?? 0, 0, timezone);
|
|
51
28
|
return new Date(tz.getTime());
|
|
52
29
|
}
|
|
53
30
|
|
|
54
|
-
/** Format a UTC Date into a "YYYY-MM-DD" string in the target timezone. */
|
|
55
31
|
export function formatLocalDate(d: Date, timezone: string): string {
|
|
56
32
|
const tz = new TZDate(d.getTime(), timezone);
|
|
57
33
|
const yyyy = tz.getFullYear();
|
|
@@ -60,7 +36,6 @@ export function formatLocalDate(d: Date, timezone: string): string {
|
|
|
60
36
|
return `${yyyy}-${mm}-${dd}`;
|
|
61
37
|
}
|
|
62
38
|
|
|
63
|
-
/** Format a UTC Date into a "HH:MM" string in the target timezone. */
|
|
64
39
|
export function formatLocalTime(d: Date, timezone: string): string {
|
|
65
40
|
const tz = new TZDate(d.getTime(), timezone);
|
|
66
41
|
const hh = String(tz.getHours()).padStart(2, "0");
|
|
@@ -68,13 +43,11 @@ export function formatLocalTime(d: Date, timezone: string): string {
|
|
|
68
43
|
return `${hh}:${mm}`;
|
|
69
44
|
}
|
|
70
45
|
|
|
71
|
-
/** Day of week (0=Sun, 6=Sat) for a UTC Date in the target timezone. */
|
|
72
46
|
export function getDayOfWeek(d: Date, timezone: string): number {
|
|
73
47
|
const tz = new TZDate(d.getTime(), timezone);
|
|
74
48
|
return tz.getDay();
|
|
75
49
|
}
|
|
76
50
|
|
|
77
|
-
/** True if two intervals overlap (end-exclusive). */
|
|
78
51
|
export function overlaps(
|
|
79
52
|
a: { start: Date; end: Date },
|
|
80
53
|
b: { start: Date; end: Date },
|
|
@@ -82,7 +55,6 @@ export function overlaps(
|
|
|
82
55
|
return a.start < b.end && b.start < a.end;
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
/** Clamp a date range to a window. Returns null if they don't intersect. */
|
|
86
58
|
export function clampRange(
|
|
87
59
|
range: { start: Date; end: Date },
|
|
88
60
|
window: { start: Date; end: Date },
|
|
@@ -93,10 +65,6 @@ export function clampRange(
|
|
|
93
65
|
return { start, end };
|
|
94
66
|
}
|
|
95
67
|
|
|
96
|
-
/**
|
|
97
|
-
* Iterate UTC Date objects from start (inclusive) to end (exclusive) by
|
|
98
|
-
* stepping `stepMinutes`. Safe across DST because we never touch local time.
|
|
99
|
-
*/
|
|
100
68
|
export function* steppedDates(
|
|
101
69
|
start: Date,
|
|
102
70
|
end: Date,
|
|
@@ -109,10 +77,6 @@ export function* steppedDates(
|
|
|
109
77
|
}
|
|
110
78
|
}
|
|
111
79
|
|
|
112
|
-
/**
|
|
113
|
-
* Enumerate "YYYY-MM-DD" local dates in `timezone` from `startUtc` to `endUtc`.
|
|
114
|
-
* Useful for iterating per-day availability over a booking window.
|
|
115
|
-
*/
|
|
116
80
|
export function localDatesInRange(
|
|
117
81
|
startUtc: Date,
|
|
118
82
|
endUtc: Date,
|
|
@@ -129,7 +93,6 @@ export function localDatesInRange(
|
|
|
129
93
|
}
|
|
130
94
|
cursor = addMinutes(cursor, 60);
|
|
131
95
|
}
|
|
132
|
-
// Include final local day even if loop stopped exactly at midnight boundary
|
|
133
96
|
const endLocal = formatLocalDate(new Date(endUtc.getTime() - 1), timezone);
|
|
134
97
|
if (out[out.length - 1] !== endLocal) out.push(endLocal);
|
|
135
98
|
return out;
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @agent-native/scheduling
|
|
3
|
-
*
|
|
4
|
-
* Root entry — re-exports shared types and constants only.
|
|
5
|
-
* Subpath imports are the primary API surface:
|
|
6
|
-
*
|
|
7
|
-
* import { eventTypes, bookings } from "@agent-native/scheduling/schema";
|
|
8
|
-
* import { computeAvailableSlots } from "@agent-native/scheduling/core";
|
|
9
|
-
* import { useSlots } from "@agent-native/scheduling/react";
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
export * from "./shared/index.js";
|
|
13
2
|
export { MANIFEST } from "./manifest.js";
|
|
14
3
|
export { VERSION } from "./version.js";
|