Services
All business logic lives in src/lib/server/services/. Each service is a singleton class.
Services import db from the database seam (src/lib/server/db/index.ts) rather than opening a connection themselves. The seam resolves to a local better-sqlite3 database on the Node.js target and to the D1 binding on Cloudflare, so the same service code runs on both targets. See Database.
Product Service
Manages products and their variants.
import { productService } from "$lib/server/services/products";
// Get by ID or slug — returns resolved flat fields
const product = await productService.getById(123);
const product = await productService.getBySlug("blue-shirt");
product.name; // "Blue Shirt" (default language, directly from products table)
// List with pagination
const { items, total } = await productService.list({
limit: 20
});
// Search
const results = await productService.list({ search: "shirt" });
// CRUD
await productService.create({ slug: "new-product" /* ... */ });
await productService.update(123, {
/* ... */
});
await productService.delete(123);Variant operations (stock, pricing, SKU management) are also handled through productService.
Order Service
Manages the full order lifecycle, including cart operations.
import { orderService } from "$lib/server/services/orders";
// The cart is a cookie — an order row is created only when checkout starts,
// snapshotting the cookie lines into order lines with 15-min stock reservations.
const { order, checkoutToken } = await orderService.startCheckout(cartLines, {
customerId,
checkoutToken
});
// State transitions (all go through transitionState)
await orderService.transitionState(orderId, "payment_pending");
await orderService.transitionState(orderId, "paid");
await orderService.transitionState(orderId, "shipped");
await orderService.transitionState(orderId, "delivered");
await orderService.transitionState(orderId, "cancelled");
// Checkout
await orderService.setShippingAddress(orderId, address);
await orderService.setCustomerEmail(orderId, email);
await orderService.updateTotals(orderId);
// Query
const order = await orderService.getByCode("ORD-EJXEDYIX");Customer Service
import { customerService } from "$lib/server/services/customers";
// Create from auth user
const customer = await customerService.getOrCreateFromAuth(authUser);
// Query
const customer = await customerService.getByEmail("user@example.com");Translation Service
Manages translations for all 7 translatable entities. Uses Drizzle’s onConflictDoUpdate for upserts.
import { translationService } from "$lib/server/services/translations";
// Get translations for an entity
const rows = await translationService.getProductTranslations(productId);
// Upsert a translation (insert or update)
await translationService.upsertProductTranslation(productId, "fi", {
name: "Sininen paita",
slug: "sininen-paita",
description: "<p>Hieno paita</p>"
});
// Bulk methods for list pages
const allCategoryTranslations = await translationService.getAllCategoryTranslations();
const facetValueTranslations = await translationService.getAllFacetValueTranslations(facetId);See Localization for the full API and admin UI integration.
Other Services
See src/lib/server/services/ for all available services including:
categoryService— Category tree, breadcrumbs, and product assignmentfacetService— Facets and facet values (filterable attributes)collectionService— Product collections (rule-based)taxService— Tax rate lookups and calculationspaymentService— Payment provider orchestrationshippingService— Shipping provider orchestrationpromotionService— Discount and promotion rulesreservationService— Stock reservationscustomerGroupService— Customer group managementcontentPageService— CMS content pagestranslationService— Multi-language translations for all entitiesassetService— Image/file managementrelatedProductService— Related-product linksdigitalDeliveryService— Delivery of digital products after paymentreviewService— Product reviews
Product search and reindexing live in product-search.ts, exposed as functions
(listProducts, quickSearchProducts, reindexProduct, reindexAll, …) rather
than a class.
The cart is a cookie, not a service singleton, and the wishlist is likewise just a cookie of product ids — there is no
wishlistService. See Cart and Wishlists.
Promotion math
All promotion discount math is centralized in
OrderService.computePromotionDiscount — coupon codes, automatic promotions, and
totals recalculation all route through it, so the applied discount is computed the
same way everywhere. See Promotions.
Emitting events
Services and routes record out-of-band work by calling emitEvent(type, payload)
(a durable outbox insert) rather than doing it inline — e.g. the checkout
completion emits order.paid. A scheduled drain processes these with retry. See
Integrations.
MCP server
Hoikka ships a local MCP server (pnpm mcp, defined in scripts/mcp.ts and
registered in .mcp.json) that exposes schema, catalog, and order tools to AI
assistants over stdio. Point an MCP-capable client at it to let an agent inspect
the schema and query products and orders directly.