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-metadata
pnpm check:package
npx --yes @bazel/bazelisk build //:pkg
npm pack --dry-run ./bazel-bin/pkgThose checks keep package.json, MODULE.bazel, and BUILD.bazel aligned,
then validate the Bazel-built package artifact before anything gets near a
registry.
Current reality:
- the functional release line is
Jesssullivan/scheduling-kit tinyland-inc/scheduling-kitis now a downstream mirror and validation surface, not a second publish authority
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
Canonical GitHub Actions package CI and publish now run on the repo-owned
GloriousFlywheel self-hosted runner lane with the plain repo label
["scheduling-kit"].
Treat that label as the active truth surface for package authority. Do not describe broader repo-owned self-hosted label arrays as live contract unless they are reproven from current runs.
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')
);This package owns reusable scheduling contracts, payment adapters, checkout UI, and Acuity REST plus iframe handoff primitives.
It does not own:
- browser automation
- remote Acuity scraping
- Modal deployment/runtime control
- site-specific booking orchestration
If your Acuity flow needs browser automation or remote bridge-backed booking
semantics, that ownership belongs to @tummycrypt/scheduling-bridge plus the
adopter app that wires the handoff.
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 UI for adopter-provided Acuity handoff |
VenmoButton |
Venmo/PayPal payment button |
VenmoCheckout |
Full Venmo checkout flow |
StripeCheckout |
Stripe Elements checkout |
AcuityEmbedHandoff |
Prefilled Acuity iframe handoff with postMessage |
<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.