Backend-agnostic scheduling system with Svelte 5 components, multiple scheduling adapters, and alternative payment support. Built with Effect for typed workflows and Zod for runtime validation.
Docs, prebuilts, packages and blog post to come later. Another tinyland artifact it is time to publish. This package powers scheduling transactions for small buisnesses in the eastern US for whom I've done contracting work.
- Multiple scheduling backends -- Acuity REST API, Cal.com, or bring-your-own PostgreSQL (HomegrownAdapter)
- Svelte 5 components -- ServicePicker, DateTimePicker, ClientForm, CheckoutDrawer, and more
- Payment adapters -- Stripe, Venmo/PayPal SDK, cash, Zelle, check
- Availability engine -- Pure-function slot generation, DST-safe via
Intl.DateTimeFormat - Reconciliation -- Alt-payment matching and webhook handling
- Test infrastructure -- Cassette-based API recording/playback, MSW mocking, property-based tests
- Functional core -- Effect-powered scheduling flows and typed error handling
pnpm add @tummycrypt/scheduling-kitPeer dependencies (install those you need):
# Required
pnpm add svelte
# Optional -- for UI components
pnpm add @skeletonlabs/skeleton @skeletonlabs/skeleton-svelte
# Optional -- for E2E tests
pnpm add -D playwright-corepnpm check:release-metadataThat check keeps package.json, MODULE.bazel, and BUILD.bazel aligned so
the published npm package and Bazel metadata do not silently drift apart.
Current reality:
- the functional release line is
Jesssullivan/scheduling-kit tinyland-inc/scheduling-kitis still a convergence target while remote truth is being cleaned up
Until that convergence work is complete, treat Jesssullivan/main as the
release authority for package publication and metadata changes. Do not assume
both main branches are equivalent.
Longer term, the intended publish shape is:
- release metadata declared once
- Bazel validates/builds the publishable artifact
- GitHub Actions publishes that artifact to npm
- downstream apps consume the published package only
import { Effect } from 'effect';
import {
createSchedulingKit,
createHomegrownAdapter,
createStripeAdapter,
createVenmoAdapter,
} from '@tummycrypt/scheduling-kit';
// Create a scheduling adapter
const scheduler = createHomegrownAdapter({
db: drizzleInstance,
timezone: 'America/New_York',
});
// Create payment adapters
const stripe = createStripeAdapter({
type: 'stripe',
secretKey: process.env.STRIPE_SECRET_KEY!,
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY!,
});
const venmo = createVenmoAdapter({
type: 'venmo',
clientId: process.env.PAYPAL_CLIENT_ID!,
clientSecret: process.env.PAYPAL_CLIENT_SECRET!,
environment: 'sandbox',
});
// Compose into a scheduling kit
const kit = createSchedulingKit(scheduler, [stripe, venmo]);
// Complete a booking
const result = await Effect.runPromise(
kit.completeBooking(request, 'stripe')
);Direct PostgreSQL adapter using Drizzle ORM. Replaces third-party scheduling APIs entirely.
import { createHomegrownAdapter } from '@tummycrypt/scheduling-kit/adapters';
const adapter = createHomegrownAdapter({
db: drizzleInstance,
timezone: 'America/New_York',
});
// 16 methods: getServices, getAvailability, getSlots, book, cancel, reschedule, ...API-based adapter for Acuity Scheduling (requires Powerhouse plan).
For browser automation and no-API migration flows, use @tummycrypt/scheduling-bridge.
import { createAcuityAdapter } from '@tummycrypt/scheduling-kit/adapters';
const config = {
type: 'acuity' as const,
userId: process.env.ACUITY_USER_ID!,
apiKey: process.env['ACUITY_API_KEY']!, // from Acuity Integrations page
};
const adapter = createAcuityAdapter(config);Stub adapter for future Cal.com integration.
import { createCalComAdapter } from '@tummycrypt/scheduling-kit/adapters';
const adapter = createCalComAdapter({
type: 'calcom',
apiKey: process.env['CALCOM_API_KEY']!,
baseUrl: 'https://api.cal.com/v1',
});Pure functions for slot generation. DST-safe, timezone-aware, fully tested.
import {
getAvailableSlots,
isSlotAvailable,
getDatesWithAvailability,
getEffectiveHours,
} from '@tummycrypt/scheduling-kit/adapters';
const slots = getAvailableSlots({
date: '2026-03-22',
timezone: 'America/New_York',
hours: [{ dayOfWeek: 6, startTime: '11:00', endTime: '16:00' }],
overrides: [],
occupied: [],
slotDuration: 60,
bufferMinutes: 15,
});Svelte 5 components using runes syntax. Optional Skeleton 4 integration for styling.
| Component | Description |
|---|---|
ServicePicker |
Service/appointment type selector |
DateTimePicker |
Calendar date + time slot picker |
ClientForm |
Client info form with Zod validation |
PaymentSelector |
Payment method chooser |
ProviderPicker |
Practitioner/provider selector |
BookingConfirmation |
Post-booking confirmation display |
CheckoutDrawer |
Full checkout flow in a slide-out drawer |
HybridCheckoutDrawer |
Checkout with Acuity iframe handoff |
VenmoButton |
Venmo/PayPal payment button |
VenmoCheckout |
Full Venmo checkout flow |
StripeCheckout |
Stripe Elements checkout |
AcuityEmbedHandoff |
Acuity iframe handoff with postMessage integration |
<script lang="ts">
import { ServicePicker, DateTimePicker, ClientForm } from '@tummycrypt/scheduling-kit/components';
</script>
<ServicePicker services={data.services} onselect={handleSelect} />
<DateTimePicker slots={availableSlots} onselect={handleTimeSelect} />
<ClientForm onsubmit={handleSubmit} />import {
createStripeAdapter,
createVenmoAdapter,
createCashAdapter,
createZelleAdapter,
createCheckAdapter,
createVenmoDirectAdapter,
} from '@tummycrypt/scheduling-kit/payments';| Adapter | Type | Description |
|---|---|---|
createStripeAdapter |
stripe |
Stripe Connect with Payment Intents |
createVenmoAdapter |
venmo |
PayPal SDK with Venmo button |
createCashAdapter |
cash |
Cash/in-person manual payment |
createZelleAdapter |
zelle |
Zelle manual payment |
createCheckAdapter |
check |
Check manual payment |
createVenmoDirectAdapter |
venmo-direct |
Venmo deep link (no SDK) |
Match alt-payment transactions (Venmo, Zelle, cash) to bookings.
import { createReconciliationMatcher } from '@tummycrypt/scheduling-kit/reconciliation';Svelte 5 runes-based checkout state management.
import { checkoutStore } from '@tummycrypt/scheduling-kit/stores';pnpm test:unit # Run all unit tests
pnpm test:coverage # With coverage reportpnpm test:integration # Mocked backend integration testspnpm test:component # jsdom-based component testspnpm test:e2e # Playwright browser tests (starts dev server)# Copy .env.test.local.example to .env.test.local and fill in credentials
RUN_LIVE_TESTS=true pnpm test:liveThe @tummycrypt/scheduling-kit/testing export provides cassette-based API recording and playback for deterministic integration tests.
import { CassetteRecorder, CassettePlayer } from '@tummycrypt/scheduling-kit/testing';pnpm install
pnpm dev # Start dev server
pnpm build # Build package
pnpm check # TypeScript check
pnpm lint # ESLint
pnpm test:all # Run all test suitesMIT -- see LICENSE for details.