@cimplify/sdk 0.38.2 → 0.38.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/dispatcher.mjs +1 -1
  2. package/dist/mock/cli.mjs +956 -436
  3. package/dist/mock/library.d.mts +2 -2
  4. package/dist/mock/library.d.ts +2 -2
  5. package/dist/mock/library.js +956 -436
  6. package/dist/mock/library.mjs +956 -436
  7. package/dist/mock/msw.d.mts +1 -1
  8. package/dist/mock/msw.d.ts +1 -1
  9. package/dist/mock/msw.js +956 -436
  10. package/dist/mock/msw.mjs +956 -436
  11. package/dist/{server-B8TeE2OD.d.mts → server-BNy6Ri-4.d.mts} +18 -0
  12. package/dist/{server-B8TeE2OD.d.ts → server-BNy6Ri-4.d.ts} +18 -0
  13. package/dist/styles.css +1 -1
  14. package/package.json +1 -1
  15. package/templates/storefront/.env.example +11 -2
  16. package/templates/storefront/README.md +35 -18
  17. package/templates/storefront/app/cart/page.tsx +9 -0
  18. package/templates/storefront/app/categories/[slug]/page.tsx +80 -0
  19. package/templates/storefront/app/checkout/page.tsx +17 -0
  20. package/templates/storefront/app/collections/[slug]/page.tsx +80 -0
  21. package/templates/storefront/app/globals.css +47 -0
  22. package/templates/storefront/app/layout.tsx +45 -0
  23. package/templates/storefront/app/orders/[id]/page.tsx +21 -0
  24. package/templates/storefront/app/page.tsx +65 -0
  25. package/templates/storefront/app/shop/page.tsx +46 -0
  26. package/templates/storefront/components/category-grid.tsx +28 -0
  27. package/templates/storefront/components/collection-strip.tsx +45 -0
  28. package/templates/storefront/components/footer.tsx +19 -0
  29. package/templates/storefront/components/header.tsx +41 -0
  30. package/templates/storefront/components/hero.tsx +25 -0
  31. package/templates/storefront/components/product-modal.tsx +105 -0
  32. package/templates/storefront/components/providers.tsx +31 -0
  33. package/templates/storefront/components/store-product-card.tsx +48 -0
  34. package/templates/storefront/lib/cart.ts +27 -0
  35. package/templates/storefront/next.config.ts +36 -0
  36. package/templates/storefront/package.json +13 -14
  37. package/templates/storefront/postcss.config.mjs +7 -0
  38. package/templates/storefront/tsconfig.json +15 -11
  39. package/templates/storefront/index.html +0 -12
  40. package/templates/storefront/src/App.tsx +0 -118
  41. package/templates/storefront/src/main.tsx +0 -28
  42. package/templates/storefront/src/styles.css +0 -65
  43. package/templates/storefront/vite.config.ts +0 -8
package/dist/mock/msw.mjs CHANGED
@@ -2982,6 +2982,178 @@ function buildElements(deps) {
2982
2982
  return app;
2983
2983
  }
2984
2984
 
2985
+ // src/mock/routes/images.ts
2986
+ var SLUG_KEYWORDS = {
2987
+ // ─── Akua's Bakery (default seed) ───
2988
+ sourdough: "sourdough,bread",
2989
+ wheat: "whole-wheat,bread",
2990
+ baguette: "baguette",
2991
+ focaccia: "focaccia,bread",
2992
+ croissant: "croissant",
2993
+ pac: "pain-au-chocolat,croissant",
2994
+ danish: "danish,pastry",
2995
+ cinnamon: "cinnamon-roll",
2996
+ "choc-cake": "chocolate-cake,slice",
2997
+ "red-velvet": "red-velvet-cake",
2998
+ carrot: "carrot-cake",
2999
+ cheesecake: "cheesecake",
3000
+ coffee: "coffee,cup",
3001
+ tea: "tea,cup",
3002
+ sobolo: "hibiscus-drink",
3003
+ oj: "orange-juice,glass",
3004
+ banana: "banana-bread",
3005
+ brownie: "brownie",
3006
+ macaron: "macaron,box",
3007
+ birthday: "birthday-cake",
3008
+ "breakfast-box": "breakfast",
3009
+ "pastry-box": "pastry,assortment",
3010
+ // ─── Mama's Kitchen (restaurant seed) ───
3011
+ kelewele: "fried-plantain,spicy",
3012
+ "spring-rolls": "spring-rolls",
3013
+ "soup-of-the-day": "soup,bowl",
3014
+ "jollof-rice": "jollof-rice",
3015
+ "banku-tilapia": "tilapia,grilled-fish",
3016
+ "fufu-light-soup": "fufu,soup",
3017
+ waakye: "rice,beans,african-food",
3018
+ "grilled-chicken": "grilled-chicken",
3019
+ burger: "burger",
3020
+ "yam-fries": "yam-fries,chips",
3021
+ "fried-plantain": "fried-plantain",
3022
+ salad: "garden-salad",
3023
+ "ice-cream": "ice-cream,scoop",
3024
+ "cake-slice": "chocolate-cake,slice",
3025
+ "fruit-bowl": "fruit-bowl,tropical",
3026
+ "soft-drink": "cola,glass",
3027
+ "fresh-juice": "fresh-juice",
3028
+ water: "water-bottle",
3029
+ feast: "family-meal,buffet",
3030
+ byo: "buffet,plate",
3031
+ // ─── Currents Electronics ───
3032
+ "android-flagship": "smartphone,android",
3033
+ "android-mid": "smartphone",
3034
+ "phone-budget": "phone",
3035
+ "laptop-pro": "laptop,macbook",
3036
+ "laptop-air": "laptop,thin",
3037
+ "laptop-budget": "laptop",
3038
+ "headphones-anc": "headphones",
3039
+ "earbuds-pro": "earbuds,wireless",
3040
+ "speaker-bt": "speaker,bluetooth",
3041
+ "phone-case": "phone-case",
3042
+ "screen-protector": "screen,protector",
3043
+ "fast-charger": "charger,usb",
3044
+ "laptop-sleeve": "laptop-sleeve,case",
3045
+ "wireless-mouse": "wireless-mouse",
3046
+ "mech-keyboard": "mechanical-keyboard",
3047
+ "gaming-controller": "game-controller",
3048
+ "gaming-headset": "gaming-headset",
3049
+ "console-handheld": "handheld-console,gaming",
3050
+ wfh: "desk-setup,laptop",
3051
+ "game-pack": "gaming,console",
3052
+ // ─── Reesa apparel ───
3053
+ "ankara-shirt": "ankara,african-fashion",
3054
+ "agbada-set": "agbada,nigerian-fashion",
3055
+ kaftan: "kaftan,african-dress",
3056
+ "leather-tote": "leather-tote,bag",
3057
+ sandals: "sandals,leather",
3058
+ "beaded-bracelet": "beaded-bracelet",
3059
+ owambe: "african-fashion",
3060
+ // ─── FreshMart ───
3061
+ "tomatoes-1kg": "tomatoes",
3062
+ "onions-1kg": "onions",
3063
+ "ginger-200g": "ginger,root",
3064
+ "bananas-bunch": "bananas",
3065
+ pineapple: "pineapple",
3066
+ "spinach-bunch": "spinach,leaves",
3067
+ "milk-1l": "milk,carton",
3068
+ "yogurt-500g": "yogurt",
3069
+ "eggs-12": "eggs,carton",
3070
+ "cheese-200g": "cheese",
3071
+ "chicken-1kg": "raw-chicken",
3072
+ "beef-mince-500g": "beef,mince",
3073
+ "tilapia-each": "fish,tilapia",
3074
+ "rice-5kg": "rice,bag",
3075
+ "garri-2kg": "rice,grain",
3076
+ "palm-oil-1l": "oil,bottle",
3077
+ "vegetable-oil-1l": "oil,bottle",
3078
+ "salt-1kg": "salt,seasoning",
3079
+ "sugar-1kg": "sugar,white",
3080
+ "tomato-paste": "tomato-paste,can",
3081
+ "soap-bar": "soap,bar",
3082
+ "detergent-1kg": "detergent,laundry",
3083
+ "tissue-pack": "tissue,paper",
3084
+ "trash-bags": "trash-bag",
3085
+ essentials: "groceries,box",
3086
+ // ─── Serene Spa ───
3087
+ "swedish-massage-60": "massage,relaxation",
3088
+ "swedish-massage-90": "massage,relaxation",
3089
+ "deep-tissue-60": "massage,deep-tissue",
3090
+ "couples-massage": "couples-massage",
3091
+ "hot-stone-90": "hot-stone,massage",
3092
+ "facial-classic": "facial,spa",
3093
+ "facial-anti-aging": "facial,skincare",
3094
+ "facial-acne": "facial,skincare",
3095
+ manicure: "manicure,nails",
3096
+ pedicure: "pedicure,feet",
3097
+ "gel-manicure": "manicure,gel-nails",
3098
+ haircut: "haircut,salon",
3099
+ "hair-treatment": "hair-treatment,salon",
3100
+ color: "hair-color,salon",
3101
+ "yoga-session": "yoga,studio",
3102
+ meditation: "meditation,zen",
3103
+ "spa-day": "spa,wellness",
3104
+ "couples-retreat": "spa,couple",
3105
+ "custom-experience": "spa,wellness"
3106
+ };
3107
+ var NO_CACHE = {
3108
+ "Cache-Control": "no-cache, no-store, must-revalidate"
3109
+ };
3110
+ function lockFromSlug(slug) {
3111
+ let hash = 0;
3112
+ for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
3113
+ return hash % 9999 + 1;
3114
+ }
3115
+ function svgFallback(slug) {
3116
+ const label = slug.replace(/[-_]+/g, " ").trim() || "product";
3117
+ const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
3118
+ let hash = 0;
3119
+ for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
3120
+ const hue = hash % 360;
3121
+ const safe = display.replace(/[<>&]/g, "");
3122
+ const svg = `<?xml version="1.0" encoding="UTF-8"?>
3123
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
3124
+ <defs>
3125
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
3126
+ <stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
3127
+ <stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
3128
+ </linearGradient>
3129
+ </defs>
3130
+ <rect width="400" height="400" fill="url(#g)"/>
3131
+ <text x="200" y="210" font-family="-apple-system, BlinkMacSystemFont, Inter, sans-serif" font-size="22" font-weight="600" fill="white" text-anchor="middle" dominant-baseline="middle" style="text-transform: capitalize">${safe}</text>
3132
+ </svg>`;
3133
+ return new Response(svg, {
3134
+ headers: {
3135
+ "Content-Type": "image/svg+xml; charset=utf-8",
3136
+ ...NO_CACHE,
3137
+ "Access-Control-Allow-Origin": "*"
3138
+ }
3139
+ });
3140
+ }
3141
+ function handleImageRequest(c) {
3142
+ const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
3143
+ const slug = path.split("/").pop() ?? "";
3144
+ for (const [k, v] of Object.entries(NO_CACHE)) c.header(k, v);
3145
+ if (!slug) return svgFallback("");
3146
+ const keyword = SLUG_KEYWORDS[slug] ?? slug.replace(/[-_]+/g, ",");
3147
+ const lock = lockFromSlug(slug);
3148
+ return c.redirect(
3149
+ `https://loremflickr.com/600/600/${encodeURIComponent(keyword)}?lock=${lock}`,
3150
+ 302
3151
+ );
3152
+ }
3153
+ function mountImageRoutes(app) {
3154
+ app.get("/img/*", handleImageRequest);
3155
+ }
3156
+
2985
3157
  // src/mock/core/clock.ts
