Skip to content

Commit 4072de3

Browse files
authored
Rename reservations to advisory soft holds (#94)
1 parent dfb922b commit 4072de3

22 files changed

Lines changed: 153 additions & 138 deletions

BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ npm_package(
149149
],
150150
package = "@tummycrypt/scheduling-kit",
151151
tags = ["manual"],
152-
version = "0.7.8",
152+
version = "0.8.0",
153153
visibility = ["//visibility:public"],
154154
)
155155

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Subpackage targets (finer-grained caching):
2222

2323
module(
2424
name = "tummycrypt_scheduling_kit",
25-
version = "0.7.8",
25+
version = "0.8.0",
2626
compatibility_level = 1,
2727
)
2828

docs/generated/package-surface.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Generated from `package.json` and the current `src/` tree.
1111
| Field | Value |
1212
| --- | --- |
1313
| Package | `@tummycrypt/scheduling-kit` |
14-
| Version | `0.7.8` |
14+
| Version | `0.8.0` |
1515
| Description | Backend-agnostic scheduling components with alternative payment support |
1616
| Node range | `>=20 <25` |
1717
| Repository | https://github.com/Jesssullivan/scheduling-kit.git |

docs/generated/release-metadata.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ Generated from `package.json`, `MODULE.bazel`, `BUILD.bazel`,
1111

1212
| Surface | Value |
1313
| --- | --- |
14-
| package.json version | `0.7.8` |
15-
| MODULE.bazel version | `0.7.8` |
16-
| BUILD.bazel npm_package version | `0.7.8` |
14+
| package.json version | `0.8.0` |
15+
| MODULE.bazel version | `0.8.0` |
16+
| BUILD.bazel npm_package version | `0.8.0` |
1717
| BUILD.bazel package name | `@tummycrypt/scheduling-kit` |
1818
| .bazelversion | `8.1.1` |
1919
| pnpm packageManager | `pnpm@9.15.9` |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tummycrypt/scheduling-kit",
3-
"version": "0.7.8",
3+
"version": "0.8.0",
44
"description": "Backend-agnostic scheduling components with alternative payment support",
55
"type": "module",
66
"packageManager": "pnpm@9.15.9",

