Skip to content

Module System

Honeycomb organizes client-side code into feature modules under src/lib/modules/. Each module maps to a single feature domain and follows a standard folder structure. Only client-importable code lives in modules — server-side logic stays in src/routes/.

Each module lives at src/lib/modules/<module-name>/ and contains only the subfolders it actually needs. Do not create empty placeholders.

src/lib/modules/<module-name>/
├── index.ts # Public barrel -- re-exports from all subfolders
├── schemas/ # Zod validation schemas
│ ├── index.ts # Barrel file for schemas
│ ├── create-foo.ts
│ └── update-foo.ts
├── types/ # TypeScript types (inferred from schemas or custom)
│ └── index.ts
├── components/ # Svelte components scoped to this feature
│ ├── index.ts # Barrel file for components
│ ├── foo-form.svelte
│ └── foo-card.svelte
├── stores/ # Svelte stores / runes scoped to this module
│ ├── index.ts
│ └── foo.svelte.ts
├── utils/ # Pure helper functions specific to this feature
│ ├── index.ts
│ └── format-foo.ts
└── constants.ts # Module-scoped constants (optional)

Every module and every subfolder with more than one file must have an index.ts that re-exports its public API. Consumers always import from the barrel, never from internal files.

// Good
import { LoginForm, signInSchema } from "$lib/modules/auth";
// Avoid
import LoginForm from "$lib/modules/auth/components/login-form.svelte";

Each Zod validation schema gets its own file. These schemas are used for form validation, API request parsing, and type inference.

schemas/sign-in.ts
import { z } from "zod";
export const signInSchema = z.object({
email: z.email("Please enter a valid email address"),
password: z.string().check(z.minLength(1, "Password is required")),
});

TypeScript types inferred from schemas or defined manually. Keeps type definitions separate from runtime validation logic.

types/index.ts
import type { z } from "zod";
import type { signInSchema } from "../schemas/index.js";
export type SignInSchema = z.infer<typeof signInSchema>;

Svelte components that belong to this feature (forms, cards, dialogs). Shared, generic UI primitives belong in $lib/components/ui/ instead.

Reactive state using Svelte 5 runes (.svelte.ts files) or classic stores. Only create this folder when the module needs client-side state beyond what components manage internally.

Pure functions specific to this module (formatters, validators, mappers). General-purpose utilities go in $lib/utils/.

Static values such as enum-like objects and config maps. Optional — only add when the module actually needs shared constants.

Honeycomb has 15 feature modules:

ModuleDomainDescription
authAuthenticationSign-in, sign-up, password reset schemas and forms
exploreDiscoveryExplore/discover feed, search, trending content
honeycombCore/appsHoneycomb app system and extension UI components
layoutApp shellSidebar, header, navigation, and layout components
marketplaceCommerceProduct listings, store pages, job marketplace
messagingChatConversations, message threads, group chats, invites
notificationsAlertsNotification list, cards, realtime notification handling
onboardingSetupPost-signup onboarding wizard steps and schemas
postContentPost creation, feed cards, comments, reactions
profileIdentityProfile pages, editing, follow system, social links
sharedCross-cuttingComponents and utilities used across multiple modules
sitebuilderWebsite builderUser website/page builder feature
socialSocial graphFollow suggestions, social connections, social features
storiesEphemeral contentStory creation, viewer, highlights
walletPaymentsWallet balance, transaction history, transfers, tips

To add a new feature module:

  1. Create the directory at src/lib/modules/<module-name>/.

  2. Add an index.ts barrel that re-exports everything consumers need:

    src/lib/modules/billing/index.ts
    export * from "./schemas/index.js";
    export * from "./types/index.js";
    export * from "./components/index.js";
  3. Add subfolders as needed. Start with schemas/ and components/ — add stores/, utils/, and constants.ts only when you have something to put in them.

  4. Create barrel files for each subfolder that contains more than one file.

  5. Write server-side logic in the corresponding route files under src/routes/, not in the module.

  6. Import from the barrel in routes and other modules:

    import { BillingForm, createSubscriptionSchema } from "$lib/modules/billing";
src/lib/modules/billing/
├── index.ts
├── schemas/
│ ├── index.ts
│ └── create-subscription.ts
├── types/
│ └── index.ts
└── components/
├── index.ts
├── billing-form.svelte
└── plan-card.svelte
  • One module = one feature domain. If a module grows too large, split it into sub-modules rather than nesting deeply.
  • Keep modules focused. A billing module should not contain notification components.
  • Shared code goes in $lib/. If two modules need the same utility, move it to $lib/utils/. If they share a component, move it to $lib/components/.
  • Never import internal files. Always go through the barrel. This makes refactoring safe and keeps the public API explicit.