2986
3158
  function createClock(opts) {
2987
3159
  let frozenAt = opts?.frozenAt ? opts.frozenAt.getTime() : null;
@@ -3319,51 +3491,249 @@ function percent(a, pct) {
3319
3491
  return toMajor(Math.round(toMinor(a) * pct / 100));
3320
3492
  }
3321
3493
 
3322
- // src/mock/domain/catalogue.ts
3323
- function createCatalogueService(opts) {
3324
- const { registry, clock, ids } = opts;
3325
- const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
3326
- const defaultPriceInfo = (price, currency) => ({
3494
+ // src/mock/domain/catalogue/price-info.ts
3495
+ function defaultPriceInfo(price, currency) {
3496
+ return {
3327
3497
  base_price: price,
3328
3498
  final_price: price,
3499
+ pre_tax_price: price,
3329
3500
  markup_percentage: "0.00",
3330
3501
  markup_amount: "0.00",
3331
3502
  markup_discount_percentage: "0.00",
3332
3503
  markup_discount_amount: "0.00",
3333
- currency
3334
- });
3335
- const toApiProduct = (p) => ({
3504
+ currency,
3505
+ tax_info: {
3506
+ tax_rate: "0",
3507
+ tax_amount: "0.00",
3508
+ is_inclusive: true,
3509
+ pre_tax_price: price,
3510
+ components: []
3511
+ }
3512
+ };
3513
+ }
3514
+
3515
+ // src/mock/domain/catalogue/product-shape.ts
3516
+ function toCanonicalProductSummary(p) {
3517
+ return {
3518
+ id: p.id,
3519
+ business_id: p.business_id,
3520
+ category_id: p.category_ids[0],
3521
+ name: p.name,
3522
+ slug: p.slug,
3523
+ description: p.description,
3524
+ image_url: p.image,
3525
+ images: p.images,
3526
+ default_price: p.base_price,
3527
+ type: p.product_type,
3528
+ inventory_type: "none",
3529
+ variant_strategy: "fetch_all",
3530
+ is_active: p.is_available,
3531
+ inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
3532
+ tags: p.tags ?? [],
3533
+ created_at: p.created_at,
3534
+ updated_at: p.updated_at
3535
+ };
3536
+ }
3537
+ function deriveRenderHint(p) {
3538
+ if (p.product_type === "service") return "general";
3539
+ const foodish = (p.tags ?? []).some(
3540
+ (t) => /food|drink|cake|bread|pastry|dessert|coffee|tea|juice|spice/i.test(t)
3541
+ );
3542
+ return foodish ? "food" : "general";
3543
+ }
3544
+ function toApiProduct(p, ctx) {
3545
+ const { registry, priceInfo, groups, components, composite, bundle } = ctx;
3546
+ return {
3547
+ // ─── Canonical Product fields (SDK type: Product / ProductWithDetails) ───
3336
3548
  id: p.id,
3337
3549
  business_id: p.business_id,
3550
+ category_id: p.category_ids[0],
3338
3551
  name: p.name,
3339
3552
  slug: p.slug,
3340
3553
  description: p.description ?? "",
3341
- product_type: p.product_type,
3342
- base_price: p.base_price,
3554
+ image_url: p.image,
3555
+ images: p.images,
3343
3556
  default_price: p.base_price,
3344
- default_price_info: defaultPriceInfo(p.base_price, p.currency),
3345
- price_info: defaultPriceInfo(p.base_price, p.currency),
3557
+ type: p.product_type,
3558
+ render_hint: deriveRenderHint(p),
3559
+ inventory_type: "none",
3560
+ variant_strategy: p.variant_ids.length > 0 ? "use_axes" : "fetch_all",
3561
+ is_active: p.is_available,
3562
+ inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
3563
+ tags: p.tags ?? [],
3564
+ calories: p.calories,
3565
+ allergies: p.allergies ?? [],
3566
+ created_at: p.created_at,
3567
+ updated_at: p.updated_at,
3568
+ metadata: {
3569
+ is_signature: p.is_signature ?? false,
3570
+ is_new: p.is_new ?? false,
3571
+ ingredients: p.ingredients ?? [],
3572
+ pairings: p.pairings ?? [],
3573
+ ...p.metadata ?? {}
3574
+ },
3575
+ // ─── Pricing breakdown (Price / ProductCustomizer) ───
3576
+ base_price: p.base_price,
3577
+ default_price_info: priceInfo,
3578
+ price_info: priceInfo,
3346
3579
  currency: p.currency,
3580
+ // ─── Compatibility aliases (older callers) ───
3347
3581
  image: p.image,
3348
- image_url: p.image,
3349
- images: p.images,
3582
+ product_type: p.product_type,
3350
3583
  is_available: p.is_available,
3351
- category_id: p.category_ids[0],
3352
3584
  category_ids: p.category_ids,
3353
3585
  collection_ids: p.collection_ids,
3354
3586
  add_on_ids: p.add_on_ids,
3355
- metadata: p.metadata ?? {},
3356
- created_at: p.created_at,
3357
- updated_at: p.updated_at,
3587
+ variant_ids: p.variant_ids,
3358
3588
  custom_attributes: [],
3359
3589
  properties: [],
3360
3590
  has_variants: p.variant_ids.length > 0,
3361
- type: p.product_type,
3362
- render_hint: "general"
3363
- });
3591
+ // ─── Embedded relations (ProductWithDetails) ───
3592
+ category: registry.categories.get(p.category_ids[0]),
3593
+ add_ons: p.add_on_ids.map((aid) => registry.addOns.get(aid)).filter(Boolean),
3594
+ variants: registry.variants.list((v) => v.product_id === p.id),
3595
+ variant_axes: registry.variantAxes.list((a) => a.product_id === p.id),
3596
+ // ─── Composite-specific (CompositeProductLayout reads product.groups) ───
3597
+ groups,
3598
+ composite_id: composite?.id,
3599
+ pricing_mode: composite?.pricing_mode,
3600
+ min_order_quantity: composite?.min_order_quantity,
3601
+ max_order_quantity: composite?.max_order_quantity,
3602
+ // ─── Bundle-specific (BundleProductLayout reads product.components) ───
3603
+ bundle_id: bundle?.id,
3604
+ bundle_pricing_type: bundle?.pricing_type,
3605
+ bundle_price: bundle?.bundle_price,
3606
+ bundle_discount_value: bundle?.discount_value,
3607
+ components
3608
+ };
3609
+ }
3610
+
3611
+ // src/mock/domain/catalogue/composite-shape.ts
3612
+ function expandCompositeGroups(registry, productId, isoNow) {
3613
+ const composite = registry.composites.list((c) => c.product_id === productId)[0];
3614
+ if (!composite) return void 0;
3615
+ return composite.groups.map((g, gIdx) => buildGroup(registry, composite, g, gIdx, isoNow));
3616
+ }
3617
+ function buildGroup(registry, composite, group, groupIdx, isoNow) {
3618
+ return {
3619
+ // ─── Canonical ComponentGroupWithComponents ───
3620
+ id: group.id,
3621
+ composite_id: composite.id,
3622
+ name: group.name,
3623
+ description: void 0,
3624
+ display_order: groupIdx,
3625
+ min_selections: group.min,
3626
+ max_selections: group.max,
3627
+ allow_quantity: true,
3628
+ max_quantity_per_component: group.max,
3629
+ pricing_behavior: "additive",
3630
+ created_at: isoNow,
3631
+ updated_at: isoNow,
3632
+ // ─── Aliases for older selectors ───
3633
+ min: group.min,
3634
+ max: group.max,
3635
+ components: group.component_ids.map((cid, idx) => ({ p: registry.products.get(cid), idx })).filter((x) => Boolean(x.p)).map(({ p, idx }) => buildComponent(composite, group, p, idx))
3636
+ };
3637
+ }
3638
+ function buildComponent(composite, group, p, idx) {
3639
+ return {
3640
+ // ─── Canonical CompositeComponent ───
3641
+ id: `comp_${composite.product_id}_${p.id}`,
3642
+ composite_id: composite.id,
3643
+ group_id: group.id,
3644
+ source_type: "product",
3645
+ source_product_id: p.id,
3646
+ product_id: p.id,
3647
+ display_name: p.name,
3648
+ display_description: p.description,
3649
+ display_image_url: p.image,
3650
+ price: p.base_price,
3651
+ calories: p.calories,
3652
+ allergens: p.allergies ?? [],
3653
+ display_order: idx,
3654
+ is_popular: idx === 0,
3655
+ is_premium: false,
3656
+ is_available: p.is_available,
3657
+ is_archived: false,
3658
+ created_at: p.created_at,
3659
+ updated_at: p.updated_at,
3660
+ // ─── Compatibility aliases ───
3661
+ name: p.name,
3662
+ description: p.description,
3663
+ image_url: p.image,
3664
+ unit_price: p.base_price,
3665
+ default_price: p.base_price,
3666
+ base_price: p.base_price,
3667
+ currency: p.currency,
3668
+ product: toCanonicalProductSummary(p)
3669
+ };
3670
+ }
3671
+
3672
+ // src/mock/domain/catalogue/bundle-shape.ts
3673
+ function expandBundleComponents(registry, productId) {
3674
+ const bundle = registry.bundles.list((b) => b.product_id === productId)[0];
3675
+ if (!bundle) return void 0;
3676
+ return bundle.product_ids.map((cid) => registry.products.get(cid)).filter((p) => Boolean(p)).map((p) => buildBundleComponent(bundle.id, productId, p));
3677
+ }
3678
+ function buildBundleComponent(bundleId, productId, p) {
3679
+ return {
3680
+ id: `bp_${productId}_${p.id}`,
3681
+ bundle_id: bundleId,
3682
+ product_id: p.id,
3683
+ // ─── Fields BundleProductLayout reads ───
3684
+ product_name: p.name,
3685
+ product_image_url: p.image,
3686
+ product_description: p.description,
3687
+ product_slug: p.slug,
3688
+ effective_price: p.base_price,
3689
+ unit_price: p.base_price,
3690
+ default_price: p.base_price,
3691
+ quantity: 1,
3692
+ allow_variant_choice: false,
3693
+ available_variants: [],
3694
+ selected_variant_id: null,
3695
+ // ─── Compatibility aliases ───
3696
+ display_name: p.name,
3697
+ display_description: p.description,
3698
+ name: p.name,
3699
+ description: p.description,
3700
+ image_url: p.image,
3701
+ currency: p.currency,
3702
+ is_available: p.is_available,
3703
+ product: toCanonicalProductSummary(p)
3704
+ };
3705
+ }
3706
+
3707
+ // src/mock/domain/catalogue/index.ts
3708
+ function createCatalogueService(opts) {
3709
+ const { registry, clock, ids } = opts;
3710
+ const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
3364
3711
  const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
3365
3712
  const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
3366
3713
  const addOnLinksForProduct = (productId) => registry.productAddOns.list((l) => l.product_id === productId);
3714
+ const apiProduct = (p) => {
3715
+ const composite = p.product_type === "composite" ? registry.composites.list((c) => c.product_id === p.id)[0] : void 0;
3716
+ const bundle = p.product_type === "bundle" ? registry.bundles.list((b) => b.product_id === p.id)[0] : void 0;
3717
+ return toApiProduct(p, {
3718
+ registry,
3719
+ priceInfo: defaultPriceInfo(p.base_price, p.currency),
3720
+ groups: expandCompositeGroups(registry, p.id, clock.iso()),
3721
+ components: expandBundleComponents(registry, p.id),
3722
+ composite: composite ? {
3723
+ id: composite.id,
3724
+ pricing_mode: composite.pricing_mode,
3725
+ min_order_quantity: composite.min_order_quantity,
3726
+ max_order_quantity: composite.max_order_quantity
3727
+ } : void 0,
3728
+ bundle: bundle ? {
3729
+ id: bundle.id,
3730
+ pricing_type: bundle.pricing_type,
3731
+ bundle_price: bundle.bundle_price,
3732
+ discount_value: bundle.discount_value
3733
+ } : void 0
3734
+ });
3735
+ };
3736
+ const resolveProduct = (idOrSlug) => registry.products.get(idOrSlug) ?? registry.products.list((p) => p.slug === idOrSlug)[0];
3367
3737
  return {
3368
3738
  snapshot(businessId) {
3369
3739
  const products = productsForBusiness(businessId);
@@ -3371,7 +3741,7 @@ function createCatalogueService(opts) {
3371
3741
  const addOns = registry.addOns.list((a) => a.business_id === businessId);
3372
3742
  return {
3373
3743
  categories,
3374
- products: products.map(toApiProduct),
3744
+ products: products.map(apiProduct),
3375
3745
  add_ons: addOns,
3376
3746
  is_complete: true,
3377
3747
  total_available: products.length
@@ -3386,7 +3756,7 @@ function createCatalogueService(opts) {
3386
3756
  const start = (page - 1) * limit;
3387
3757
  const slice = items.slice(start, start + limit);
3388
3758
  return {
3389
- items: slice.map(toApiProduct),
3759
+ items: slice.map(apiProduct),
3390
3760
  is_complete: start + limit >= items.length,
3391
3761
  total_available: items.length,
3392
3762
  pagination: {
@@ -3399,9 +3769,9 @@ function createCatalogueService(opts) {
3399
3769
  };
3400
3770
  },
3401
3771
  getProduct(productId) {
3402
- const p = registry.products.get(productId);
3772
+ const p = resolveProduct(productId);
3403
3773
  if (!p) throw notFound(`Product ${productId} not found`);
3404
- return toApiProduct(p);
3774
+ return apiProduct(p);
3405
3775
  },
3406
3776
  getProductVariants(productId) {
3407
3777
  return variantsForProduct(productId);
@@ -3411,7 +3781,9 @@ function createCatalogueService(opts) {
3411
3781
  },
3412
3782
  getProductVariantById(productId, variantId) {
3413
3783
  const v = registry.variants.get(variantId);
3414
- if (!v || v.product_id !== productId) throw notFound(`Variant ${variantId} not found on product ${productId}`);
3784
+ if (!v || v.product_id !== productId) {
3785
+ throw notFound(`Variant ${variantId} not found on product ${productId}`);
3786
+ }
3415
3787
  return v;
3416
3788
  },
3417
3789
  findProductVariant(productId, properties) {
@@ -3441,13 +3813,13 @@ function createCatalogueService(opts) {
3441
3813
  return registry.categories.list((c) => c.business_id === businessId);
3442
3814
  },
3443
3815
  getCategory(id) {
3444
- const c = registry.categories.get(id);
3816
+ const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
3445
3817
  if (!c) throw notFound(`Category ${id} not found`);
3446
3818
  return c;
3447
3819
  },
3448
3820
  getCategoryProducts(id) {
3449
3821
  const c = this.getCategory(id);
3450
- return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(toApiProduct);
3822
+ return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
3451
3823
  },
3452
3824
  getCategoryAttributes(id) {
3453
3825
  const c = registry.categories.get(id);
@@ -3460,13 +3832,13 @@ function createCatalogueService(opts) {
3460
3832
  return registry.collections.list((c) => c.business_id === businessId);
3461
3833
  },
3462
3834
  getCollection(id) {
3463
- const c = registry.collections.get(id);
3835
+ const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
3464
3836
  if (!c) throw notFound(`Collection ${id} not found`);
3465
3837
  return c;
3466
3838
  },
3467
3839
  getCollectionProducts(id) {
3468
3840
  const c = this.getCollection(id);
3469
- return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(toApiProduct);
3841
+ return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
3470
3842
  },
3471
3843
  getCollectionAttributes(id) {
3472
3844
  const c = registry.collections.get(id);
@@ -3479,7 +3851,7 @@ function createCatalogueService(opts) {
3479
3851
  return registry.bundles.list((b) => b.business_id === businessId);
3480
3852
  },
3481
3853
  getBundle(id) {
3482
- const b = registry.bundles.get(id);
3854
+ const b = registry.bundles.get(id) ?? registry.bundles.list((x) => x.product_id === id || x.slug === id)[0];
3483
3855
  if (!b) throw notFound(`Bundle ${id} not found`);
3484
3856
  return b;
3485
3857
  },
@@ -3487,12 +3859,16 @@ function createCatalogueService(opts) {
3487
3859
  return registry.composites.list((c) => c.business_id === businessId);
3488
3860
  },
3489
3861
  getComposite(id) {
3490
- const c = registry.composites.get(id);
3862
+ const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
3491
3863
  if (!c) throw notFound(`Composite ${id} not found`);
3492
3864
  return c;
3493
3865
  },
3494
3866
  getCompositeByProduct(productId) {
3495
- return registry.composites.list((c) => c.product_id === productId)[0] ?? null;
3867
+ const direct = registry.composites.list((c) => c.product_id === productId)[0];
3868
+ if (direct) return direct;
3869
+ const product = registry.products.list((p) => p.slug === productId)[0];
3870
+ if (!product) return null;
3871
+ return registry.composites.list((c) => c.product_id === product.id)[0] ?? null;
3496
3872
  },
3497
3873
  calculateCompositePrice(id, body) {
3498
3874
  const c = registry.composites.get(id);
@@ -3500,14 +3876,20 @@ function createCatalogueService(opts) {
3500
3876
  const sel = body.selections ?? [];
3501
3877
  let total = c.base_price;
3502
3878
  for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
3503
- return { final_price: total, base_price: c.base_price, components_total: money(parseFloat(total) - parseFloat(c.base_price)) };
3879
+ return {
3880
+ final_price: total,
3881
+ base_price: c.base_price,
3882
+ components_total: money(parseFloat(total) - parseFloat(c.base_price))
3883
+ };
3504
3884
  },
3505
3885
  listTaxonomies(businessId) {
3506
3886
  return registry.taxonomies.list((t) => t.business_id === businessId);
3507
3887
  },
3508
3888
  searchTaxonomies(businessId, q) {
3509
3889
  const term = q.toLowerCase();
3510
- return registry.taxonomies.list((t) => t.business_id === businessId && t.name.toLowerCase().includes(term));
3890
+ return registry.taxonomies.list(
3891
+ (t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
3892
+ );
3511
3893
  },
3512
3894
  getTaxonomy(id) {
3513
3895
  const t = registry.taxonomies.get(id);
@@ -3541,7 +3923,11 @@ function createCatalogueService(opts) {
3541
3923
  listPropertyFacets(businessId) {
3542
3924
  const products = productsForBusiness(businessId);
3543
3925
  const byCategory = /* @__PURE__ */ new Map();
3544
- for (const p of products) for (const cid of p.category_ids) byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
3926
+ for (const p of products) {
3927
+ for (const cid of p.category_ids) {
3928
+ byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
3929
+ }
3930
+ }
3545
3931
  return [
3546
3932
  {
3547
3933
  name: "Category",
@@ -3560,12 +3946,6 @@ function createCatalogueService(opts) {
3560
3946
  listOnSale() {
3561
3947
  return [];
3562
3948
  },
3563
- validateDiscount(code) {
3564
- if (code === "WELCOME10") {
3565
- return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
3566
- }
3567
- return { valid: false, reason: "Code not recognized" };
3568
- },
3569
3949
  getProductDeals() {
3570
3950
  return [];
3571
3951
  },
@@ -3575,15 +3955,33 @@ function createCatalogueService(opts) {
3575
3955
  getCollectionDeals() {
3576
3956
  return [];
3577
3957
  },
3958
+ validateDiscount(code) {
3959
+ if (code === "WELCOME10") {
3960
+ return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
3961
+ }
3962
+ return { valid: false, reason: "Code not recognized" };
3963
+ },
3578
3964
  createQuote(businessId, body) {
3579
3965
  const b = body;
3580
3966
  const id = ids.quote();
3967
+ const productId = b.product_id ?? "";
3968
+ const product = resolveProduct(productId);
3969
+ const price = b.total_price ?? product?.base_price ?? "0.00";
3970
+ const currency = b.currency ?? product?.currency ?? "GHS";
3971
+ const priceInfo = defaultPriceInfo(price, currency);
3581
3972
  const quote = {
3582
3973
  id,
3974
+ quote_id: id,
3583
3975
  business_id: businessId,
3584
- product_id: b.product_id ?? "",
3976
+ product_id: product?.id ?? productId,
3585
3977
  status: "fresh",
3586
- total_price: b.total_price ?? "0.00",
3978
+ total_price: price,
3979
+ currency,
3980
+ unit_price_info: priceInfo,
3981
+ final_price_info: priceInfo,
3982
+ quoted_total_price_info: priceInfo,
3983
+ components: [],
3984
+ breakdown: { base_price: price, components_total: "0.00", final_price: price },
3587
3985
  expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
3588
3986
  created_at: clock.iso()
3589
3987
  };
@@ -3599,7 +3997,11 @@ function createCatalogueService(opts) {
3599
3997
  refreshQuote(id) {
3600
3998
  const q = registry.quotes.get(id);
3601
3999
  if (!q) throw notFound(`Quote ${id} not found`);
3602
- const refreshed = { ...q, status: "fresh", expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString() };
4000
+ const refreshed = {
4001
+ ...q,
4002
+ status: "fresh",
4003
+ expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
4004
+ };
3603
4005
  registry.quotes.put(id, refreshed);
3604
4006
  return refreshed;
3605
4007
  }
@@ -4624,67 +5026,82 @@ function createLiteService(opts) {
4624
5026
 
4625
5027
  // src/mock/seeds/default.ts
4626
5028
  var ISO = "2026-01-01T00:00:00.000Z";
4627
- var PRODUCTS = [
4628
- { name: "Sourdough Loaf", slug: "sourdough-loaf", price: "25.00", category: "breads", description: "Artisanal 24-hour fermented sourdough.", image: "/img/sourdough.jpg" },
4629
- { name: "Whole Wheat Bread", slug: "whole-wheat", price: "20.00", category: "breads", description: "Hearty whole wheat with seeds.", image: "/img/wheat.jpg" },
4630
- { name: "Baguette", slug: "baguette", price: "15.00", category: "breads", description: "Crispy crust, soft inside.", image: "/img/baguette.jpg" },
4631
- { name: "Focaccia", slug: "focaccia", price: "30.00", category: "breads", description: "Olive oil and rosemary focaccia.", image: "/img/focaccia.jpg" },
4632
- { name: "Croissant", slug: "croissant", price: "12.00", category: "pastries", description: "Buttery French croissant.", image: "/img/croissant.jpg" },
4633
- { name: "Pain au Chocolat", slug: "pain-au-chocolat", price: "14.00", category: "pastries", description: "Croissant with dark chocolate.", image: "/img/pac.jpg" },
4634
- { name: "Almond Danish", slug: "almond-danish", price: "16.00", category: "pastries", description: "Flaky pastry with almond cream.", image: "/img/danish.jpg" },
4635
- { name: "Cinnamon Roll", slug: "cinnamon-roll", price: "13.00", category: "pastries", description: "Soft roll with cinnamon glaze.", image: "/img/cinnamon.jpg" },
4636
- { name: "Chocolate Cake (Slice)", slug: "chocolate-cake", price: "22.00", category: "cakes", description: "Rich dark chocolate cake.", image: "/img/choc-cake.jpg" },
4637
- { name: "Red Velvet (Slice)", slug: "red-velvet", price: "22.00", category: "cakes", description: "Classic red velvet with cream cheese.", image: "/img/red-velvet.jpg" },
4638
- { name: "Carrot Cake (Slice)", slug: "carrot-cake", price: "20.00", category: "cakes", description: "Spiced carrot cake with walnut.", image: "/img/carrot.jpg" },
4639
- { name: "Cheesecake (Slice)", slug: "cheesecake", price: "24.00", category: "cakes", description: "New York style cheesecake.", image: "/img/cheesecake.jpg" },
4640
- { name: "Coffee", slug: "coffee", price: "10.00", category: "drinks", description: "Locally roasted Arabica.", image: "/img/coffee.jpg" },
4641
- { name: "Tea", slug: "tea", price: "8.00", category: "drinks", description: "Black, green, or herbal.", image: "/img/tea.jpg" },
4642
- { name: "Hibiscus Tea (Sobolo)", slug: "sobolo", price: "12.00", category: "drinks", description: "Iced hibiscus drink.", image: "/img/sobolo.jpg" },
4643
- { name: "Fresh Orange Juice", slug: "orange-juice", price: "15.00", category: "drinks", description: "Cold-pressed orange juice.", image: "/img/oj.jpg" },
4644
- { name: "Banana Bread", slug: "banana-bread", price: "18.00", category: "breads", description: "Moist banana loaf.", image: "/img/banana.jpg" },
4645
- { name: "Brownie", slug: "brownie", price: "12.00", category: "pastries", description: "Fudgy chocolate brownie.", image: "/img/brownie.jpg" },
4646
- { name: "Macaron Box (6)", slug: "macaron-box", price: "60.00", category: "pastries", description: "Six assorted macarons.", image: "/img/macaron.jpg" },
4647
- { name: "Custom Birthday Cake", slug: "birthday-cake", price: "250.00", category: "cakes", description: "Made-to-order. Min 48h notice.", image: "/img/birthday.jpg" }
4648
- ];
4649
- var CATEGORIES = [
4650
- { slug: "breads", name: "Breads", description: "Sourdough, baguettes, focaccia." },
4651
- { slug: "pastries", name: "Pastries", description: "Croissants, danishes, brownies." },
4652
- { slug: "cakes", name: "Cakes", description: "Cake slices and custom orders." },
4653
- { slug: "drinks", name: "Drinks", description: "Coffee, tea, juices." }
4654
- ];
5029
+ var BUSINESS_ID = "bus_default_akua_bakery";
5030
+ var img = (id) => `https://images.unsplash.com/photo-${id}?w=800&h=800&fit=crop&auto=format&q=80`;
5031
+ function makeProduct(input, categoryIds) {
5032
+ const catId = categoryIds.get(input.category);
5033
+ return {
5034
+ id: input.id,
5035
+ business_id: BUSINESS_ID,
5036
+ name: input.name,
5037
+ slug: input.slug,
5038
+ description: input.description,
5039
+ product_type: input.productType ?? "product",
5040
+ base_price: input.price,
5041
+ currency: "GHS",
5042
+ image: input.imageUrl,
5043
+ images: input.images ?? [input.imageUrl],
5044
+ is_available: true,
5045
+ category_ids: [catId],
5046
+ collection_ids: [],
5047
+ add_on_ids: [],
5048
+ variant_ids: [],
5049
+ tags: input.tags ?? [],
5050
+ calories: input.calories,
5051
+ allergies: input.allergies ?? [],
5052
+ ingredients: input.ingredients ?? [],
5053
+ pairings: input.pairings ?? [],
5054
+ is_signature: input.isSignature ?? false,
5055
+ is_new: input.isNew ?? false,
5056
+ created_at: ISO,
5057
+ updated_at: ISO
5058
+ };
5059
+ }
4655
5060
  function seedDefault(registry) {
4656
- const businessId = "bus_default_akua_bakery";
4657
5061
  const business = {
4658
- id: businessId,
5062
+ id: BUSINESS_ID,
4659
5063
  name: "Akua's Bakery",
4660
5064
  handle: "akua",
4661
5065
  business_type: "eatery",
4662
5066
  email: "hello@akuasbakery.test",
4663
5067
  default_currency: "GHS",
4664
- default_phone: "+233244000000",
5068
+ default_phone: "+233 244 000 000",
4665
5069
  default_address: "12 Independence Ave, Accra",
4666
5070
  default_offers_table_service: true,
4667
5071
  default_accepts_online_orders: true,
4668
- image: "/img/akua-logo.png",
5072
+ image: img("1568254183919-78a4f43a2877"),
4669
5073
  status: "active",
4670
5074
  created_at: ISO,
4671
5075
  updated_at: ISO,
4672
5076
  created_by: "seed",
4673
- preferences: { theme: "warm", checkout: { allow_guest: true } },
5077
+ preferences: {
5078
+ tagline: "Fresh from the oven, every day",
5079
+ description: "Family-run bakery in East Legon. Sourdough, croissants, layered cakes \u2014 handcrafted since 2018, delivered across Accra.",
5080
+ instagram: "https://instagram.com/akuasbakery",
5081
+ theme: "warm",
5082
+ checkout: { allow_guest: true }
5083
+ },
4674
5084
  is_online_only: false,
4675
5085
  enabled_payment_types: ["mobile_money", "card", "cash"],
4676
5086
  default_location_settings: {},
4677
5087
  country_code: "GH",
4678
5088
  timezone: "Africa/Accra"
4679
5089
  };
4680
- registry.businesses.put(businessId, business);
4681
- const categoryIdsBySlug = /* @__PURE__ */ new Map();
4682
- for (const c of CATEGORIES) {
5090
+ registry.businesses.put(BUSINESS_ID, business);
5091
+ const categoryDefs = [
5092
+ { slug: "breads", name: "Breads", description: "Naturally leavened sourdough, baguettes, focaccia." },
5093
+ { slug: "pastries", name: "Pastries", description: "Hand-laminated croissants, danishes, brownies." },
5094
+ { slug: "cakes", name: "Cakes", description: "Slices and made-to-order showstoppers." },
5095
+ { slug: "drinks", name: "Drinks", description: "Locally roasted coffee, brewed teas, fresh juice." },
5096
+ { slug: "boxes", name: "Boxes & Bundles", description: "Curated boxes for sharing." }
5097
+ ];
5098
+ const catIds = /* @__PURE__ */ new Map();
5099
+ for (const c of categoryDefs) {
4683
5100
  const id = `cat_${c.slug}`;
4684
- categoryIdsBySlug.set(c.slug, id);
5101
+ catIds.set(c.slug, id);
4685
5102
  const cat = {
4686
5103
  id,
4687
- business_id: businessId,
5104
+ business_id: BUSINESS_ID,
4688
5105
  name: c.name,
4689
5106
  slug: c.slug,
4690
5107
  description: c.description,
@@ -4696,99 +5113,354 @@ function seedDefault(registry) {
4696
5113
  };
4697
5114
  registry.categories.put(id, cat);
4698
5115
  }
4699
- const featured = [];
4700
- PRODUCTS.forEach((p, i) => {
4701
- const id = `prod_${p.slug}`;
4702
- const catId = categoryIdsBySlug.get(p.category);
4703
- const product = {
4704
- id,
4705
- business_id: businessId,
4706
- name: p.name,
4707
- slug: p.slug,
4708
- description: p.description,
4709
- product_type: "product",
4710
- base_price: p.price,
4711
- currency: "GHS",
4712
- image: p.image,
4713
- images: [p.image],
4714
- is_available: true,
4715
- category_ids: [catId],
4716
- collection_ids: [],
4717
- add_on_ids: [],
4718
- variant_ids: [],
4719
- created_at: ISO,
4720
- updated_at: ISO
4721
- };
4722
- registry.products.put(id, product);
4723
- const cat = registry.categories.get(catId);
4724
- cat.product_ids.push(id);
4725
- registry.categories.put(catId, cat);
4726
- if (i < 5) featured.push(id);
4727
- });
4728
- const featuredCollection = {
5116
+ const productInputs = [
5117
+ // ── Breads ──
5118
+ {
5119
+ id: "prod_sourdough-loaf",
5120
+ name: "Country Sourdough",
5121
+ slug: "sourdough-loaf",
5122
+ price: "25.00",
5123
+ description: "24-hour fermented country loaf with our heritage starter \u2014 open crumb, blistered crust, naturally tangy. Baked fresh every morning.",
5124
+ category: "breads",
5125
+ imageUrl: img("1509440159596-0249088772ff"),
5126
+ images: [img("1509440159596-0249088772ff"), img("1486887396153-fa416526c108")],
5127
+ tags: ["bread", "vegan", "signature"],
5128
+ isSignature: true,
5129
+ ingredients: ["Wheat flour", "Sourdough starter", "Sea salt", "Filtered water"],
5130
+ pairings: ["Olive oil", "Aged cheese", "Tomato jam"],
5131
+ allergies: ["wheat", "gluten"],
5132
+ calories: 240
5133
+ },
5134
+ {
5135
+ id: "prod_whole-wheat",
5136
+ name: "Whole Wheat Seed Loaf",
5137
+ slug: "whole-wheat",
5138
+ price: "20.00",
5139
+ description: "Hearty 100% whole wheat with sunflower, sesame, and pumpkin seeds. Slow-fermented for a soft crumb and nutty depth.",
5140
+ category: "breads",
5141
+ imageUrl: img("1486887396153-fa416526c108"),
5142
+ tags: ["bread", "vegan", "high-fibre"],
5143
+ ingredients: ["Whole wheat flour", "Sunflower seeds", "Sesame", "Pumpkin seeds"],
5144
+ allergies: ["wheat", "gluten", "sesame"],
5145
+ calories: 220
5146
+ },
5147
+ {
5148
+ id: "prod_baguette",
5149
+ name: "Classic Baguette",
5150
+ slug: "baguette",
5151
+ price: "15.00",
5152
+ description: "Crackling crust, airy crumb. Best eaten warm, within an hour of baking.",
5153
+ category: "breads",
5154
+ imageUrl: img("1568471173242-461f0a730452"),
5155
+ tags: ["bread", "vegan"],
5156
+ ingredients: ["Wheat flour", "Yeast", "Sea salt", "Water"],
5157
+ allergies: ["wheat", "gluten"],
5158
+ calories: 180
5159
+ },
5160
+ {
5161
+ id: "prod_focaccia",
5162
+ name: "Rosemary Focaccia",
5163
+ slug: "focaccia",
5164
+ price: "30.00",
5165
+ description: "High-hydration focaccia drenched in olive oil, scattered with sea salt and fresh rosemary. Sold whole or by the slab.",
5166
+ category: "breads",
5167
+ imageUrl: img("1571167530149-c1105da4c2c7"),
5168
+ tags: ["bread", "vegetarian"],
5169
+ ingredients: ["00 flour", "Olive oil", "Rosemary", "Maldon salt"],
5170
+ pairings: ["Burrata", "Prosciutto", "Caponata"],
5171
+ allergies: ["wheat", "gluten"],
5172
+ calories: 310
5173
+ },
5174
+ {
5175
+ id: "prod_banana-bread",
5176
+ name: "Brown Butter Banana Bread",
5177
+ slug: "banana-bread",
5178
+ price: "18.00",
5179
+ description: "Caramelised brown butter, three over-ripe bananas, walnuts, finished with a salted maple glaze.",
5180
+ category: "breads",
5181
+ imageUrl: img("1632931057819-4eefffa8e007"),
5182
+ tags: ["sweet", "comfort"],
5183
+ ingredients: ["Bananas", "Brown butter", "Walnuts", "Maple syrup"],
5184
+ allergies: ["wheat", "gluten", "dairy", "nuts", "eggs"],
5185
+ calories: 360,
5186
+ isNew: true
5187
+ },
5188
+ // ── Pastries ──
5189
+ {
5190
+ id: "prod_croissant",
5191
+ name: "Butter Croissant",
5192
+ slug: "croissant",
5193
+ price: "12.00",
5194
+ description: "27 layers of laminated dough wrapped around French butter. Crisp, shattering crust and a tender honeycomb interior.",
5195
+ category: "pastries",
5196
+ imageUrl: img("1555507036-ab1f4038808a"),
5197
+ images: [img("1555507036-ab1f4038808a"), img("1530610476181-d83430b64dcd")],
5198
+ tags: ["pastry", "vegetarian", "signature"],
5199
+ isSignature: true,
5200
+ ingredients: ["French butter", "Wheat flour", "Milk", "Eggs"],
5201
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5202
+ calories: 280
5203
+ },
5204
+ {
5205
+ id: "prod_pain-au-chocolat",
5206
+ name: "Pain au Chocolat",
5207
+ slug: "pain-au-chocolat",
5208
+ price: "14.00",
5209
+ description: "Our croissant dough wrapped around two batons of 70% Valrhona dark chocolate.",
5210
+ category: "pastries",
5211
+ imageUrl: img("1530610476181-d83430b64dcd"),
5212
+ tags: ["pastry", "chocolate"],
5213
+ ingredients: ["Croissant dough", "Valrhona chocolate"],
5214
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5215
+ calories: 320
5216
+ },
5217
+ {
5218
+ id: "prod_almond-danish",
5219
+ name: "Almond Danish",
5220
+ slug: "almond-danish",
5221
+ price: "16.00",
5222
+ description: "Flaky pastry filled with almond cream, dusted with toasted slivers and powdered sugar.",
5223
+ category: "pastries",
5224
+ imageUrl: img("1517433367423-c7e5b0f35086"),
5225
+ tags: ["pastry", "nuts"],
5226
+ ingredients: ["Pastry dough", "Almond cream", "Toasted almonds"],
5227
+ allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
5228
+ calories: 340
5229
+ },
5230
+ {
5231
+ id: "prod_cinnamon-roll",
5232
+ name: "Cinnamon Roll",
5233
+ slug: "cinnamon-roll",
5234
+ price: "13.00",
5235
+ description: "Soft enriched dough swirled with butter and Ceylon cinnamon, finished with cream-cheese glaze.",
5236
+ category: "pastries",
5237
+ imageUrl: img("1607920591413-4ec007e70023"),
5238
+ tags: ["pastry", "comfort"],
5239
+ ingredients: ["Enriched dough", "Cinnamon", "Brown sugar", "Cream cheese"],
5240
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5241
+ calories: 380
5242
+ },
5243
+ {
5244
+ id: "prod_brownie",
5245
+ name: "Fudge Brownie",
5246
+ slug: "brownie",
5247
+ price: "12.00",
5248
+ description: "Dark, dense, fudgy brownie with a glossy shell. Chocolate four ways. Best slightly warm.",
5249
+ category: "pastries",
5250
+ imageUrl: img("1606313564200-e75d5e30476c"),
5251
+ tags: ["chocolate", "vegetarian"],
5252
+ ingredients: ["Dark chocolate", "Cocoa", "Brown butter", "Eggs"],
5253
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5254
+ calories: 420
5255
+ },
5256
+ {
5257
+ id: "prod_macaron-box",
5258
+ name: "Macaron Box (6)",
5259
+ slug: "macaron-box",
5260
+ price: "60.00",
5261
+ description: "Six French macarons \u2014 pistachio, raspberry, salted caramel, espresso, vanilla bean, and dark chocolate ganache.",
5262
+ category: "pastries",
5263
+ imageUrl: img("1558326567-98ae2405596b"),
5264
+ tags: ["pastry", "gift", "premium"],
5265
+ ingredients: ["Almond flour", "Egg whites", "Sugar", "Various fillings"],
5266
+ allergies: ["nuts", "eggs", "dairy"],
5267
+ calories: 540
5268
+ },
5269
+ // ── Cakes ──
5270
+ {
5271
+ id: "prod_chocolate-cake",
5272
+ name: "Dark Chocolate Layer Cake",
5273
+ slug: "chocolate-cake",
5274
+ price: "22.00",
5275
+ description: "Three layers of dark chocolate sponge with chocolate ganache and cr\xE8me p\xE2tissi\xE8re, finished with a mirror glaze. (Slice)",
5276
+ category: "cakes",
5277
+ imageUrl: img("1565958011703-44f9829ba187"),
5278
+ tags: ["cake", "chocolate", "signature"],
5279
+ isSignature: true,
5280
+ ingredients: ["Dark chocolate", "Cr\xE8me p\xE2tissi\xE8re", "Cocoa", "Eggs"],
5281
+ pairings: ["Espresso", "Cabernet"],
5282
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5283
+ calories: 480
5284
+ },
5285
+ {
5286
+ id: "prod_red-velvet",
5287
+ name: "Red Velvet (Slice)",
5288
+ slug: "red-velvet",
5289
+ price: "22.00",
5290
+ description: "Classic red velvet with cream cheese frosting and white chocolate shavings.",
5291
+ category: "cakes",
5292
+ imageUrl: img("1586788680434-30d324b2d46f"),
5293
+ tags: ["cake"],
5294
+ ingredients: ["Buttermilk", "Cocoa", "Cream cheese", "Beetroot"],
5295
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5296
+ calories: 460
5297
+ },
5298
+ {
5299
+ id: "prod_carrot-cake",
5300
+ name: "Carrot Cake (Slice)",
5301
+ slug: "carrot-cake",
5302
+ price: "20.00",
5303
+ description: "Spiced carrot cake with toasted walnuts, golden raisins, and cream-cheese frosting.",
5304
+ category: "cakes",
5305
+ imageUrl: img("1568827999250-3f6afff96e66"),
5306
+ tags: ["cake", "spiced"],
5307
+ ingredients: ["Carrot", "Walnuts", "Cinnamon", "Cream cheese"],
5308
+ allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
5309
+ calories: 420
5310
+ },
5311
+ {
5312
+ id: "prod_cheesecake",
5313
+ name: "New York Cheesecake (Slice)",
5314
+ slug: "cheesecake",
5315
+ price: "24.00",
5316
+ description: "Dense, creamy New York cheesecake on a butter graham crust. Topped with seasonal berry compote.",
5317
+ category: "cakes",
5318
+ imageUrl: img("1567306226416-28f0efdc88ce"),
5319
+ tags: ["cake", "premium"],
5320
+ ingredients: ["Cream cheese", "Sour cream", "Graham crust", "Vanilla"],
5321
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5322
+ calories: 520
5323
+ },
5324
+ {
5325
+ id: "prod_birthday-cake",
5326
+ name: "Custom Celebration Cake",
5327
+ slug: "birthday-cake",
5328
+ price: "250.00",
5329
+ description: "Made-to-order three-tier cake. Choose size, flavour, and frosting. 48-hour notice required for personalisation.",
5330
+ category: "cakes",
5331
+ imageUrl: img("1535254973040-607b474cb50d"),
5332
+ images: [img("1535254973040-607b474cb50d"), img("1565958011703-44f9829ba187")],
5333
+ tags: ["cake", "made-to-order", "premium"],
5334
+ ingredients: ["Sponge cake", "Buttercream", "Custom decoration"],
5335
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5336
+ productType: "product"
5337
+ },
5338
+ // ── Drinks ──
5339
+ {
5340
+ id: "prod_coffee",
5341
+ name: "Single-Origin Coffee",
5342
+ slug: "coffee",
5343
+ price: "10.00",
5344
+ description: "Locally roasted Arabica from Volta Region. Notes of cocoa, dark fruit, and brown sugar.",
5345
+ category: "drinks",
5346
+ imageUrl: img("1495474472287-4d71bcdd2085"),
5347
+ tags: ["drink", "coffee", "vegan"],
5348
+ ingredients: ["Single-origin Arabica beans"],
5349
+ pairings: ["Croissant", "Brownie"],
5350
+ calories: 5
5351
+ },
5352
+ {
5353
+ id: "prod_tea",
5354
+ name: "Loose-Leaf Tea",
5355
+ slug: "tea",
5356
+ price: "8.00",
5357
+ description: "Choice of English breakfast, Earl Grey, green sencha, or chamomile.",
5358
+ category: "drinks",
5359
+ imageUrl: img("1576092768241-dec231879fc3"),
5360
+ tags: ["drink", "tea", "vegan"],
5361
+ calories: 0
5362
+ },
5363
+ {
5364
+ id: "prod_sobolo",
5365
+ name: "Hibiscus (Sobolo)",
5366
+ slug: "sobolo",
5367
+ price: "12.00",
5368
+ description: "Iced hibiscus tea with ginger, pineapple peel, and a hint of clove. Tart, refreshing, lightly sweet.",
5369
+ category: "drinks",
5370
+ imageUrl: img("1556679343-c7306c1976bc"),
5371
+ tags: ["drink", "vegan", "ghana"],
5372
+ isSignature: true,
5373
+ ingredients: ["Hibiscus", "Ginger", "Pineapple", "Clove"],
5374
+ calories: 60
5375
+ },
5376
+ {
5377
+ id: "prod_orange-juice",
5378
+ name: "Cold-Pressed Orange Juice",
5379
+ slug: "orange-juice",
5380
+ price: "15.00",
5381
+ description: "Hand-pressed at sunrise from Ghanaian Valencia oranges. No sugar, no concentrate.",
5382
+ category: "drinks",
5383
+ imageUrl: img("1622597467836-f3285f2131b8"),
5384
+ tags: ["drink", "vegan"],
5385
+ calories: 110
5386
+ },
5387
+ // ── Boxes (anchor products for bundle/composite) ──
5388
+ {
5389
+ id: "prod_breakfast-box",
5390
+ name: "Breakfast Box",
5391
+ slug: "breakfast-box",
5392
+ price: "30.00",
5393
+ description: "A buttery croissant, a single-origin coffee, and a fresh-pressed orange juice. Everything you need to start the day. Save GH\u20B57 vs buying separately.",
5394
+ category: "boxes",
5395
+ imageUrl: img("1533089860892-a7c6f0a88666"),
5396
+ images: [img("1533089860892-a7c6f0a88666"), img("1495474472287-4d71bcdd2085")],
5397
+ tags: ["bundle", "breakfast"],
5398
+ productType: "bundle"
5399
+ },
5400
+ {
5401
+ id: "prod_pastry-box",
5402
+ name: "Build-Your-Own Pastry Box",
5403
+ slug: "pastry-box",
5404
+ price: "60.00",
5405
+ description: "Pick 6 to 12 of our pastries \u2014 mix and match. Freshly boxed for sharing.",
5406
+ category: "boxes",
5407
+ imageUrl: img("1486427944299-d1955d23e34d"),
5408
+ tags: ["composite", "build-your-own", "gift"],
5409
+ productType: "composite"
5410
+ }
5411
+ ];
5412
+ for (const input of productInputs) {
5413
+ const product = makeProduct(input, catIds);
5414
+ registry.products.put(product.id, product);
5415
+ const cat = registry.categories.get(catIds.get(input.category));
5416
+ if (cat) {
5417
+ cat.product_ids.push(product.id);
5418
+ registry.categories.put(cat.id, cat);
5419
+ }
5420
+ }
5421
+ const featuredIds = [
5422
+ "prod_sourdough-loaf",
5423
+ "prod_croissant",
5424
+ "prod_chocolate-cake",
5425
+ "prod_sobolo",
5426
+ "prod_pastry-box"
5427
+ ];
5428
+ const featured = {
4729
5429
  id: "col_featured",
4730
- business_id: businessId,
4731
- name: "Featured",
5430
+ business_id: BUSINESS_ID,
5431
+ name: "Signature & Featured",
4732
5432
  slug: "featured",
4733
- description: "Our most popular items.",
4734
- product_ids: featured,
5433
+ description: "Our most-loved bakes and a few seasonal stars.",
5434
+ product_ids: featuredIds,
4735
5435
  is_active: true,
4736
5436
  created_at: ISO,
4737
5437
  updated_at: ISO
4738
5438
  };
4739
- registry.collections.put(featuredCollection.id, featuredCollection);
4740
- for (const pid of featured) {
5439
+ registry.collections.put(featured.id, featured);
5440
+ for (const pid of featuredIds) {
4741
5441
  const p = registry.products.get(pid);
4742
5442
  if (p) {
4743
- p.collection_ids.push(featuredCollection.id);
5443
+ p.collection_ids.push(featured.id);
4744
5444
  registry.products.put(pid, p);
4745
5445
  }
4746
5446
  }
4747
- const tasting = {
4748
- id: "svc_tasting",
4749
- business_id: businessId,
4750
- name: "Bakery Tasting Session",
4751
- description: "Walk through our pastry menu with our head baker.",
4752
- duration_minutes: 60,
4753
- price: "150.00",
4754
- is_available: true
4755
- };
4756
- registry.services.put(tasting.id, tasting);
4757
- const cake = {
4758
- id: "svc_custom_cake",
4759
- business_id: businessId,
4760
- name: "Custom Cake Consultation",
4761
- description: "Design a custom cake for your event.",
4762
- duration_minutes: 30,
4763
- price: "0.00",
4764
- is_available: true
4765
- };
4766
- registry.services.put(cake.id, cake);
4767
- const sizeAxisId = "axis_size";
4768
- const flavourAxisId = "axis_flavour";
4769
- const sizeValues = [
4770
- { id: "axv_size_small", name: 'Small (8")' },
4771
- { id: "axv_size_medium", name: 'Medium (10")' },
4772
- { id: "axv_size_large", name: 'Large (12")' }
4773
- ];
4774
- const flavourValues = [
4775
- { id: "axv_flavour_chocolate", name: "Chocolate" },
4776
- { id: "axv_flavour_vanilla", name: "Vanilla" },
4777
- { id: "axv_flavour_red_velvet", name: "Red Velvet" }
4778
- ];
4779
5447
  const sizeAxis = {
4780
- id: sizeAxisId,
4781
- business_id: businessId,
5448
+ id: "axis_cake_size",
5449
+ business_id: BUSINESS_ID,
4782
5450
  product_id: "prod_birthday-cake",
4783
5451
  name: "Size",
4784
5452
  display_order: 0,
4785
5453
  affects_recipe: false,
4786
- values: sizeValues.map((v, i) => ({
5454
+ values: [
5455
+ { id: "axv_size_small", name: 'Small (8")', display: 0 },
5456
+ { id: "axv_size_medium", name: 'Medium (10")', display: 1 },
5457
+ { id: "axv_size_large", name: 'Large (12")', display: 2 }
5458
+ ].map((v) => ({
4787
5459
  id: v.id,
4788
- business_id: businessId,
4789
- axis_id: sizeAxisId,
5460
+ business_id: BUSINESS_ID,
5461
+ axis_id: "axis_cake_size",
4790
5462
  name: v.name,
4791
- display_order: i,
5463
+ display_order: v.display,
4792
5464
  created_at: ISO,
4793
5465
  updated_at: ISO
4794
5466
  })),
@@ -4796,18 +5468,22 @@ function seedDefault(registry) {
4796
5468
  updated_at: ISO
4797
5469
  };
4798
5470
  const flavourAxis = {
4799
- id: flavourAxisId,
4800
- business_id: businessId,
5471
+ id: "axis_cake_flavour",
5472
+ business_id: BUSINESS_ID,
4801
5473
  product_id: "prod_birthday-cake",
4802
5474
  name: "Flavour",
4803
5475
  display_order: 1,
4804
5476
  affects_recipe: true,
4805
- values: flavourValues.map((v, i) => ({
5477
+ values: [
5478
+ { id: "axv_flavour_chocolate", name: "Dark Chocolate", display: 0 },
5479
+ { id: "axv_flavour_vanilla", name: "Vanilla Bean", display: 1 },
5480
+ { id: "axv_flavour_red_velvet", name: "Red Velvet", display: 2 }
5481
+ ].map((v) => ({
4806
5482
  id: v.id,
4807
- business_id: businessId,
4808
- axis_id: flavourAxisId,
5483
+ business_id: BUSINESS_ID,
5484
+ axis_id: "axis_cake_flavour",
4809
5485
  name: v.name,
4810
- display_order: i,
5486
+ display_order: v.display,
4811
5487
  created_at: ISO,
4812
5488
  updated_at: ISO
4813
5489
  })),
@@ -4816,40 +5492,42 @@ function seedDefault(registry) {
4816
5492
  };
4817
5493
  registry.variantAxes.put(sizeAxis.id, sizeAxis);
4818
5494
  registry.variantAxes.put(flavourAxis.id, flavourAxis);
4819
- const variantPrices = ["250.00", "350.00", "450.00"];
4820
- const variantIds = [];
4821
- let isFirst = true;
4822
- for (let s = 0; s < sizeValues.length; s++) {
4823
- for (let f = 0; f < flavourValues.length; f++) {
4824
- const id = `var_${sizeValues[s].id.replace("axv_", "")}_${flavourValues[f].id.replace("axv_", "")}`;
5495
+ const sizeAdj = { small: "250.00", medium: "350.00", large: "450.00" };
5496
+ const cakeVariantIds = [];
5497
+ let firstVariant = true;
5498
+ for (const sz of sizeAxis.values) {
5499
+ for (const fl of flavourAxis.values) {
5500
+ const sizeKey = sz.id.replace("axv_size_", "");
5501
+ const flavourKey = fl.id.replace("axv_flavour_", "");
5502
+ const id = `var_size_${sizeKey}_flavour_${flavourKey}`;
4825
5503
  const variant = {
4826
5504
  id,
4827
5505
  product_id: "prod_birthday-cake",
4828
- business_id: businessId,
4829
- name: `${sizeValues[s].name} \u2022 ${flavourValues[f].name}`,
4830
- sku: `BIRTHDAY-${sizeValues[s].id.split("_").pop()}-${flavourValues[f].id.split("_").pop()}`,
4831
- price_adjustment: variantPrices[s],
5506
+ business_id: BUSINESS_ID,
5507
+ name: `${sz.name} \u2022 ${fl.name}`,
5508
+ sku: `BIRTHDAY-${sizeKey.toUpperCase()}-${flavourKey.toUpperCase()}`,
5509
+ price_adjustment: sizeAdj[sizeKey] ?? "0.00",
4832
5510
  component_multiplier: "1.00",
4833
- is_default: isFirst,
5511
+ is_default: firstVariant,
4834
5512
  is_active: true,
4835
- axis_value_ids: [sizeValues[s].id, flavourValues[f].id],
5513
+ axis_value_ids: [sz.id, fl.id],
4836
5514
  created_at: ISO,
4837
5515
  updated_at: ISO
4838
5516
  };
4839
5517
  registry.variants.put(id, variant);
4840
- variantIds.push(id);
4841
- isFirst = false;
5518
+ cakeVariantIds.push(id);
5519
+ firstVariant = false;
4842
5520
  }
4843
5521
  }
4844
- const birthday = registry.products.get("prod_birthday-cake");
4845
- if (birthday) {
4846
- birthday.variant_ids = variantIds;
4847
- registry.products.put(birthday.id, birthday);
5522
+ const birthdayProduct = registry.products.get("prod_birthday-cake");
5523
+ if (birthdayProduct) {
5524
+ birthdayProduct.variant_ids = cakeVariantIds;
5525
+ registry.products.put(birthdayProduct.id, birthdayProduct);
4848
5526
  }
4849
5527
  const toppingsAddOn = {
4850
5528
  id: "addon_toppings",
4851
- business_id: businessId,
4852
- name: "Toppings",
5529
+ business_id: BUSINESS_ID,
5530
+ name: "Cake toppings",
4853
5531
  is_multiple_allowed: true,
4854
5532
  is_required: false,
4855
5533
  is_mutually_exclusive: false,
@@ -4858,15 +5536,14 @@ function seedDefault(registry) {
4858
5536
  created_at: ISO,
4859
5537
  updated_at: ISO,
4860
5538
  options: [
4861
- { id: "addopt_strawberry", add_on_id: "addon_toppings", business_id: businessId, name: "Fresh strawberries", default_price: "8.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO },
4862
- { id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id: businessId, name: "Chocolate chips", default_price: "5.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO },
4863
- { id: "addopt_walnuts", add_on_id: "addon_toppings", business_id: businessId, name: "Walnuts", default_price: "6.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO }
5539
+ { id: "addopt_strawberry", add_on_id: "addon_toppings", business_id: BUSINESS_ID, name: "Fresh strawberries", default_price: "8.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO },
5540
+ { id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id: BUSINESS_ID, name: "Dark chocolate shards", default_price: "5.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO },
5541
+ { id: "addopt_walnuts", add_on_id: "addon_toppings", business_id: BUSINESS_ID, name: "Toasted walnuts", default_price: "6.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO }
4864
5542
  ]
4865
5543
  };
4866
- registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
4867
5544
  const milkAddOn = {
4868
5545
  id: "addon_milk",
4869
- business_id: businessId,
5546
+ business_id: BUSINESS_ID,
4870
5547
  name: "Milk",
4871
5548
  is_multiple_allowed: false,
4872
5549
  is_required: false,
@@ -4876,17 +5553,17 @@ function seedDefault(registry) {
4876
5553
  created_at: ISO,
4877
5554
  updated_at: ISO,
4878
5555
  options: [
4879
- { id: "addopt_whole", add_on_id: "addon_milk", business_id: businessId, name: "Whole milk", default_price: "0.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO },
4880
- { id: "addopt_oat", add_on_id: "addon_milk", business_id: businessId, name: "Oat milk", default_price: "3.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO },
4881
- { id: "addopt_almond", add_on_id: "addon_milk", business_id: businessId, name: "Almond milk", default_price: "3.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO }
5556
+ { id: "addopt_whole", add_on_id: "addon_milk", business_id: BUSINESS_ID, name: "Whole milk", default_price: "0.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO },
5557
+ { id: "addopt_oat", add_on_id: "addon_milk", business_id: BUSINESS_ID, name: "Oat milk", default_price: "3.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO },
5558
+ { id: "addopt_almond", add_on_id: "addon_milk", business_id: BUSINESS_ID, name: "Almond milk", default_price: "3.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO }
4882
5559
  ]
4883
5560
  };
5561
+ registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
4884
5562
  registry.addOns.put(milkAddOn.id, milkAddOn);
4885
5563
  const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
4886
5564
  const drinkProductIds = ["prod_coffee", "prod_tea"];
4887
5565
  for (const pid of cakeProductIds) {
4888
- const link = { product_id: pid, add_on_id: toppingsAddOn.id };
4889
- registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
5566
+ registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
4890
5567
  const p = registry.products.get(pid);
4891
5568
  if (p) {
4892
5569
  p.add_on_ids.push(toppingsAddOn.id);
@@ -4894,8 +5571,7 @@ function seedDefault(registry) {
4894
5571
  }
4895
5572
  }
4896
5573
  for (const pid of drinkProductIds) {
4897
- const link = { product_id: pid, add_on_id: milkAddOn.id };
4898
- registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
5574
+ registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
4899
5575
  const p = registry.products.get(pid);
4900
5576
  if (p) {
4901
5577
  p.add_on_ids.push(milkAddOn.id);
@@ -4904,7 +5580,7 @@ function seedDefault(registry) {
4904
5580
  }
4905
5581
  const breakfastBundle = {
4906
5582
  id: "bnd_breakfast_box",
4907
- business_id: businessId,
5583
+ business_id: BUSINESS_ID,
4908
5584
  product_id: "prod_breakfast-box",
4909
5585
  name: "Breakfast Box",
4910
5586
  slug: "breakfast-box",
@@ -4916,29 +5592,9 @@ function seedDefault(registry) {
4916
5592
  updated_at: ISO
4917
5593
  };
4918
5594
  registry.bundles.put(breakfastBundle.id, breakfastBundle);
4919
- const breakfastProduct = {
4920
- id: "prod_breakfast-box",
4921
- business_id: businessId,
4922
- name: "Breakfast Box",
4923
- slug: "breakfast-box",
4924
- description: "Bundled breakfast for one.",
4925
- product_type: "bundle",
4926
- base_price: "30.00",
4927
- currency: "GHS",
4928
- image: "/img/breakfast-box.jpg",
4929
- images: ["/img/breakfast-box.jpg"],
4930
- is_available: true,
4931
- category_ids: [categoryIdsBySlug.get("pastries")],
4932
- collection_ids: [],
4933
- add_on_ids: [],
4934
- variant_ids: [],
4935
- created_at: ISO,
4936
- updated_at: ISO
4937
- };
4938
- registry.products.put(breakfastProduct.id, breakfastProduct);
4939
5595
  const pastryComposite = {
4940
5596
  id: "comp_pastry_box",
4941
- business_id: businessId,
5597
+ business_id: BUSINESS_ID,
4942
5598
  product_id: "prod_pastry-box",
4943
5599
  base_price: "60.00",
4944
5600
  pricing_mode: "additive",
@@ -4950,43 +5606,51 @@ function seedDefault(registry) {
4950
5606
  name: "Choose your pastries",
4951
5607
  min: 6,
4952
5608
  max: 12,
4953
- component_ids: ["prod_croissant", "prod_pain-au-chocolat", "prod_almond-danish", "prod_cinnamon-roll", "prod_brownie"]
5609
+ component_ids: [
5610
+ "prod_croissant",
5611
+ "prod_pain-au-chocolat",
5612
+ "prod_almond-danish",
5613
+ "prod_cinnamon-roll",
5614
+ "prod_brownie",
5615
+ "prod_banana-bread",
5616
+ "prod_macaron-box"
5617
+ ]
4954
5618
  }
4955
5619
  ],
4956
5620
  created_at: ISO,
4957
5621
  updated_at: ISO
4958
5622
  };
4959
5623
  registry.composites.put(pastryComposite.id, pastryComposite);
4960
- const pastryBoxProduct = {
4961
- id: "prod_pastry-box",
4962
- business_id: businessId,
4963
- name: "Build-Your-Own Pastry Box",
4964
- slug: "pastry-box",
4965
- description: "Pick 6 to 12 pastries \u2014 mix and match.",
4966
- product_type: "composite",
4967
- base_price: "60.00",
4968
- currency: "GHS",
4969
- image: "/img/pastry-box.jpg",
4970
- images: ["/img/pastry-box.jpg"],
4971
- is_available: true,
4972
- category_ids: [categoryIdsBySlug.get("pastries")],
4973
- collection_ids: [],
4974
- add_on_ids: [],
4975
- variant_ids: [],
4976
- created_at: ISO,
4977
- updated_at: ISO
5624
+ const tasting = {
5625
+ id: "svc_tasting",
5626
+ business_id: BUSINESS_ID,
5627
+ name: "Bakery Tasting Session",
5628
+ description: "Sample our pastry menu with our head baker.",
5629
+ duration_minutes: 60,
5630
+ price: "150.00",
5631
+ is_available: true
4978
5632
  };
4979
- registry.products.put(pastryBoxProduct.id, pastryBoxProduct);
5633
+ const consult = {
5634
+ id: "svc_custom_cake",
5635
+ business_id: BUSINESS_ID,
5636
+ name: "Custom Cake Consultation",
5637
+ description: "Design a custom cake for your event.",
5638
+ duration_minutes: 30,
5639
+ price: "0.00",
5640
+ is_available: true
5641
+ };
5642
+ registry.services.put(tasting.id, tasting);
5643
+ registry.services.put(consult.id, consult);
4980
5644
  const tags = [
4981
- { id: "tag_signature", business_id: businessId, name: "Signature", slug: "signature", color: "#d97706", sort_order: 0, usage_count: 5, created_at: ISO, updated_at: ISO },
4982
- { id: "tag_vegan", business_id: businessId, name: "Vegan", slug: "vegan", color: "#16a34a", sort_order: 1, usage_count: 3, created_at: ISO, updated_at: ISO },
4983
- { id: "tag_gluten_free", business_id: businessId, name: "Gluten-Free", slug: "gluten-free", color: "#2563eb", sort_order: 2, usage_count: 2, created_at: ISO, updated_at: ISO },
4984
- { id: "tag_seasonal", business_id: businessId, name: "Seasonal", slug: "seasonal", color: "#dc2626", sort_order: 3, usage_count: 4, created_at: ISO, updated_at: ISO }
5645
+ { id: "tag_signature", business_id: BUSINESS_ID, name: "Signature", slug: "signature", color: "#d97706", sort_order: 0, usage_count: 5, created_at: ISO, updated_at: ISO },
5646
+ { id: "tag_vegan", business_id: BUSINESS_ID, name: "Vegan", slug: "vegan", color: "#16a34a", sort_order: 1, usage_count: 4, created_at: ISO, updated_at: ISO },
5647
+ { id: "tag_gluten_free", business_id: BUSINESS_ID, name: "Gluten-Free", slug: "gluten-free", color: "#2563eb", sort_order: 2, usage_count: 0, created_at: ISO, updated_at: ISO },
5648
+ { id: "tag_seasonal", business_id: BUSINESS_ID, name: "Seasonal", slug: "seasonal", color: "#dc2626", sort_order: 3, usage_count: 1, created_at: ISO, updated_at: ISO }
4985
5649
  ];
4986
5650
  for (const t of tags) registry.tags.put(t.id, t);
4987
5651
  const baked = {
4988
5652
  id: "tax_baked_goods",
4989
- business_id: businessId,
5653
+ business_id: BUSINESS_ID,
4990
5654
  name: "Baked Goods",
4991
5655
  slug: "baked-goods",
4992
5656
  path: ["Food", "Baked Goods"],
@@ -4996,7 +5660,7 @@ function seedDefault(registry) {
4996
5660
  };
4997
5661
  const beverages = {
4998
5662
  id: "tax_beverages",
4999
- business_id: businessId,
5663
+ business_id: BUSINESS_ID,
5000
5664
  name: "Beverages",
5001
5665
  slug: "beverages",
5002
5666
  path: ["Food", "Beverages"],
@@ -5009,13 +5673,13 @@ function seedDefault(registry) {
5009
5673
  const attrs = [
5010
5674
  {
5011
5675
  id: "attr_allergens",
5012
- business_id: businessId,
5676
+ business_id: BUSINESS_ID,
5013
5677
  namespace: "core",
5014
5678
  name: "Allergens",
5015
5679
  slug: "allergens",
5016
5680
  description: "Common allergens present in this item.",
5017
5681
  attribute_type: "multi_select",
5018
- options: ["wheat", "eggs", "dairy", "nuts", "soy"],
5682
+ options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
5019
5683
  is_required: false,
5020
5684
  is_filterable: true,
5021
5685
  visibility: "public",
@@ -5026,7 +5690,7 @@ function seedDefault(registry) {
5026
5690
  },
5027
5691
  {
5028
5692
  id: "attr_shelf_life",
5029
- business_id: businessId,
5693
+ business_id: BUSINESS_ID,
5030
5694
  namespace: "core",
5031
5695
  name: "Shelf life",
5032
5696
  slug: "shelf-life",
@@ -5041,7 +5705,7 @@ function seedDefault(registry) {
5041
5705
  },
5042
5706
  {
5043
5707
  id: "attr_caffeine",
5044
- business_id: businessId,
5708
+ business_id: BUSINESS_ID,
5045
5709
  namespace: "core",
5046
5710
  name: "Caffeine",
5047
5711
  slug: "caffeine",
@@ -5056,7 +5720,7 @@ function seedDefault(registry) {
5056
5720
  },
5057
5721
  {
5058
5722
  id: "attr_seasonal",
5059
- business_id: businessId,
5723
+ business_id: BUSINESS_ID,
5060
5724
  namespace: "core",
5061
5725
  name: "Seasonal",
5062
5726
  slug: "seasonal",
@@ -5074,10 +5738,10 @@ function seedDefault(registry) {
5074
5738
  const articles = [
5075
5739
  {
5076
5740
  id: "kb_delivery",
5077
- business_id: businessId,
5078
- title: "Delivery & Pickup",
5741
+ business_id: BUSINESS_ID,
5742
+ title: "Delivery & pickup",
5079
5743
  slug: "delivery-pickup",
5080
- content: "Same-day delivery within Accra for orders placed before 2pm. Pickup available 8am\u20138pm.",
5744
+ content: "Same-day delivery within Greater Accra for orders placed before 2pm. Pickup available 8am\u20138pm at our East Legon location.",
5081
5745
  category: "logistics",
5082
5746
  tags: ["delivery", "pickup"],
5083
5747
  created_at: ISO,
@@ -5085,10 +5749,10 @@ function seedDefault(registry) {
5085
5749
  },
5086
5750
  {
5087
5751
  id: "kb_custom_cakes",
5088
- business_id: businessId,
5089
- title: "Ordering custom cakes",
5752
+ business_id: BUSINESS_ID,
5753
+ title: "Custom cakes & celebrations",
5090
5754
  slug: "custom-cakes",
5091
- content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page.",
5755
+ content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page to walk through flavours, sizes, and decoration.",
5092
5756
  category: "products",
5093
5757
  tags: ["cakes", "custom"],
5094
5758
  created_at: ISO,
@@ -5096,10 +5760,10 @@ function seedDefault(registry) {
5096
5760
  },
5097
5761
  {
5098
5762
  id: "kb_allergens",
5099
- business_id: businessId,
5100
- title: "Allergens",
5763
+ business_id: BUSINESS_ID,
5764
+ title: "Allergen information",
5101
5765
  slug: "allergens",
5102
- content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible.",
5766
+ content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible \u2014 let us know about severe allergies before ordering.",
5103
5767
  category: "products",
5104
5768
  tags: ["allergens", "safety"],
5105
5769
  created_at: ISO,
@@ -5107,7 +5771,7 @@ function seedDefault(registry) {
5107
5771
  }
5108
5772
  ];
5109
5773
  for (const a of articles) registry.knowledgeArticles.put(a.id, a);
5110
- return { businessId };
5774
+ return { businessId: BUSINESS_ID };
5111
5775
  }
5112
5776
 
5113
5777
  // src/mock/seeds/empty.ts
@@ -7210,195 +7874,47 @@ function createDeps(options = {}) {
7210
7874
  }
7211
7875
 
7212
7876
  // src/mock/server.ts
7877
+ var PACKAGE_NAME = "@cimplify/sdk/mock";
7878
+ function corsMiddleware(allow) {
7879
+ return async (c, next) => {
7880
+ const origin = c.req.header("origin") ?? "";
7881
+ const allowOrigin = allow === "*" ? "*" : allow.includes(origin) ? origin : allow[0] ?? "*";
7882
+ const headers = {
7883
+ "Access-Control-Allow-Origin": allowOrigin,
7884
+ "Access-Control-Allow-Credentials": "true",
7885
+ "Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
7886
+ "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
7887
+ "Access-Control-Expose-Headers": "x-session-token",
7888
+ "Access-Control-Max-Age": "86400",
7889
+ Vary: "Origin"
7890
+ };
7891
+ if (c.req.method === "OPTIONS") {
7892
+ return new Response(null, { status: 204, headers });
7893
+ }
7894
+ for (const [k, v] of Object.entries(headers)) c.header(k, v);
7895
+ await next();
7896
+ };
7897
+ }
7213
7898
  function createMockApp(options = {}) {
7214
7899
  const deps = createDeps(options);
7215
7900
  const app = new Hono2();
7216
- const corsOrigins = options.cors === "*" ? "*" : options.cors;
7217
- if (corsOrigins) {
7218
- app.use("*", async (c, next) => {
7219
- const origin = c.req.header("origin") ?? "";
7220
- const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
7221
- const headers = {
7222
- "Access-Control-Allow-Origin": allow,
7223
- "Access-Control-Allow-Credentials": "true",
7224
- "Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
7225
- "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
7226
- "Access-Control-Expose-Headers": "x-session-token",
7227
- "Access-Control-Max-Age": "86400",
7228
- Vary: "Origin"
7229
- };
7230
- if (c.req.method === "OPTIONS") {
7231
- return new Response(null, { status: 204, headers });
7232
- }
7233
- for (const [k, v] of Object.entries(headers)) c.header(k, v);
7234
- await next();
7235
- });
7236
- }
7901
+ const cors = options.cors ?? "*";
7902
+ if (cors) app.use("*", corsMiddleware(cors));
7237
7903
  app.get(
7238
7904
  "/",
7239
7905
  (c) => c.json({
7240
- name: "@cimplify/sdk/mock",
7241
- version: "0.36.0",
7906
+ name: PACKAGE_NAME,
7242
7907
  docs: "https://github.com/cimplify/cimplify",
7243
7908
  api: "/api/v1",
7244
7909
  admin: "/_mock",
7245
7910
  elements: "/elements",
7246
- seed_business_id: deps.defaultBusinessId
7911
+ images: "/img/<slug>",
7912
+ seed_business_id: deps.defaultBusinessId,
7913
+ time: deps.clock.iso()
7247
7914
  })
7248
7915
  );
7249
7916
  app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
7250
- const UNSPLASH_PHOTOS = {
7251
- // Akua's Bakery (default seed)
7252
- sourdough: "1509440159596-0249088772ff",
7253
- wheat: "1486887396153-fa416526c108",
7254
- baguette: "1568471173242-461f0a730452",
7255
- focaccia: "1573472635068-fd9b1abdd8e5",
7256
- croissant: "1555507036-ab1f4038808a",
7257
- pac: "1530610476181-d83430b64dcd",
7258
- danish: "1517433367423-c7e5b0f35086",
7259
- cinnamon: "1583527976767-50068e72b7a3",
7260
- "choc-cake": "1565958011703-44f9829ba187",
7261
- "red-velvet": "1586788680434-30d324b2d46f",
7262
- carrot: "1568827999250-3f6afff96e66",
7263
- cheesecake: "1567306226416-28f0efdc88ce",
7264
- coffee: "1495474472287-4d71bcdd2085",
7265
- tea: "1576092768241-dec231879fc3",
7266
- sobolo: "1556679343-c7306c1976bc",
7267
- oj: "1600271886742-f049b969dc7a",
7268
- banana: "1632931057819-4eefffa8e007",
7269
- brownie: "1551024506-0bccd828d307",
7270
- macaron: "1558326567-98ae2405596b",
7271
- birthday: "1535254973040-607b474cb50d",
7272
- "breakfast-box": "1533089860892-a7c6f0a88666",
7273
- "pastry-box": "1486427944299-d1955d23e34d",
7274
- // Mama's Kitchen (restaurant seed)
7275
- kelewele: "1585536830049-aedf5c1b98a4",
7276
- "spring-rolls": "1606755962773-d324e0a13086",
7277
- "soup-of-the-day": "1547592180-85f173990554",
7278
- "jollof-rice": "1604329760661-e71dc83f8f26",
7279
- "banku-tilapia": "1559847844-5315695dadae",
7280
- "fufu-light-soup": "1547928576-b822bcced36e",
7281
- waakye: "1604329760661-e71dc83f8f26",
7282
- "grilled-chicken": "1532550907401-a500c9a57435",
7283
- burger: "1568901346375-23c9450c58cd",
7284
- "yam-fries": "1573080496219-bb080dd4f877",
7285
- "fried-plantain": "1604329760661-e71dc83f8f26",
7286
- salad: "1512621776951-a57141f2eefd",
7287
- "ice-cream": "1497034825429-c343d7c6a68f",
7288
- "cake-slice": "1565958011703-44f9829ba187",
7289
- "fruit-bowl": "1490474418585-ba9bad8fd0ea",
7290
- "soft-drink": "1497534446932-c925b458314e",
7291
- "fresh-juice": "1622597467836-f3285f2131b8",
7292
- water: "1548839140-29a749e1cf4d",
7293
- feast: "1546069901-ba9599a7e63c",
7294
- byo: "1546069901-ba9599a7e63c",
7295
- // Currents Electronics
7296
- "android-flagship": "1511707171634-5f897ff02aa9",
7297
- "android-mid": "1592899677977-9c10ca588bbd",
7298
- "phone-budget": "1574944985070-8f3ebc6b79d2",
7299
- "laptop-pro": "1496181133206-80ce9b88a853",
7300
- "laptop-air": "1517336714731-489689fd1ca8",
7301
- "laptop-budget": "1541807084-5c52b6b3adef",
7302
- "headphones-anc": "1583394838336-acd977736f90",
7303
- "earbuds-pro": "1606220945770-b5b6c2c55bf1",
7304
- "speaker-bt": "1608043152269-423dbba4e7e1",
7305
- "phone-case": "1601593346740-925612772716",
7306
- "screen-protector": "1592890288564-76628a30a657",
7307
- "fast-charger": "1583394838336-acd977736f90",
7308
- "laptop-sleeve": "1525547719571-a2d4ac8945e2",
7309
- "wireless-mouse": "1527864550417-7fd91fc51a46",
7310
- "mech-keyboard": "1587829741301-dc798b83add3",
7311
- "gaming-controller": "1592840496694-26d035b52b48",
7312
- "gaming-headset": "1591105327764-407a4a06e768",
7313
- "console-handheld": "1606144042614-b2417e99c4e3",
7314
- wfh: "1593642632559-0c6d3fc62b89",
7315
- "game-pack": "1606144042614-b2417e99c4e3",
7316
- // Reesa apparel
7317
- "ankara-shirt": "1591047139829-d91aecb6caea",
7318
- "agbada-set": "1605497788044-5a32c7078486",
7319
- kaftan: "1554412933-514a83d2f3c8",
7320
- "leather-tote": "1584917865442-de89df76afd3",
7321
- sandals: "1543163521-1bf539c55dd2",
7322
- "beaded-bracelet": "1611591437281-460bfbe1220a",
7323
- owambe: "1605497788044-5a32c7078486",
7324
- // FreshMart
7325
- "tomatoes-1kg": "1546470427-227f1c75a9ef",
7326
- "onions-1kg": "1620574387735-3624d75b2dbc",
7327
- "ginger-200g": "1606937330526-f5e6a2a83555",
7328
- "bananas-bunch": "1571771894821-ce9b6c11b08e",
7329
- pineapple: "1550258987-190a2d41a8ba",
7330
- "spinach-bunch": "1576045057995-568f588f82fb",
7331
- "milk-1l": "1550583724-b2692b85b150",
7332
- "yogurt-500g": "1571212515416-fef01fc43637",
7333
- "eggs-12": "1582722872445-44dc5f7e3c8f",
7334
- "cheese-200g": "1486297678162-eb2a19b0a32d",
7335
- "chicken-1kg": "1604503468506-a8da13d82791",
7336
- "beef-mince-500g": "1607623814075-e51df1bdc82f",
7337
- "tilapia-each": "1546069901-ba9599a7e63c",
7338
- "rice-5kg": "1586201375761-83865001e31c",
7339
- "garri-2kg": "1604329760661-e71dc83f8f26",
7340
- "palm-oil-1l": "1474979266404-7eaacbcd87c5",
7341
- "vegetable-oil-1l": "1474979266404-7eaacbcd87c5",
7342
- "salt-1kg": "1612531386530-97286d97c2d2",
7343
- "sugar-1kg": "1581281697996-3aef0c3b9b12",
7344
- "tomato-paste": "1546470427-227f1c75a9ef",
7345
- "soap-bar": "1583912267550-d6c2ac3196c0",
7346
- "detergent-1kg": "1583912267550-d6c2ac3196c0",
7347
- "tissue-pack": "1610557892470-55d9e80c0bce",
7348
- "trash-bags": "1583912267550-d6c2ac3196c0",
7349
- essentials: "1542838132-92c53300491e",
7350
- // Serene Spa services
7351
- "swedish-massage-60": "1544161515-4ab6ce6db874",
7352
- "swedish-massage-90": "1544161515-4ab6ce6db874",
7353
- "deep-tissue-60": "1600334129128-685c5582fd35",
7354
- "couples-massage": "1540555700478-4be289fbecef",
7355
- "hot-stone-90": "1600334129128-685c5582fd35",
7356
- "facial-classic": "1570172619644-dfd03ed5d881",
7357
- "facial-anti-aging": "1571019613454-1cb2f99b2d8b",
7358
- "facial-acne": "1571019613454-1cb2f99b2d8b",
7359
- manicure: "1604654894610-df63bc536371",
7360
- pedicure: "1610701596007-11502861dcfa",
7361
- "gel-manicure": "1604654894610-df63bc536371",
7362
- haircut: "1562322140-8baeececf3df",
7363
- "hair-treatment": "1522337360788-8b13dee7a37e",
7364
- color: "1562322140-8baeececf3df",
7365
- "yoga-session": "1545205597-3d9d02c29597",
7366
- meditation: "1545389336-cf090694435e",
7367
- "spa-day": "1540555700478-4be289fbecef",
7368
- "couples-retreat": "1540555700478-4be289fbecef",
7369
- "custom-experience": "1545389336-cf090694435e"
7370
- };
7371
- app.get("/img/*", (c) => {
7372
- const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
7373
- const slug = path.split("/").pop() ?? "";
7374
- const photoId = UNSPLASH_PHOTOS[slug];
7375
- if (photoId) {
7376
- return c.redirect(`https://images.unsplash.com/photo-${photoId}?w=600&h=600&fit=crop&auto=format&q=80`, 302);
7377
- }
7378
- const label = slug.replace(/[-_]+/g, " ").trim() || "product";
7379
- let hash = 0;
7380
- for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
7381
- const hue = hash % 360;
7382
- const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
7383
- const svg = `<?xml version="1.0" encoding="UTF-8"?>
7384
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
7385
- <defs>
7386
- <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
7387
- <stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
7388
- <stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
7389
- </linearGradient>
7390
- </defs>
7391
- <rect width="400" height="400" fill="url(#g)"/>
7392
- <text x="200" y="210" font-family="-apple-system, BlinkMacSystemFont, Inter, sans-serif" font-size="22" font-weight="600" fill="white" text-anchor="middle" dominant-baseline="middle" style="text-transform: capitalize">${display.replace(/[<>&]/g, "")}</text>
7393
- </svg>`;
7394
- return new Response(svg, {
7395
- headers: {
7396
- "Content-Type": "image/svg+xml; charset=utf-8",
7397
- "Cache-Control": "public, max-age=3600",
7398
- "Access-Control-Allow-Origin": "*"
7399
- }
7400
- });
7401
- });
7917
+ mountImageRoutes(app);
7402
7918
  app.route("/api/v1", buildV1(deps));
7403
7919
  app.route("/_mock", buildAdmin(deps));
7404
7920
  app.route("/elements", buildElements(deps));
@@ -7406,7 +7922,11 @@ function createMockApp(options = {}) {
7406
7922
  (c) => c.json(
7407
7923
  {
7408
7924
  success: false,
7409
- error: { code: "ROUTE_NOT_REGISTERED", message: `Route ${c.req.method} ${c.req.path} not handled by mock`, retryable: false },
7925
+ error: {
7926
+ code: "ROUTE_NOT_REGISTERED",
7927
+ message: `Route ${c.req.method} ${c.req.path} not handled by mock`,
7928
+ retryable: false
7929
+ },
7410
7930
  hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
7411
7931
  },
7412
7932
  501