src/adapters/__tests__/homegrown-adapter.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,8 @@ describe("HomegrownAdapter", () => {
308308
"getAvailableDates",
309309
"getAvailableSlots",
310310
"checkSlotAvailability",
311-
"createReservation",
312-
"releaseReservation",
311+
"softHoldSlot",
312+
"releaseSoftHold",
313313
"createBooking",
314314
"createBookingWithPaymentRef",
315315
"getBooking",
@@ -530,13 +530,13 @@ describe("HomegrownAdapter", () => {
530530
// Reservations
531531
// -------------------------------------------------------------------------
532532

533-
describe("createReservation", () => {
534-
it("inserts a reservation and returns SlotReservation", async () => {
533+
describe("softHoldSlot", () => {
534+
it("inserts an advisory soft hold and returns SlotSoftHold", async () => {
535535
const mockDb = createMockDb({ insert: [RESERVATION_ROW] });
536536
const adapter = createAdapter({ getDb: async () => mockDb });
537537

538538
const result = await Effect.runPromise(
539-
adapter.createReservation({
539+
adapter.softHoldSlot({
540540
serviceId: "svc-uuid-1",
541541
providerId: "prac-uuid-1",
542542
datetime: "2026-04-20T14:00:00.000Z",
@@ -561,7 +561,7 @@ describe("HomegrownAdapter", () => {
561561
// The adapter calculates expiresAt internally — we just verify the
562562
// insert goes through and returns the DB row
563563
const result = await Effect.runPromise(
564-
adapter.createReservation({
564+
adapter.softHoldSlot({
565565
serviceId: "svc-uuid-1",
566566
datetime: "2026-04-20T14:00:00.000Z",
567567
duration: 60,
@@ -572,14 +572,14 @@ describe("HomegrownAdapter", () => {
572572
});
573573
});
574574

575-
describe("releaseReservation", () => {
576-
it("sets releasedAt on the reservation", async () => {
575+
describe("releaseSoftHold", () => {
576+
it("sets releasedAt on the soft hold", async () => {
577577
const mockDb = createMockDb();
578578
const adapter = createAdapter({ getDb: async () => mockDb });
579579

580-
// releaseReservation returns void — just ensure no throw
580+
// releaseSoftHold returns void — just ensure no throw
581581
await expect(
582-
Effect.runPromise(adapter.releaseReservation("res-uuid-1")),
582+
Effect.runPromise(adapter.releaseSoftHold("res-uuid-1")),
583583
).resolves.toBeUndefined();
584584

585585
expect(mockDb.update).toHaveBeenCalled();

src/adapters/acuity.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
AvailableDate,
1313
Booking,
1414
BookingRequest,
15-
SlotReservation,
15+
SlotSoftHold,
1616
ClientInfo,
1717
} from '../core/types.js';
1818
import { Errors } from '../core/types.js';
@@ -297,10 +297,10 @@ export const createAcuityAdapter = (config: AcuityAdapterConfig): SchedulingAdap
297297
);
298298
},
299299

300-
// Reservations (via Acuity blocks)
301-
createReservation: ({ providerId, datetime, duration, notes }) => {
300+
// Advisory soft holds (via Acuity blocks)
301+
softHoldSlot: ({ providerId, datetime, duration, notes }) => {
302302
if (!providerId) {
303-
return Effect.fail(Errors.reservation('BLOCK_FAILED', 'Provider ID required for reservation'));
303+
return Effect.fail(Errors.reservation('BLOCK_FAILED', 'Provider ID required for soft hold'));
304304
}
305305

306306
const startTime = new Date(datetime);
@@ -312,25 +312,25 @@ export const createAcuityAdapter = (config: AcuityAdapterConfig): SchedulingAdap
312312
calendarID: parseInt(providerId, 10),
313313
start: startTime.toISOString(),
314314
end: endTime.toISOString(),
315-
notes: notes ?? 'Payment pending - slot reserved',
315+
notes: notes ?? 'Payment pending - advisory soft hold',
316316
}),
317317
(block) => ({
318318
id: String(block.id),
319319
datetime,
320320
duration,
321321
expiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
322322
providerId,
323-
} satisfies SlotReservation)
323+
} satisfies SlotSoftHold)
324324
),
325325
Effect.mapError((e) =>
326-
Errors.reservation('BLOCK_FAILED', e._tag === 'AcuityError' ? e.message : 'Failed to create reservation')
326+
Errors.reservation('BLOCK_FAILED', e._tag === 'AcuityError' ? e.message : 'Failed to create soft hold')
327327
)
328328
);
329329
},
330330

331-
releaseReservation: (reservationId) =>
331+
releaseSoftHold: (softHoldId) =>
332332
pipe(
333-
Effect.map(del<void>(`/blocks/${reservationId}`), () => undefined),
333+
Effect.map(del<void>(`/blocks/${softHoldId}`), () => undefined),
334334
Effect.catchAll(() => Effect.succeed(undefined)) // Ignore errors when releasing
335335
),
336336

src/adapters/calcom.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
TimeSlot,
1515
Booking,
1616
BookingRequest,
17-
SlotReservation,
17+
SlotSoftHold,
1818
ClientInfo,
1919
} from '../core/types.js';
2020
import type { SchedulingResult } from '../core/types.js';
@@ -82,16 +82,16 @@ export const createCalComAdapter = (_config: CalComConfig): SchedulingAdapter =>
8282
datetime: string;
8383
}) => notImplemented<boolean>(),
8484

85-
createReservation: (_params: {
85+
softHoldSlot: (_params: {
8686
serviceId: string;
8787
providerId?: string;
8888
datetime: string;
8989
duration: number;
9090
expirationMinutes?: number;
9191
notes?: string;
92-
}) => notImplemented<SlotReservation>(),
92+
}) => notImplemented<SlotSoftHold>(),
9393

94-
releaseReservation: (_reservationId: string) => notImplemented<void>(),
94+
releaseSoftHold: (_softHoldId: string) => notImplemented<void>(),
9595

9696
createBooking: (_request: BookingRequest) => notImplemented<Booking>(),
9797

src/adapters/homegrown.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
AvailableDate,
1919
Booking,
2020
BookingRequest,
21-
SlotReservation,
21+
SlotSoftHold,
2222
ClientInfo,
2323
SchedulingResult,
2424
BookingStatus,
@@ -295,8 +295,8 @@ export const createHomegrownAdapter = (
295295
),
296296
);
297297

298-
// Active reservations (not expired, not released)
299-
const reservationRows = await d
298+
// Active soft holds (not expired, not released)
299+
const softHoldRows = await d
300300
.select({
301301
datetime: slotReservations.datetime,
302302
duration: slotReservations.duration,
@@ -325,7 +325,7 @@ export const createHomegrownAdapter = (
325325
end: new Date(r.endTime),
326326
});
327327
}
328-
for (const r of reservationRows) {
328+
for (const r of softHoldRows) {
329329
const start = new Date(r.datetime);
330330
occupied.push({
331331
start,
@@ -606,9 +606,9 @@ export const createHomegrownAdapter = (
606606
});
607607
}),
608608

609-
// --- Reservations ---
609+
// --- Advisory soft holds ---
610610

611-
createReservation: (params) =>
611+
softHoldSlot: (params) =>
612612
fromAsync(async () => {
613613
const { slotReservations } = (await loadSchemas()).booking;
614614
const expirationMinutes = params.expirationMinutes ?? 10;
@@ -633,18 +633,18 @@ export const createHomegrownAdapter = (
633633
duration: row.duration,
634634
expiresAt: row.expiresAt,
635635
providerId: params.providerId,
636-
} satisfies SlotReservation;
636+
} satisfies SlotSoftHold;
637637
}),
638638

639-
releaseReservation: (reservationId: string) =>
639+
releaseSoftHold: (softHoldId: string) =>
640640
fromAsync(async () => {
641641
const { slotReservations } = (await loadSchemas()).booking;
642642
const { eq } = await import("drizzle-orm");
643643
await withDb((d) =>
644644
d
645645
.update(slotReservations)
646646
.set({ releasedAt: new Date().toISOString() })
647-
.where(eq(slotReservations.id, reservationId)),
647+
.where(eq(slotReservations.id, softHoldId)),
648648
);
649649
}),
650650

src/adapters/types.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
AvailableDate,
1212
Booking,
1313
BookingRequest,
14-
SlotReservation,
14+
SlotSoftHold,
1515
ClientInfo,
1616
} from '../core/types.js';
1717

@@ -92,26 +92,30 @@ export interface SchedulingAdapter {
9292
}): SchedulingResult<boolean>;
9393

9494
// ---------------------------------------------------------------------------
95-
// Slot Reservation (Hold)
95+
// Slot Soft Hold (Advisory)
9696
// ---------------------------------------------------------------------------
9797

9898
/**
99-
* Create a temporary hold on a time slot while payment is processing.
100-
* This prevents double-booking during the checkout flow.
99+
* Create an advisory temporary hold on a time slot while payment is
100+
* processing. This is not a cross-backend reservation guarantee.
101+
*
102+
* Correctness still belongs to the backing scheduler's final booking
103+
* conflict/rejection semantics, and backends should pair this with their own
104+
* slot-scoped locking when they can.
101105
*/
102-
createReservation(params: {
106+
softHoldSlot(params: {
103107
serviceId: string;
104108
providerId?: string;
105109
datetime: string;
106110
duration: number;
107111
expirationMinutes?: number;
108112
notes?: string;
109-
}): SchedulingResult<SlotReservation>;
113+
}): SchedulingResult<SlotSoftHold>;
110114

111115
/**
112-
* Release a slot reservation (after successful booking or timeout)
116+
* Release a soft hold after successful booking, timeout, or abandonment.
113117
*/
114-
releaseReservation(reservationId: string): SchedulingResult<void>;
118+
releaseSoftHold(softHoldId: string): SchedulingResult<void>;
115119

116120
// ---------------------------------------------------------------------------
117121
// Bookings

0 commit comments

Comments
 (0)