@cimplify/sdk 0.38.1 → 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 (45) hide show
  1. package/dist/dispatcher.mjs +1 -1
  2. package/dist/mock/cli.mjs +995 -308
  3. package/dist/mock/library.d.mts +2 -2
  4. package/dist/mock/library.d.ts +2 -2
  5. package/dist/mock/library.js +995 -308
  6. package/dist/mock/library.mjs +995 -308
  7. package/dist/mock/msw.d.mts +1 -1
  8. package/dist/mock/msw.d.ts +1 -1
  9. package/dist/mock/msw.js +995 -308
  10. package/dist/mock/msw.mjs +995 -308
  11. package/dist/react.js +12 -2
  12. package/dist/react.mjs +12 -2
  13. package/dist/{server-B8TeE2OD.d.mts → server-BNy6Ri-4.d.mts} +18 -0
  14. package/dist/{server-B8TeE2OD.d.ts → server-BNy6Ri-4.d.ts} +18 -0
  15. package/dist/styles.css +1 -1
  16. package/package.json +1 -1
  17. package/templates/storefront/.env.example +11 -2
  18. package/templates/storefront/README.md +35 -18
  19. package/templates/storefront/app/cart/page.tsx +9 -0
  20. package/templates/storefront/app/categories/[slug]/page.tsx +80 -0
  21. package/templates/storefront/app/checkout/page.tsx +17 -0
  22. package/templates/storefront/app/collections/[slug]/page.tsx +80 -0
  23. package/templates/storefront/app/globals.css +47 -0
  24. package/templates/storefront/app/layout.tsx +45 -0
  25. package/templates/storefront/app/orders/[id]/page.tsx +21 -0
  26. package/templates/storefront/app/page.tsx +65 -0
  27. package/templates/storefront/app/shop/page.tsx +46 -0
  28. package/templates/storefront/components/category-grid.tsx +28 -0
  29. package/templates/storefront/components/collection-strip.tsx +45 -0
  30. package/templates/storefront/components/footer.tsx +19 -0
  31. package/templates/storefront/components/header.tsx +41 -0
  32. package/templates/storefront/components/hero.tsx +25 -0
  33. package/templates/storefront/components/product-modal.tsx +105 -0
  34. package/templates/storefront/components/providers.tsx +31 -0
  35. package/templates/storefront/components/store-product-card.tsx +48 -0
  36. package/templates/storefront/lib/cart.ts +27 -0
  37. package/templates/storefront/next.config.ts +36 -0
  38. package/templates/storefront/package.json +13 -14
  39. package/templates/storefront/postcss.config.mjs +7 -0
  40. package/templates/storefront/tsconfig.json +15 -11
  41. package/templates/storefront/index.html +0 -12
  42. package/templates/storefront/src/App.tsx +0 -118
  43. package/templates/storefront/src/main.tsx +0 -28
  44. package/templates/storefront/src/styles.css +0 -65
  45. package/templates/storefront/vite.config.ts +0 -8
@@ -2984,6 +2984,178 @@ function buildElements(deps) {
2984
2984
  return app;
2985
2985
  }
2986
2986
 
2987
+ // src/mock/routes/images.ts
2988
+ var SLUG_KEYWORDS = {
2989
+ // ─── Akua's Bakery (default seed) ───
2990
+ sourdough: "sourdough,bread",
2991
+ wheat: "whole-wheat,bread",
2992
+ baguette: "baguette",
2993
+ focaccia: "focaccia,bread",
2994
+ croissant: "croissant",
2995
+ pac: "pain-au-chocolat,croissant",
2996
+ danish: "danish,pastry",
2997
+ cinnamon: "cinnamon-roll",
2998
+ "choc-cake": "chocolate-cake,slice",
2999
+ "red-velvet": "red-velvet-cake",
3000
+ carrot: "carrot-cake",
3001
+ cheesecake: "cheesecake",
3002
+ coffee: "coffee,cup",
3003
+ tea: "tea,cup",
3004
+ sobolo: "hibiscus-drink",
3005
+ oj: "orange-juice,glass",
3006
+ banana: "banana-bread",
3007
+ brownie: "brownie",
3008
+ macaron: "macaron,box",
3009
+ birthday: "birthday-cake",
3010
+ "breakfast-box": "breakfast",
3011
+ "pastry-box": "pastry,assortment",
3012
+ // ─── Mama's Kitchen (restaurant seed) ───
3013
+ kelewele: "fried-plantain,spicy",
3014
+ "spring-rolls": "spring-rolls",
3015
+ "soup-of-the-day": "soup,bowl",
3016
+ "jollof-rice": "jollof-rice",
3017
+ "banku-tilapia": "tilapia,grilled-fish",
3018
+ "fufu-light-soup": "fufu,soup",
3019
+ waakye: "rice,beans,african-food",
3020
+ "grilled-chicken": "grilled-chicken",
3021
+ burger: "burger",
3022
+ "yam-fries": "yam-fries,chips",
3023
+ "fried-plantain": "fried-plantain",
3024
+ salad: "garden-salad",
3025
+ "ice-cream": "ice-cream,scoop",
3026
+ "cake-slice": "chocolate-cake,slice",
3027
+ "fruit-bowl": "fruit-bowl,tropical",
3028
+ "soft-drink": "cola,glass",
3029
+ "fresh-juice": "fresh-juice",
3030
+ water: "water-bottle",
3031
+ feast: "family-meal,buffet",
3032
+ byo: "buffet,plate",
3033
+ // ─── Currents Electronics ───
3034
+ "android-flagship": "smartphone,android",
3035
+ "android-mid": "smartphone",
3036
+ "phone-budget": "phone",
3037
+ "laptop-pro": "laptop,macbook",
3038
+ "laptop-air": "laptop,thin",
3039
+ "laptop-budget": "laptop",
3040
+ "headphones-anc": "headphones",
3041
+ "earbuds-pro": "earbuds,wireless",
3042
+ "speaker-bt": "speaker,bluetooth",
3043
+ "phone-case": "phone-case",
3044
+ "screen-protector": "screen,protector",
3045
+ "fast-charger": "charger,usb",
3046
+ "laptop-sleeve": "laptop-sleeve,case",
3047
+ "wireless-mouse": "wireless-mouse",
3048
+ "mech-keyboard": "mechanical-keyboard",
3049
+ "gaming-controller": "game-controller",
3050
+ "gaming-headset": "gaming-headset",
3051
+ "console-handheld": "handheld-console,gaming",
3052
+ wfh: "desk-setup,laptop",
3053
+ "game-pack": "gaming,console",
3054
+ // ─── Reesa apparel ───
3055
+ "ankara-shirt": "ankara,african-fashion",
3056
+ "agbada-set": "agbada,nigerian-fashion",
3057
+ kaftan: "kaftan,african-dress",
3058
+ "leather-tote": "leather-tote,bag",
3059
+ sandals: "sandals,leather",
3060
+ "beaded-bracelet": "beaded-bracelet",
3061
+ owambe: "african-fashion",
3062
+ // ─── FreshMart ───
3063
+ "tomatoes-1kg": "tomatoes",
3064
+ "onions-1kg": "onions",
3065
+ "ginger-200g": "ginger,root",
3066
+ "bananas-bunch": "bananas",
3067
+ pineapple: "pineapple",
3068
+ "spinach-bunch": "spinach,leaves",
3069
+ "milk-1l": "milk,carton",
3070
+ "yogurt-500g": "yogurt",
3071
+ "eggs-12": "eggs,carton",
3072
+ "cheese-200g": "cheese",
3073
+ "chicken-1kg": "raw-chicken",
3074
+ "beef-mince-500g": "beef,mince",
3075
+ "tilapia-each": "fish,tilapia",
3076
+ "rice-5kg": "rice,bag",
3077
+ "garri-2kg": "rice,grain",
3078
+ "palm-oil-1l": "oil,bottle",
3079
+ "vegetable-oil-1l": "oil,bottle",
3080
+ "salt-1kg": "salt,seasoning",
3081
+ "sugar-1kg": "sugar,white",
3082
+ "tomato-paste": "tomato-paste,can",
3083
+ "soap-bar": "soap,bar",
3084
+ "detergent-1kg": "detergent,laundry",
3085
+ "tissue-pack": "tissue,paper",
3086
+ "trash-bags": "trash-bag",
3087
+ essentials: "groceries,box",
3088
+ // ─── Serene Spa ───
3089
+ "swedish-massage-60": "massage,relaxation",
3090
+ "swedish-massage-90": "massage,relaxation",
3091
+ "deep-tissue-60": "massage,deep-tissue",
3092
+ "couples-massage": "couples-massage",
3093
+ "hot-stone-90": "hot-stone,massage",
3094
+ "facial-classic": "facial,spa",
3095
+ "facial-anti-aging": "facial,skincare",
3096
+ "facial-acne": "facial,skincare",
3097
+ manicure: "manicure,nails",
3098
+ pedicure: "pedicure,feet",
3099
+ "gel-manicure": "manicure,gel-nails",
3100
+ haircut: "haircut,salon",
3101
+ "hair-treatment": "hair-treatment,salon",
3102
+ color: "hair-color,salon",
3103
+ "yoga-session": "yoga,studio",
3104
+ meditation: "meditation,zen",
3105
+ "spa-day": "spa,wellness",
3106
+ "couples-retreat": "spa,couple",
3107
+ "custom-experience": "spa,wellness"
3108
+ };
3109
+ var NO_CACHE = {
3110
+ "Cache-Control": "no-cache, no-store, must-revalidate"
3111
+ };
3112
+ function lockFromSlug(slug) {
3113
+ let hash = 0;
3114
+ for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
3115
+ return hash % 9999 + 1;
3116
+ }
3117
+ function svgFallback(slug) {
3118
+ const label = slug.replace(/[-_]+/g, " ").trim() || "product";
3119
+ const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
3120
+ let hash = 0;
3121
+ for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
3122
+ const hue = hash % 360;
3123
+ const safe = display.replace(/[<>&]/g, "");
3124
+ const svg = `<?xml version="1.0" encoding="UTF-8"?>
3125
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
3126
+ <defs>
3127
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
3128
+ <stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
3129
+ <stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
3130
+ </linearGradient>
3131
+ </defs>
3132
+ <rect width="400" height="400" fill="url(#g)"/>
3133
+ <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>
3134
+ </svg>`;
3135
+ return new Response(svg, {
3136
+ headers: {
3137
+ "Content-Type": "image/svg+xml; charset=utf-8",
3138
+ ...NO_CACHE,
3139
+ "Access-Control-Allow-Origin": "*"
3140
+ }
3141
+ });
3142
+ }
3143
+ function handleImageRequest(c) {
3144
+ const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
3145
+ const slug = path.split("/").pop() ?? "";
3146
+ for (const [k, v] of Object.entries(NO_CACHE)) c.header(k, v);
3147
+ if (!slug) return svgFallback("");
3148
+ const keyword = SLUG_KEYWORDS[slug] ?? slug.replace(/[-_]+/g, ",");
3149
+ const lock = lockFromSlug(slug);
3150
+ return c.redirect(
3151
+ `https://loremflickr.com/600/600/${encodeURIComponent(keyword)}?lock=${lock}`,
3152
+ 302
3153
+ );
3154
+ }
3155
+ function mountImageRoutes(app) {
3156
+ app.get("/img/*", handleImageRequest);
3157
+ }
3158
+
2987
3159
  // src/mock/core/clock.ts
2988
3160
  function createClock(opts) {
2989
3161
  let frozenAt = opts?.frozenAt ? opts.frozenAt.getTime() : null;
@@ -3321,36 +3493,249 @@ function percent(a, pct) {
3321
3493
  return toMajor(Math.round(toMinor(a) * pct / 100));
3322
3494
  }
3323
3495
 
3324
- // src/mock/domain/catalogue.ts
3325
- function createCatalogueService(opts) {
3326
- const { registry, clock, ids } = opts;
3327
- const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
3328
- const toApiProduct = (p) => ({
3496
+ // src/mock/domain/catalogue/price-info.ts
3497
+ function defaultPriceInfo(price, currency) {
3498
+ return {
3499
+ base_price: price,
3500
+ final_price: price,
3501
+ pre_tax_price: price,
3502
+ markup_percentage: "0.00",
3503
+ markup_amount: "0.00",
3504
+ markup_discount_percentage: "0.00",
3505
+ markup_discount_amount: "0.00",
3506
+ currency,
3507
+ tax_info: {
3508
+ tax_rate: "0",
3509
+ tax_amount: "0.00",
3510
+ is_inclusive: true,
3511
+ pre_tax_price: price,
3512
+ components: []
3513
+ }
3514
+ };
3515
+ }
3516
+
3517
+ // src/mock/domain/catalogue/product-shape.ts
3518
+ function toCanonicalProductSummary(p) {
3519
+ return {
3520
+ id: p.id,
3521
+ business_id: p.business_id,
3522
+ category_id: p.category_ids[0],
3523
+ name: p.name,
3524
+ slug: p.slug,
3525
+ description: p.description,
3526
+ image_url: p.image,
3527
+ images: p.images,
3528
+ default_price: p.base_price,
3529
+ type: p.product_type,
3530
+ inventory_type: "none",
3531
+ variant_strategy: "fetch_all",
3532
+ is_active: p.is_available,
3533
+ inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
3534
+ tags: p.tags ?? [],
3535
+ created_at: p.created_at,
3536
+ updated_at: p.updated_at
3537
+ };
3538
+ }
3539
+ function deriveRenderHint(p) {
3540
+ if (p.product_type === "service") return "general";
3541
+ const foodish = (p.tags ?? []).some(
3542
+ (t) => /food|drink|cake|bread|pastry|dessert|coffee|tea|juice|spice/i.test(t)
3543
+ );
3544
+ return foodish ? "food" : "general";
3545
+ }
3546
+ function toApiProduct(p, ctx) {
3547
+ const { registry, priceInfo, groups, components, composite, bundle } = ctx;
3548
+ return {
3549
+ // ─── Canonical Product fields (SDK type: Product / ProductWithDetails) ───
3329
3550
  id: p.id,
3330
3551
  business_id: p.business_id,
3552
+ category_id: p.category_ids[0],
3331
3553
  name: p.name,
3332
3554
  slug: p.slug,
3333
3555
  description: p.description ?? "",
3334
- product_type: p.product_type,
3556
+ image_url: p.image,
3557
+ images: p.images,
3558
+ default_price: p.base_price,
3559
+ type: p.product_type,
3560
+ render_hint: deriveRenderHint(p),
3561
+ inventory_type: "none",
3562
+ variant_strategy: p.variant_ids.length > 0 ? "use_axes" : "fetch_all",
3563
+ is_active: p.is_available,
3564
+ inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
3565
+ tags: p.tags ?? [],
3566
+ calories: p.calories,
3567
+ allergies: p.allergies ?? [],
3568
+ created_at: p.created_at,
3569
+ updated_at: p.updated_at,
3570
+ metadata: {
3571
+ is_signature: p.is_signature ?? false,
3572
+ is_new: p.is_new ?? false,
3573
+ ingredients: p.ingredients ?? [],
3574
+ pairings: p.pairings ?? [],
3575
+ ...p.metadata ?? {}
3576
+ },
3577
+ // ─── Pricing breakdown (Price / ProductCustomizer) ───
3335
3578
  base_price: p.base_price,
3579
+ default_price_info: priceInfo,
3580
+ price_info: priceInfo,
3336
3581
  currency: p.currency,
3582
+ // ─── Compatibility aliases (older callers) ───
3337
3583
  image: p.image,
3338
- images: p.images,
3584
+ product_type: p.product_type,
3339
3585
  is_available: p.is_available,
3340
3586
  category_ids: p.category_ids,
3341
3587
  collection_ids: p.collection_ids,
3342
3588
  add_on_ids: p.add_on_ids,
3343
- metadata: p.metadata ?? {},
3344
- created_at: p.created_at,
3345
- updated_at: p.updated_at,
3589
+ variant_ids: p.variant_ids,
3346
3590
  custom_attributes: [],
3347
3591
  properties: [],
3348
3592
  has_variants: p.variant_ids.length > 0,
3349
- render_hint: "general"
3350
- });
3593
+ // ─── Embedded relations (ProductWithDetails) ───
3594
+ category: registry.categories.get(p.category_ids[0]),
3595
+ add_ons: p.add_on_ids.map((aid) => registry.addOns.get(aid)).filter(Boolean),
3596
+ variants: registry.variants.list((v) => v.product_id === p.id),
3597
+ variant_axes: registry.variantAxes.list((a) => a.product_id === p.id),
3598
+ // ─── Composite-specific (CompositeProductLayout reads product.groups) ───
3599
+ groups,
3600
+ composite_id: composite?.id,
3601
+ pricing_mode: composite?.pricing_mode,
3602
+ min_order_quantity: composite?.min_order_quantity,
3603
+ max_order_quantity: composite?.max_order_quantity,
3604
+ // ─── Bundle-specific (BundleProductLayout reads product.components) ───
3605
+ bundle_id: bundle?.id,
3606
+ bundle_pricing_type: bundle?.pricing_type,
3607
+ bundle_price: bundle?.bundle_price,
3608
+ bundle_discount_value: bundle?.discount_value,
3609
+ components
3610
+ };
3611
+ }
3612
+
3613
+ // src/mock/domain/catalogue/composite-shape.ts
3614
+ function expandCompositeGroups(registry, productId, isoNow) {
3615
+ const composite = registry.composites.list((c) => c.product_id === productId)[0];
3616
+ if (!composite) return void 0;
3617
+ return composite.groups.map((g, gIdx) => buildGroup(registry, composite, g, gIdx, isoNow));
3618
+ }
3619
+ function buildGroup(registry, composite, group, groupIdx, isoNow) {
3620
+ return {
3621
+ // ─── Canonical ComponentGroupWithComponents ───
3622
+ id: group.id,
3623
+ composite_id: composite.id,
3624
+ name: group.name,
3625
+ description: void 0,
3626
+ display_order: groupIdx,
3627
+ min_selections: group.min,
3628
+ max_selections: group.max,
3629
+ allow_quantity: true,
3630
+ max_quantity_per_component: group.max,
3631
+ pricing_behavior: "additive",
3632
+ created_at: isoNow,
3633
+ updated_at: isoNow,
3634
+ // ─── Aliases for older selectors ───
3635
+ min: group.min,
3636
+ max: group.max,
3637
+ 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))
3638
+ };
3639
+ }
3640
+ function buildComponent(composite, group, p, idx) {
3641
+ return {
3642
+ // ─── Canonical CompositeComponent ───
3643
+ id: `comp_${composite.product_id}_${p.id}`,
3644
+ composite_id: composite.id,
3645
+ group_id: group.id,
3646
+ source_type: "product",
3647
+ source_product_id: p.id,
3648
+ product_id: p.id,
3649
+ display_name: p.name,
3650
+ display_description: p.description,
3651
+ display_image_url: p.image,
3652
+ price: p.base_price,
3653
+ calories: p.calories,
3654
+ allergens: p.allergies ?? [],
3655
+ display_order: idx,
3656
+ is_popular: idx === 0,
3657
+ is_premium: false,
3658
+ is_available: p.is_available,
3659
+ is_archived: false,
3660
+ created_at: p.created_at,
3661
+ updated_at: p.updated_at,
3662
+ // ─── Compatibility aliases ───
3663
+ name: p.name,
3664
+ description: p.description,
3665
+ image_url: p.image,
3666
+ unit_price: p.base_price,
3667
+ default_price: p.base_price,
3668
+ base_price: p.base_price,
3669
+ currency: p.currency,
3670
+ product: toCanonicalProductSummary(p)
3671
+ };
3672
+ }
3673
+
3674
+ // src/mock/domain/catalogue/bundle-shape.ts
3675
+ function expandBundleComponents(registry, productId) {
3676
+ const bundle = registry.bundles.list((b) => b.product_id === productId)[0];
3677
+ if (!bundle) return void 0;
3678
+ return bundle.product_ids.map((cid) => registry.products.get(cid)).filter((p) => Boolean(p)).map((p) => buildBundleComponent(bundle.id, productId, p));
3679
+ }
3680
+ function buildBundleComponent(bundleId, productId, p) {
3681
+ return {
3682
+ id: `bp_${productId}_${p.id}`,
3683
+ bundle_id: bundleId,
3684
+ product_id: p.id,
3685
+ // ─── Fields BundleProductLayout reads ───
3686
+ product_name: p.name,
3687
+ product_image_url: p.image,
3688
+ product_description: p.description,
3689
+ product_slug: p.slug,
3690
+ effective_price: p.base_price,
3691
+ unit_price: p.base_price,
3692
+ default_price: p.base_price,
3693
+ quantity: 1,
3694
+ allow_variant_choice: false,
3695
+ available_variants: [],
3696
+ selected_variant_id: null,
3697
+ // ─── Compatibility aliases ───
3698
+ display_name: p.name,
3699
+ display_description: p.description,
3700
+ name: p.name,
3701
+ description: p.description,
3702
+ image_url: p.image,
3703
+ currency: p.currency,
3704
+ is_available: p.is_available,
3705
+ product: toCanonicalProductSummary(p)
3706
+ };
3707
+ }
3708
+
3709
+ // src/mock/domain/catalogue/index.ts
3710
+ function createCatalogueService(opts) {
3711
+ const { registry, clock, ids } = opts;
3712
+ const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
3351
3713
  const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
3352
3714
  const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
3353
3715
  const addOnLinksForProduct = (productId) => registry.productAddOns.list((l) => l.product_id === productId);
3716
+ const apiProduct = (p) => {
3717
+ const composite = p.product_type === "composite" ? registry.composites.list((c) => c.product_id === p.id)[0] : void 0;
3718
+ const bundle = p.product_type === "bundle" ? registry.bundles.list((b) => b.product_id === p.id)[0] : void 0;
3719
+ return toApiProduct(p, {
3720
+ registry,
3721
+ priceInfo: defaultPriceInfo(p.base_price, p.currency),
3722
+ groups: expandCompositeGroups(registry, p.id, clock.iso()),
3723
+ components: expandBundleComponents(registry, p.id),
3724
+ composite: composite ? {
3725
+ id: composite.id,
3726
+ pricing_mode: composite.pricing_mode,
3727
+ min_order_quantity: composite.min_order_quantity,
3728
+ max_order_quantity: composite.max_order_quantity
3729
+ } : void 0,
3730
+ bundle: bundle ? {
3731
+ id: bundle.id,
3732
+ pricing_type: bundle.pricing_type,
3733
+ bundle_price: bundle.bundle_price,
3734
+ discount_value: bundle.discount_value
3735
+ } : void 0
3736
+ });
3737
+ };
3738
+ const resolveProduct = (idOrSlug) => registry.products.get(idOrSlug) ?? registry.products.list((p) => p.slug === idOrSlug)[0];
3354
3739
  return {
3355
3740
  snapshot(businessId) {
3356
3741
  const products = productsForBusiness(businessId);
@@ -3358,7 +3743,7 @@ function createCatalogueService(opts) {
3358
3743
  const addOns = registry.addOns.list((a) => a.business_id === businessId);
3359
3744
  return {
3360
3745
  categories,
3361
- products: products.map(toApiProduct),
3746
+ products: products.map(apiProduct),
3362
3747
  add_ons: addOns,
3363
3748
  is_complete: true,
3364
3749
  total_available: products.length
@@ -3373,7 +3758,7 @@ function createCatalogueService(opts) {
3373
3758
  const start = (page - 1) * limit;
3374
3759
  const slice = items.slice(start, start + limit);
3375
3760
  return {
3376
- items: slice.map(toApiProduct),
3761
+ items: slice.map(apiProduct),
3377
3762
  is_complete: start + limit >= items.length,
3378
3763
  total_available: items.length,
3379
3764
  pagination: {
@@ -3386,9 +3771,9 @@ function createCatalogueService(opts) {
3386
3771
  };
3387
3772
  },
3388
3773
  getProduct(productId) {
3389
- const p = registry.products.get(productId);
3774
+ const p = resolveProduct(productId);
3390
3775
  if (!p) throw notFound(`Product ${productId} not found`);
3391
- return toApiProduct(p);
3776
+ return apiProduct(p);
3392
3777
  },
3393
3778
  getProductVariants(productId) {
3394
3779
  return variantsForProduct(productId);
@@ -3398,7 +3783,9 @@ function createCatalogueService(opts) {
3398
3783
  },
3399
3784
  getProductVariantById(productId, variantId) {
3400
3785
  const v = registry.variants.get(variantId);
3401
- if (!v || v.product_id !== productId) throw notFound(`Variant ${variantId} not found on product ${productId}`);
3786
+ if (!v || v.product_id !== productId) {
3787
+ throw notFound(`Variant ${variantId} not found on product ${productId}`);
3788
+ }
3402
3789
  return v;
3403
3790
  },
3404
3791
  findProductVariant(productId, properties) {
@@ -3428,13 +3815,13 @@ function createCatalogueService(opts) {
3428
3815
  return registry.categories.list((c) => c.business_id === businessId);
3429
3816
  },
3430
3817
  getCategory(id) {
3431
- const c = registry.categories.get(id);
3818
+ const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
3432
3819
  if (!c) throw notFound(`Category ${id} not found`);
3433
3820
  return c;
3434
3821
  },
3435
3822
  getCategoryProducts(id) {
3436
3823
  const c = this.getCategory(id);
3437
- return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(toApiProduct);
3824
+ return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
3438
3825
  },
3439
3826
  getCategoryAttributes(id) {
3440
3827
  const c = registry.categories.get(id);
@@ -3447,13 +3834,13 @@ function createCatalogueService(opts) {
3447
3834
  return registry.collections.list((c) => c.business_id === businessId);
3448
3835
  },
3449
3836
  getCollection(id) {
3450
- const c = registry.collections.get(id);
3837
+ const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
3451
3838
  if (!c) throw notFound(`Collection ${id} not found`);
3452
3839
  return c;
3453
3840
  },
3454
3841
  getCollectionProducts(id) {
3455
3842
  const c = this.getCollection(id);
3456
- return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(toApiProduct);
3843
+ return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
3457
3844
  },
3458
3845
  getCollectionAttributes(id) {
3459
3846
  const c = registry.collections.get(id);
@@ -3466,7 +3853,7 @@ function createCatalogueService(opts) {
3466
3853
  return registry.bundles.list((b) => b.business_id === businessId);
3467
3854
  },
3468
3855
  getBundle(id) {
3469
- const b = registry.bundles.get(id);
3856
+ const b = registry.bundles.get(id) ?? registry.bundles.list((x) => x.product_id === id || x.slug === id)[0];
3470
3857
  if (!b) throw notFound(`Bundle ${id} not found`);
3471
3858
  return b;
3472
3859
  },
@@ -3474,12 +3861,16 @@ function createCatalogueService(opts) {
3474
3861
  return registry.composites.list((c) => c.business_id === businessId);
3475
3862
  },
3476
3863
  getComposite(id) {
3477
- const c = registry.composites.get(id);
3864
+ const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
3478
3865
  if (!c) throw notFound(`Composite ${id} not found`);
3479
3866
  return c;
3480
3867
  },
3481
3868
  getCompositeByProduct(productId) {
3482
- return registry.composites.list((c) => c.product_id === productId)[0] ?? null;
3869
+ const direct = registry.composites.list((c) => c.product_id === productId)[0];
3870
+ if (direct) return direct;
3871
+ const product = registry.products.list((p) => p.slug === productId)[0];
3872
+ if (!product) return null;
3873
+ return registry.composites.list((c) => c.product_id === product.id)[0] ?? null;
3483
3874
  },
3484
3875
  calculateCompositePrice(id, body) {
3485
3876
  const c = registry.composites.get(id);
@@ -3487,14 +3878,20 @@ function createCatalogueService(opts) {
3487
3878
  const sel = body.selections ?? [];
3488
3879
  let total = c.base_price;
3489
3880
  for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
3490
- return { final_price: total, base_price: c.base_price, components_total: money(parseFloat(total) - parseFloat(c.base_price)) };
3881
+ return {
3882
+ final_price: total,
3883
+ base_price: c.base_price,
3884
+ components_total: money(parseFloat(total) - parseFloat(c.base_price))
3885
+ };
3491
3886
  },
3492
3887
  listTaxonomies(businessId) {
3493
3888
  return registry.taxonomies.list((t) => t.business_id === businessId);
3494
3889
  },
3495
3890
  searchTaxonomies(businessId, q) {
3496
3891
  const term = q.toLowerCase();
3497
- return registry.taxonomies.list((t) => t.business_id === businessId && t.name.toLowerCase().includes(term));
3892
+ return registry.taxonomies.list(
3893
+ (t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
3894
+ );
3498
3895
  },
3499
3896
  getTaxonomy(id) {
3500
3897
  const t = registry.taxonomies.get(id);
@@ -3528,7 +3925,11 @@ function createCatalogueService(opts) {
3528
3925
  listPropertyFacets(businessId) {
3529
3926
  const products = productsForBusiness(businessId);
3530
3927
  const byCategory = /* @__PURE__ */ new Map();
3531
- for (const p of products) for (const cid of p.category_ids) byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
3928
+ for (const p of products) {
3929
+ for (const cid of p.category_ids) {
3930
+ byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
3931
+ }
3932
+ }
3532
3933
  return [
3533
3934
  {
3534
3935
  name: "Category",
@@ -3547,12 +3948,6 @@ function createCatalogueService(opts) {
3547
3948
  listOnSale() {
3548
3949
  return [];
3549
3950
  },
3550
- validateDiscount(code) {
3551
- if (code === "WELCOME10") {
3552
- return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
3553
- }
3554
- return { valid: false, reason: "Code not recognized" };
3555
- },
3556
3951
  getProductDeals() {
3557
3952
  return [];
3558
3953
  },
@@ -3562,15 +3957,33 @@ function createCatalogueService(opts) {
3562
3957
  getCollectionDeals() {
3563
3958
  return [];
3564
3959
  },
3960
+ validateDiscount(code) {
3961
+ if (code === "WELCOME10") {
3962
+ return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
3963
+ }
3964
+ return { valid: false, reason: "Code not recognized" };
3965
+ },
3565
3966
  createQuote(businessId, body) {
3566
3967
  const b = body;
3567
3968
  const id = ids.quote();
3969
+ const productId = b.product_id ?? "";
3970
+ const product = resolveProduct(productId);
3971
+ const price = b.total_price ?? product?.base_price ?? "0.00";
3972
+ const currency = b.currency ?? product?.currency ?? "GHS";
3973
+ const priceInfo = defaultPriceInfo(price, currency);
3568
3974
  const quote = {
3569
3975
  id,
3976
+ quote_id: id,
3570
3977
  business_id: businessId,
3571
- product_id: b.product_id ?? "",
3978
+ product_id: product?.id ?? productId,
3572
3979
  status: "fresh",
3573
- total_price: b.total_price ?? "0.00",
3980
+ total_price: price,
3981
+ currency,
3982
+ unit_price_info: priceInfo,
3983
+ final_price_info: priceInfo,
3984
+ quoted_total_price_info: priceInfo,
3985
+ components: [],
3986
+ breakdown: { base_price: price, components_total: "0.00", final_price: price },
3574
3987
  expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
3575
3988
  created_at: clock.iso()
3576
3989
  };
@@ -3586,7 +3999,11 @@ function createCatalogueService(opts) {
3586
3999
  refreshQuote(id) {
3587
4000
  const q = registry.quotes.get(id);
3588
4001
  if (!q) throw notFound(`Quote ${id} not found`);
3589
- const refreshed = { ...q, status: "fresh", expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString() };
4002
+ const refreshed = {
4003
+ ...q,
4004
+ status: "fresh",
4005
+ expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
4006
+ };
3590
4007
  registry.quotes.put(id, refreshed);
3591
4008
  return refreshed;
3592
4009
  }
@@ -4611,67 +5028,82 @@ function createLiteService(opts) {
4611
5028
 
4612
5029
  // src/mock/seeds/default.ts
4613
5030
  var ISO = "2026-01-01T00:00:00.000Z";
4614
- var PRODUCTS = [
4615
- { name: "Sourdough Loaf", slug: "sourdough-loaf", price: "25.00", category: "breads", description: "Artisanal 24-hour fermented sourdough.", image: "https://mock.cimplify.io/img/sourdough.jpg" },
4616
- { name: "Whole Wheat Bread", slug: "whole-wheat", price: "20.00", category: "breads", description: "Hearty whole wheat with seeds.", image: "https://mock.cimplify.io/img/wheat.jpg" },
4617
- { name: "Baguette", slug: "baguette", price: "15.00", category: "breads", description: "Crispy crust, soft inside.", image: "https://mock.cimplify.io/img/baguette.jpg" },
4618
- { name: "Focaccia", slug: "focaccia", price: "30.00", category: "breads", description: "Olive oil and rosemary focaccia.", image: "https://mock.cimplify.io/img/focaccia.jpg" },
4619
- { name: "Croissant", slug: "croissant", price: "12.00", category: "pastries", description: "Buttery French croissant.", image: "https://mock.cimplify.io/img/croissant.jpg" },
4620
- { name: "Pain au Chocolat", slug: "pain-au-chocolat", price: "14.00", category: "pastries", description: "Croissant with dark chocolate.", image: "https://mock.cimplify.io/img/pac.jpg" },
4621
- { name: "Almond Danish", slug: "almond-danish", price: "16.00", category: "pastries", description: "Flaky pastry with almond cream.", image: "https://mock.cimplify.io/img/danish.jpg" },
4622
- { name: "Cinnamon Roll", slug: "cinnamon-roll", price: "13.00", category: "pastries", description: "Soft roll with cinnamon glaze.", image: "https://mock.cimplify.io/img/cinnamon.jpg" },
4623
- { name: "Chocolate Cake (Slice)", slug: "chocolate-cake", price: "22.00", category: "cakes", description: "Rich dark chocolate cake.", image: "https://mock.cimplify.io/img/choc-cake.jpg" },
4624
- { name: "Red Velvet (Slice)", slug: "red-velvet", price: "22.00", category: "cakes", description: "Classic red velvet with cream cheese.", image: "https://mock.cimplify.io/img/red-velvet.jpg" },
4625
- { name: "Carrot Cake (Slice)", slug: "carrot-cake", price: "20.00", category: "cakes", description: "Spiced carrot cake with walnut.", image: "https://mock.cimplify.io/img/carrot.jpg" },
4626
- { name: "Cheesecake (Slice)", slug: "cheesecake", price: "24.00", category: "cakes", description: "New York style cheesecake.", image: "https://mock.cimplify.io/img/cheesecake.jpg" },
4627
- { name: "Coffee", slug: "coffee", price: "10.00", category: "drinks", description: "Locally roasted Arabica.", image: "https://mock.cimplify.io/img/coffee.jpg" },
4628
- { name: "Tea", slug: "tea", price: "8.00", category: "drinks", description: "Black, green, or herbal.", image: "https://mock.cimplify.io/img/tea.jpg" },
4629
- { name: "Hibiscus Tea (Sobolo)", slug: "sobolo", price: "12.00", category: "drinks", description: "Iced hibiscus drink.", image: "https://mock.cimplify.io/img/sobolo.jpg" },
4630
- { name: "Fresh Orange Juice", slug: "orange-juice", price: "15.00", category: "drinks", description: "Cold-pressed orange juice.", image: "https://mock.cimplify.io/img/oj.jpg" },
4631
- { name: "Banana Bread", slug: "banana-bread", price: "18.00", category: "breads", description: "Moist banana loaf.", image: "https://mock.cimplify.io/img/banana.jpg" },
4632
- { name: "Brownie", slug: "brownie", price: "12.00", category: "pastries", description: "Fudgy chocolate brownie.", image: "https://mock.cimplify.io/img/brownie.jpg" },
4633
- { name: "Macaron Box (6)", slug: "macaron-box", price: "60.00", category: "pastries", description: "Six assorted macarons.", image: "https://mock.cimplify.io/img/macaron.jpg" },
4634
- { name: "Custom Birthday Cake", slug: "birthday-cake", price: "250.00", category: "cakes", description: "Made-to-order. Min 48h notice.", image: "https://mock.cimplify.io/img/birthday.jpg" }
4635
- ];
4636
- var CATEGORIES = [
4637
- { slug: "breads", name: "Breads", description: "Sourdough, baguettes, focaccia." },
4638
- { slug: "pastries", name: "Pastries", description: "Croissants, danishes, brownies." },
4639
- { slug: "cakes", name: "Cakes", description: "Cake slices and custom orders." },
4640
- { slug: "drinks", name: "Drinks", description: "Coffee, tea, juices." }
4641
- ];
5031
+ var BUSINESS_ID = "bus_default_akua_bakery";
5032
+ var img = (id) => `https://images.unsplash.com/photo-${id}?w=800&h=800&fit=crop&auto=format&q=80`;
5033
+ function makeProduct(input, categoryIds) {
5034
+ const catId = categoryIds.get(input.category);
5035
+ return {
5036
+ id: input.id,
5037
+ business_id: BUSINESS_ID,
5038
+ name: input.name,
5039
+ slug: input.slug,
5040
+ description: input.description,
5041
+ product_type: input.productType ?? "product",
5042
+ base_price: input.price,
5043
+ currency: "GHS",
5044
+ image: input.imageUrl,
5045
+ images: input.images ?? [input.imageUrl],
5046
+ is_available: true,
5047
+ category_ids: [catId],
5048
+ collection_ids: [],
5049
+ add_on_ids: [],
5050
+ variant_ids: [],
5051
+ tags: input.tags ?? [],
5052
+ calories: input.calories,
5053
+ allergies: input.allergies ?? [],
5054
+ ingredients: input.ingredients ?? [],
5055
+ pairings: input.pairings ?? [],
5056
+ is_signature: input.isSignature ?? false,
5057
+ is_new: input.isNew ?? false,
5058
+ created_at: ISO,
5059
+ updated_at: ISO
5060
+ };
5061
+ }
4642
5062
  function seedDefault(registry) {
4643
- const businessId = "bus_default_akua_bakery";
4644
5063
  const business = {
4645
- id: businessId,
5064
+ id: BUSINESS_ID,
4646
5065
  name: "Akua's Bakery",
4647
5066
  handle: "akua",
4648
5067
  business_type: "eatery",
4649
5068
  email: "hello@akuasbakery.test",
4650
5069
  default_currency: "GHS",
4651
- default_phone: "+233244000000",
5070
+ default_phone: "+233 244 000 000",
4652
5071
  default_address: "12 Independence Ave, Accra",
4653
5072
  default_offers_table_service: true,
4654
5073
  default_accepts_online_orders: true,
4655
- image: "https://mock.cimplify.io/img/akua-logo.png",
5074
+ image: img("1568254183919-78a4f43a2877"),
4656
5075
  status: "active",
4657
5076
  created_at: ISO,
4658
5077
  updated_at: ISO,
4659
5078
  created_by: "seed",
4660
- preferences: { theme: "warm", checkout: { allow_guest: true } },
5079
+ preferences: {
5080
+ tagline: "Fresh from the oven, every day",
5081
+ description: "Family-run bakery in East Legon. Sourdough, croissants, layered cakes \u2014 handcrafted since 2018, delivered across Accra.",
5082
+ instagram: "https://instagram.com/akuasbakery",
5083
+ theme: "warm",
5084
+ checkout: { allow_guest: true }
5085
+ },
4661
5086
  is_online_only: false,
4662
5087
  enabled_payment_types: ["mobile_money", "card", "cash"],
4663
5088
  default_location_settings: {},
4664
5089
  country_code: "GH",
4665
5090
  timezone: "Africa/Accra"
4666
5091
  };
4667
- registry.businesses.put(businessId, business);
4668
- const categoryIdsBySlug = /* @__PURE__ */ new Map();
4669
- for (const c of CATEGORIES) {
5092
+ registry.businesses.put(BUSINESS_ID, business);
5093
+ const categoryDefs = [
5094
+ { slug: "breads", name: "Breads", description: "Naturally leavened sourdough, baguettes, focaccia." },
5095
+ { slug: "pastries", name: "Pastries", description: "Hand-laminated croissants, danishes, brownies." },
5096
+ { slug: "cakes", name: "Cakes", description: "Slices and made-to-order showstoppers." },
5097
+ { slug: "drinks", name: "Drinks", description: "Locally roasted coffee, brewed teas, fresh juice." },
5098
+ { slug: "boxes", name: "Boxes & Bundles", description: "Curated boxes for sharing." }
5099
+ ];
5100
+ const catIds = /* @__PURE__ */ new Map();
5101
+ for (const c of categoryDefs) {
4670
5102
  const id = `cat_${c.slug}`;
4671
- categoryIdsBySlug.set(c.slug, id);
5103
+ catIds.set(c.slug, id);
4672
5104
  const cat = {
4673
5105
  id,
4674
- business_id: businessId,
5106
+ business_id: BUSINESS_ID,
4675
5107
  name: c.name,
4676
5108
  slug: c.slug,
4677
5109
  description: c.description,
@@ -4683,99 +5115,354 @@ function seedDefault(registry) {
4683
5115
  };
4684
5116
  registry.categories.put(id, cat);
4685
5117
  }
4686
- const featured = [];
4687
- PRODUCTS.forEach((p, i) => {
4688
- const id = `prod_${p.slug}`;
4689
- const catId = categoryIdsBySlug.get(p.category);
4690
- const product = {
4691
- id,
4692
- business_id: businessId,
4693
- name: p.name,
4694
- slug: p.slug,
4695
- description: p.description,
4696
- product_type: "product",
4697
- base_price: p.price,
4698
- currency: "GHS",
4699
- image: p.image,
4700
- images: [p.image],
4701
- is_available: true,
4702
- category_ids: [catId],
4703
- collection_ids: [],
4704
- add_on_ids: [],
4705
- variant_ids: [],
4706
- created_at: ISO,
4707
- updated_at: ISO
4708
- };
4709
- registry.products.put(id, product);
4710
- const cat = registry.categories.get(catId);
4711
- cat.product_ids.push(id);
4712
- registry.categories.put(catId, cat);
4713
- if (i < 5) featured.push(id);
4714
- });
4715
- const featuredCollection = {
5118
+ const productInputs = [
5119
+ // ── Breads ──
5120
+ {
5121
+ id: "prod_sourdough-loaf",
5122
+ name: "Country Sourdough",
5123
+ slug: "sourdough-loaf",
5124
+ price: "25.00",
5125
+ description: "24-hour fermented country loaf with our heritage starter \u2014 open crumb, blistered crust, naturally tangy. Baked fresh every morning.",
5126
+ category: "breads",
5127
+ imageUrl: img("1509440159596-0249088772ff"),
5128
+ images: [img("1509440159596-0249088772ff"), img("1486887396153-fa416526c108")],
5129
+ tags: ["bread", "vegan", "signature"],
5130
+ isSignature: true,
5131
+ ingredients: ["Wheat flour", "Sourdough starter", "Sea salt", "Filtered water"],
5132
+ pairings: ["Olive oil", "Aged cheese", "Tomato jam"],
5133
+ allergies: ["wheat", "gluten"],
5134
+ calories: 240
5135
+ },
5136
+ {
5137
+ id: "prod_whole-wheat",
5138
+ name: "Whole Wheat Seed Loaf",
5139
+ slug: "whole-wheat",
5140
+ price: "20.00",
5141
+ description: "Hearty 100% whole wheat with sunflower, sesame, and pumpkin seeds. Slow-fermented for a soft crumb and nutty depth.",
5142
+ category: "breads",
5143
+ imageUrl: img("1486887396153-fa416526c108"),
5144
+ tags: ["bread", "vegan", "high-fibre"],
5145
+ ingredients: ["Whole wheat flour", "Sunflower seeds", "Sesame", "Pumpkin seeds"],
5146
+ allergies: ["wheat", "gluten", "sesame"],
5147
+ calories: 220
5148
+ },
5149
+ {
5150
+ id: "prod_baguette",
5151
+ name: "Classic Baguette",
5152
+ slug: "baguette",
5153
+ price: "15.00",
5154
+ description: "Crackling crust, airy crumb. Best eaten warm, within an hour of baking.",
5155
+ category: "breads",
5156
+ imageUrl: img("1568471173242-461f0a730452"),
5157
+ tags: ["bread", "vegan"],
5158
+ ingredients: ["Wheat flour", "Yeast", "Sea salt", "Water"],
5159
+ allergies: ["wheat", "gluten"],
5160
+ calories: 180
5161
+ },
5162
+ {
5163
+ id: "prod_focaccia",
5164
+ name: "Rosemary Focaccia",
5165
+ slug: "focaccia",
5166
+ price: "30.00",
5167
+ description: "High-hydration focaccia drenched in olive oil, scattered with sea salt and fresh rosemary. Sold whole or by the slab.",
5168
+ category: "breads",
5169
+ imageUrl: img("1571167530149-c1105da4c2c7"),
5170
+ tags: ["bread", "vegetarian"],
5171
+ ingredients: ["00 flour", "Olive oil", "Rosemary", "Maldon salt"],
5172
+ pairings: ["Burrata", "Prosciutto", "Caponata"],
5173
+ allergies: ["wheat", "gluten"],
5174
+ calories: 310
5175
+ },
5176
+ {
5177
+ id: "prod_banana-bread",
5178
+ name: "Brown Butter Banana Bread",
5179
+ slug: "banana-bread",
5180
+ price: "18.00",
5181
+ description: "Caramelised brown butter, three over-ripe bananas, walnuts, finished with a salted maple glaze.",
5182
+ category: "breads",
5183
+ imageUrl: img("1632931057819-4eefffa8e007"),
5184
+ tags: ["sweet", "comfort"],
5185
+ ingredients: ["Bananas", "Brown butter", "Walnuts", "Maple syrup"],
5186
+ allergies: ["wheat", "gluten", "dairy", "nuts", "eggs"],
5187
+ calories: 360,
5188
+ isNew: true
5189
+ },
5190
+ // ── Pastries ──
5191
+ {
5192
+ id: "prod_croissant",
5193
+ name: "Butter Croissant",
5194
+ slug: "croissant",
5195
+ price: "12.00",
5196
+ description: "27 layers of laminated dough wrapped around French butter. Crisp, shattering crust and a tender honeycomb interior.",
5197
+ category: "pastries",
5198
+ imageUrl: img("1555507036-ab1f4038808a"),
5199
+ images: [img("1555507036-ab1f4038808a"), img("1530610476181-d83430b64dcd")],
5200
+ tags: ["pastry", "vegetarian", "signature"],
5201
+ isSignature: true,
5202
+ ingredients: ["French butter", "Wheat flour", "Milk", "Eggs"],
5203
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5204
+ calories: 280
5205
+ },
5206
+ {
5207
+ id: "prod_pain-au-chocolat",
5208
+ name: "Pain au Chocolat",
5209
+ slug: "pain-au-chocolat",
5210
+ price: "14.00",
5211
+ description: "Our croissant dough wrapped around two batons of 70% Valrhona dark chocolate.",
5212
+ category: "pastries",
5213
+ imageUrl: img("1530610476181-d83430b64dcd"),
5214
+ tags: ["pastry", "chocolate"],
5215
+ ingredients: ["Croissant dough", "Valrhona chocolate"],
5216
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5217
+ calories: 320
5218
+ },
5219
+ {
5220
+ id: "prod_almond-danish",
5221
+ name: "Almond Danish",
5222
+ slug: "almond-danish",
5223
+ price: "16.00",
5224
+ description: "Flaky pastry filled with almond cream, dusted with toasted slivers and powdered sugar.",
5225
+ category: "pastries",
5226
+ imageUrl: img("1517433367423-c7e5b0f35086"),
5227
+ tags: ["pastry", "nuts"],
5228
+ ingredients: ["Pastry dough", "Almond cream", "Toasted almonds"],
5229
+ allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
5230
+ calories: 340
5231
+ },
5232
+ {
5233
+ id: "prod_cinnamon-roll",
5234
+ name: "Cinnamon Roll",
5235
+ slug: "cinnamon-roll",
5236
+ price: "13.00",
5237
+ description: "Soft enriched dough swirled with butter and Ceylon cinnamon, finished with cream-cheese glaze.",
5238
+ category: "pastries",
5239
+ imageUrl: img("1607920591413-4ec007e70023"),
5240
+ tags: ["pastry", "comfort"],
5241
+ ingredients: ["Enriched dough", "Cinnamon", "Brown sugar", "Cream cheese"],
5242
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5243
+ calories: 380
5244
+ },
5245
+ {
5246
+ id: "prod_brownie",
5247
+ name: "Fudge Brownie",
5248
+ slug: "brownie",
5249
+ price: "12.00",
5250
+ description: "Dark, dense, fudgy brownie with a glossy shell. Chocolate four ways. Best slightly warm.",
5251
+ category: "pastries",
5252
+ imageUrl: img("1606313564200-e75d5e30476c"),
5253
+ tags: ["chocolate", "vegetarian"],
5254
+ ingredients: ["Dark chocolate", "Cocoa", "Brown butter", "Eggs"],
5255
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5256
+ calories: 420
5257
+ },
5258
+ {
5259
+ id: "prod_macaron-box",
5260
+ name: "Macaron Box (6)",
5261
+ slug: "macaron-box",
5262
+ price: "60.00",
5263
+ description: "Six French macarons \u2014 pistachio, raspberry, salted caramel, espresso, vanilla bean, and dark chocolate ganache.",
5264
+ category: "pastries",
5265
+ imageUrl: img("1558326567-98ae2405596b"),
5266
+ tags: ["pastry", "gift", "premium"],
5267
+ ingredients: ["Almond flour", "Egg whites", "Sugar", "Various fillings"],
5268
+ allergies: ["nuts", "eggs", "dairy"],
5269
+ calories: 540
5270
+ },
5271
+ // ── Cakes ──
5272
+ {
5273
+ id: "prod_chocolate-cake",
5274
+ name: "Dark Chocolate Layer Cake",
5275
+ slug: "chocolate-cake",
5276
+ price: "22.00",
5277
+ description: "Three layers of dark chocolate sponge with chocolate ganache and cr\xE8me p\xE2tissi\xE8re, finished with a mirror glaze. (Slice)",
5278
+ category: "cakes",
5279
+ imageUrl: img("1565958011703-44f9829ba187"),
5280
+ tags: ["cake", "chocolate", "signature"],
5281
+ isSignature: true,
5282
+ ingredients: ["Dark chocolate", "Cr\xE8me p\xE2tissi\xE8re", "Cocoa", "Eggs"],
5283
+ pairings: ["Espresso", "Cabernet"],
5284
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5285
+ calories: 480
5286
+ },
5287
+ {
5288
+ id: "prod_red-velvet",
5289
+ name: "Red Velvet (Slice)",
5290
+ slug: "red-velvet",
5291
+ price: "22.00",
5292
+ description: "Classic red velvet with cream cheese frosting and white chocolate shavings.",
5293
+ category: "cakes",
5294
+ imageUrl: img("1586788680434-30d324b2d46f"),
5295
+ tags: ["cake"],
5296
+ ingredients: ["Buttermilk", "Cocoa", "Cream cheese", "Beetroot"],
5297
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5298
+ calories: 460
5299
+ },
5300
+ {
5301
+ id: "prod_carrot-cake",
5302
+ name: "Carrot Cake (Slice)",
5303
+ slug: "carrot-cake",
5304
+ price: "20.00",
5305
+ description: "Spiced carrot cake with toasted walnuts, golden raisins, and cream-cheese frosting.",
5306
+ category: "cakes",
5307
+ imageUrl: img("1568827999250-3f6afff96e66"),
5308
+ tags: ["cake", "spiced"],
5309
+ ingredients: ["Carrot", "Walnuts", "Cinnamon", "Cream cheese"],
5310
+ allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
5311
+ calories: 420
5312
+ },
5313
+ {
5314
+ id: "prod_cheesecake",
5315
+ name: "New York Cheesecake (Slice)",
5316
+ slug: "cheesecake",
5317
+ price: "24.00",
5318
+ description: "Dense, creamy New York cheesecake on a butter graham crust. Topped with seasonal berry compote.",
5319
+ category: "cakes",
5320
+ imageUrl: img("1567306226416-28f0efdc88ce"),
5321
+ tags: ["cake", "premium"],
5322
+ ingredients: ["Cream cheese", "Sour cream", "Graham crust", "Vanilla"],
5323
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5324
+ calories: 520
5325
+ },
5326
+ {
5327
+ id: "prod_birthday-cake",
5328
+ name: "Custom Celebration Cake",
5329
+ slug: "birthday-cake",
5330
+ price: "250.00",
5331
+ description: "Made-to-order three-tier cake. Choose size, flavour, and frosting. 48-hour notice required for personalisation.",
5332
+ category: "cakes",
5333
+ imageUrl: img("1535254973040-607b474cb50d"),
5334
+ images: [img("1535254973040-607b474cb50d"), img("1565958011703-44f9829ba187")],
5335
+ tags: ["cake", "made-to-order", "premium"],
5336
+ ingredients: ["Sponge cake", "Buttercream", "Custom decoration"],
5337
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5338
+ productType: "product"
5339
+ },
5340
+ // ── Drinks ──
5341
+ {
5342
+ id: "prod_coffee",
5343
+ name: "Single-Origin Coffee",
5344
+ slug: "coffee",
5345
+ price: "10.00",
5346
+ description: "Locally roasted Arabica from Volta Region. Notes of cocoa, dark fruit, and brown sugar.",
5347
+ category: "drinks",
5348
+ imageUrl: img("1495474472287-4d71bcdd2085"),
5349
+ tags: ["drink", "coffee", "vegan"],
5350
+ ingredients: ["Single-origin Arabica beans"],
5351
+ pairings: ["Croissant", "Brownie"],
5352
+ calories: 5
5353
+ },
5354
+ {
5355
+ id: "prod_tea",
5356
+ name: "Loose-Leaf Tea",
5357
+ slug: "tea",
5358
+ price: "8.00",
5359
+ description: "Choice of English breakfast, Earl Grey, green sencha, or chamomile.",
5360
+ category: "drinks",
5361
+ imageUrl: img("1576092768241-dec231879fc3"),
5362
+ tags: ["drink", "tea", "vegan"],
5363
+ calories: 0
5364
+ },
5365
+ {
5366
+ id: "prod_sobolo",
5367
+ name: "Hibiscus (Sobolo)",
5368
+ slug: "sobolo",
5369
+ price: "12.00",
5370
+ description: "Iced hibiscus tea with ginger, pineapple peel, and a hint of clove. Tart, refreshing, lightly sweet.",
5371
+ category: "drinks",
5372
+ imageUrl: img("1556679343-c7306c1976bc"),
5373
+ tags: ["drink", "vegan", "ghana"],
5374
+ isSignature: true,
5375
+ ingredients: ["Hibiscus", "Ginger", "Pineapple", "Clove"],
5376
+ calories: 60
5377
+ },
5378
+ {
5379
+ id: "prod_orange-juice",
5380
+ name: "Cold-Pressed Orange Juice",
5381
+ slug: "orange-juice",
5382
+ price: "15.00",
5383
+ description: "Hand-pressed at sunrise from Ghanaian Valencia oranges. No sugar, no concentrate.",
5384
+ category: "drinks",
5385
+ imageUrl: img("1622597467836-f3285f2131b8"),
5386
+ tags: ["drink", "vegan"],
5387
+ calories: 110
5388
+ },
5389
+ // ── Boxes (anchor products for bundle/composite) ──
5390
+ {
5391
+ id: "prod_breakfast-box",
5392
+ name: "Breakfast Box",
5393
+ slug: "breakfast-box",
5394
+ price: "30.00",
5395
+ 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.",
5396
+ category: "boxes",
5397
+ imageUrl: img("1533089860892-a7c6f0a88666"),
5398
+ images: [img("1533089860892-a7c6f0a88666"), img("1495474472287-4d71bcdd2085")],
5399
+ tags: ["bundle", "breakfast"],
5400
+ productType: "bundle"
5401
+ },
5402
+ {
5403
+ id: "prod_pastry-box",
5404
+ name: "Build-Your-Own Pastry Box",
5405
+ slug: "pastry-box",
5406
+ price: "60.00",
5407
+ description: "Pick 6 to 12 of our pastries \u2014 mix and match. Freshly boxed for sharing.",
5408
+ category: "boxes",
5409
+ imageUrl: img("1486427944299-d1955d23e34d"),
5410
+ tags: ["composite", "build-your-own", "gift"],
5411
+ productType: "composite"
5412
+ }
5413
+ ];
5414
+ for (const input of productInputs) {
5415
+ const product = makeProduct(input, catIds);
5416
+ registry.products.put(product.id, product);
5417
+ const cat = registry.categories.get(catIds.get(input.category));
5418
+ if (cat) {
5419
+ cat.product_ids.push(product.id);
5420
+ registry.categories.put(cat.id, cat);
5421
+ }
5422
+ }
5423
+ const featuredIds = [
5424
+ "prod_sourdough-loaf",
5425
+ "prod_croissant",
5426
+ "prod_chocolate-cake",
5427
+ "prod_sobolo",
5428
+ "prod_pastry-box"
5429
+ ];
5430
+ const featured = {
4716
5431
  id: "col_featured",
4717
- business_id: businessId,
4718
- name: "Featured",
5432
+ business_id: BUSINESS_ID,
5433
+ name: "Signature & Featured",
4719
5434
  slug: "featured",
4720
- description: "Our most popular items.",
4721
- product_ids: featured,
5435
+ description: "Our most-loved bakes and a few seasonal stars.",
5436
+ product_ids: featuredIds,
4722
5437
  is_active: true,
4723
5438
  created_at: ISO,
4724
5439
  updated_at: ISO
4725
5440
  };
4726
- registry.collections.put(featuredCollection.id, featuredCollection);
4727
- for (const pid of featured) {
5441
+ registry.collections.put(featured.id, featured);
5442
+ for (const pid of featuredIds) {
4728
5443
  const p = registry.products.get(pid);
4729
5444
  if (p) {
4730
- p.collection_ids.push(featuredCollection.id);
5445
+ p.collection_ids.push(featured.id);
4731
5446
  registry.products.put(pid, p);
4732
5447
  }
4733
5448
  }
4734
- const tasting = {
4735
- id: "svc_tasting",
4736
- business_id: businessId,
4737
- name: "Bakery Tasting Session",
4738
- description: "Walk through our pastry menu with our head baker.",
4739
- duration_minutes: 60,
4740
- price: "150.00",
4741
- is_available: true
4742
- };
4743
- registry.services.put(tasting.id, tasting);
4744
- const cake = {
4745
- id: "svc_custom_cake",
4746
- business_id: businessId,
4747
- name: "Custom Cake Consultation",
4748
- description: "Design a custom cake for your event.",
4749
- duration_minutes: 30,
4750
- price: "0.00",
4751
- is_available: true
4752
- };
4753
- registry.services.put(cake.id, cake);
4754
- const sizeAxisId = "axis_size";
4755
- const flavourAxisId = "axis_flavour";
4756
- const sizeValues = [
4757
- { id: "axv_size_small", name: 'Small (8")' },
4758
- { id: "axv_size_medium", name: 'Medium (10")' },
4759
- { id: "axv_size_large", name: 'Large (12")' }
4760
- ];
4761
- const flavourValues = [
4762
- { id: "axv_flavour_chocolate", name: "Chocolate" },
4763
- { id: "axv_flavour_vanilla", name: "Vanilla" },
4764
- { id: "axv_flavour_red_velvet", name: "Red Velvet" }
4765
- ];
4766
5449
  const sizeAxis = {
4767
- id: sizeAxisId,
4768
- business_id: businessId,
5450
+ id: "axis_cake_size",
5451
+ business_id: BUSINESS_ID,
4769
5452
  product_id: "prod_birthday-cake",
4770
5453
  name: "Size",
4771
5454
  display_order: 0,
4772
5455
  affects_recipe: false,
4773
- values: sizeValues.map((v, i) => ({
5456
+ values: [
5457
+ { id: "axv_size_small", name: 'Small (8")', display: 0 },
5458
+ { id: "axv_size_medium", name: 'Medium (10")', display: 1 },
5459
+ { id: "axv_size_large", name: 'Large (12")', display: 2 }
5460
+ ].map((v) => ({
4774
5461
  id: v.id,
4775
- business_id: businessId,
4776
- axis_id: sizeAxisId,
5462
+ business_id: BUSINESS_ID,
5463
+ axis_id: "axis_cake_size",
4777
5464
  name: v.name,
4778
- display_order: i,
5465
+ display_order: v.display,
4779
5466
  created_at: ISO,
4780
5467
  updated_at: ISO
4781
5468
  })),
@@ -4783,18 +5470,22 @@ function seedDefault(registry) {
4783
5470
  updated_at: ISO
4784
5471
  };
4785
5472
  const flavourAxis = {
4786
- id: flavourAxisId,
4787
- business_id: businessId,
5473
+ id: "axis_cake_flavour",
5474
+ business_id: BUSINESS_ID,
4788
5475
  product_id: "prod_birthday-cake",
4789
5476
  name: "Flavour",
4790
5477
  display_order: 1,
4791
5478
  affects_recipe: true,
4792
- values: flavourValues.map((v, i) => ({
5479
+ values: [
5480
+ { id: "axv_flavour_chocolate", name: "Dark Chocolate", display: 0 },
5481
+ { id: "axv_flavour_vanilla", name: "Vanilla Bean", display: 1 },
5482
+ { id: "axv_flavour_red_velvet", name: "Red Velvet", display: 2 }
5483
+ ].map((v) => ({
4793
5484
  id: v.id,
4794
- business_id: businessId,
4795
- axis_id: flavourAxisId,
5485
+ business_id: BUSINESS_ID,
5486
+ axis_id: "axis_cake_flavour",
4796
5487
  name: v.name,
4797
- display_order: i,
5488
+ display_order: v.display,
4798
5489
  created_at: ISO,
4799
5490
  updated_at: ISO
4800
5491
  })),
@@ -4803,40 +5494,42 @@ function seedDefault(registry) {
4803
5494
  };
4804
5495
  registry.variantAxes.put(sizeAxis.id, sizeAxis);
4805
5496
  registry.variantAxes.put(flavourAxis.id, flavourAxis);
4806
- const variantPrices = ["250.00", "350.00", "450.00"];
4807
- const variantIds = [];
4808
- let isFirst = true;
4809
- for (let s = 0; s < sizeValues.length; s++) {
4810
- for (let f = 0; f < flavourValues.length; f++) {
4811
- const id = `var_${sizeValues[s].id.replace("axv_", "")}_${flavourValues[f].id.replace("axv_", "")}`;
5497
+ const sizeAdj = { small: "250.00", medium: "350.00", large: "450.00" };
5498
+ const cakeVariantIds = [];
5499
+ let firstVariant = true;
5500
+ for (const sz of sizeAxis.values) {
5501
+ for (const fl of flavourAxis.values) {
5502
+ const sizeKey = sz.id.replace("axv_size_", "");
5503
+ const flavourKey = fl.id.replace("axv_flavour_", "");
5504
+ const id = `var_size_${sizeKey}_flavour_${flavourKey}`;
4812
5505
  const variant = {
4813
5506
  id,
4814
5507
  product_id: "prod_birthday-cake",
4815
- business_id: businessId,
4816
- name: `${sizeValues[s].name} \u2022 ${flavourValues[f].name}`,
4817
- sku: `BIRTHDAY-${sizeValues[s].id.split("_").pop()}-${flavourValues[f].id.split("_").pop()}`,
4818
- price_adjustment: variantPrices[s],
5508
+ business_id: BUSINESS_ID,
5509
+ name: `${sz.name} \u2022 ${fl.name}`,
5510
+ sku: `BIRTHDAY-${sizeKey.toUpperCase()}-${flavourKey.toUpperCase()}`,
5511
+ price_adjustment: sizeAdj[sizeKey] ?? "0.00",
4819
5512
  component_multiplier: "1.00",
4820
- is_default: isFirst,
5513
+ is_default: firstVariant,
4821
5514
  is_active: true,
4822
- axis_value_ids: [sizeValues[s].id, flavourValues[f].id],
5515
+ axis_value_ids: [sz.id, fl.id],
4823
5516
  created_at: ISO,
4824
5517
  updated_at: ISO
4825
5518
  };
4826
5519
  registry.variants.put(id, variant);
4827
- variantIds.push(id);
4828
- isFirst = false;
5520
+ cakeVariantIds.push(id);
5521
+ firstVariant = false;
4829
5522
  }
4830
5523
  }
4831
- const birthday = registry.products.get("prod_birthday-cake");
4832
- if (birthday) {
4833
- birthday.variant_ids = variantIds;
4834
- registry.products.put(birthday.id, birthday);
5524
+ const birthdayProduct = registry.products.get("prod_birthday-cake");
5525
+ if (birthdayProduct) {
5526
+ birthdayProduct.variant_ids = cakeVariantIds;
5527
+ registry.products.put(birthdayProduct.id, birthdayProduct);
4835
5528
  }
4836
5529
  const toppingsAddOn = {
4837
5530
  id: "addon_toppings",
4838
- business_id: businessId,
4839
- name: "Toppings",
5531
+ business_id: BUSINESS_ID,
5532
+ name: "Cake toppings",
4840
5533
  is_multiple_allowed: true,
4841
5534
  is_required: false,
4842
5535
  is_mutually_exclusive: false,
@@ -4845,15 +5538,14 @@ function seedDefault(registry) {
4845
5538
  created_at: ISO,
4846
5539
  updated_at: ISO,
4847
5540
  options: [
4848
- { 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 },
4849
- { 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 },
4850
- { 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 }
5541
+ { 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 },
5542
+ { 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 },
5543
+ { 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 }
4851
5544
  ]
4852
5545
  };
4853
- registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
4854
5546
  const milkAddOn = {
4855
5547
  id: "addon_milk",
4856
- business_id: businessId,
5548
+ business_id: BUSINESS_ID,
4857
5549
  name: "Milk",
4858
5550
  is_multiple_allowed: false,
4859
5551
  is_required: false,
@@ -4863,17 +5555,17 @@ function seedDefault(registry) {
4863
5555
  created_at: ISO,
4864
5556
  updated_at: ISO,
4865
5557
  options: [
4866
- { 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 },
4867
- { 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 },
4868
- { 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 }
5558
+ { 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 },
5559
+ { 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 },
5560
+ { 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 }
4869
5561
  ]
4870
5562
  };
5563
+ registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
4871
5564
  registry.addOns.put(milkAddOn.id, milkAddOn);
4872
5565
  const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
4873
5566
  const drinkProductIds = ["prod_coffee", "prod_tea"];
4874
5567
  for (const pid of cakeProductIds) {
4875
- const link = { product_id: pid, add_on_id: toppingsAddOn.id };
4876
- registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
5568
+ registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
4877
5569
  const p = registry.products.get(pid);
4878
5570
  if (p) {
4879
5571
  p.add_on_ids.push(toppingsAddOn.id);
@@ -4881,8 +5573,7 @@ function seedDefault(registry) {
4881
5573
  }
4882
5574
  }
4883
5575
  for (const pid of drinkProductIds) {
4884
- const link = { product_id: pid, add_on_id: milkAddOn.id };
4885
- registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
5576
+ registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
4886
5577
  const p = registry.products.get(pid);
4887
5578
  if (p) {
4888
5579
  p.add_on_ids.push(milkAddOn.id);
@@ -4891,7 +5582,7 @@ function seedDefault(registry) {
4891
5582
  }
4892
5583
  const breakfastBundle = {
4893
5584
  id: "bnd_breakfast_box",
4894
- business_id: businessId,
5585
+ business_id: BUSINESS_ID,
4895
5586
  product_id: "prod_breakfast-box",
4896
5587
  name: "Breakfast Box",
4897
5588
  slug: "breakfast-box",
@@ -4903,29 +5594,9 @@ function seedDefault(registry) {
4903
5594
  updated_at: ISO
4904
5595
  };
4905
5596
  registry.bundles.put(breakfastBundle.id, breakfastBundle);
4906
- const breakfastProduct = {
4907
- id: "prod_breakfast-box",
4908
- business_id: businessId,
4909
- name: "Breakfast Box",
4910
- slug: "breakfast-box",
4911
- description: "Bundled breakfast for one.",
4912
- product_type: "bundle",
4913
- base_price: "30.00",
4914
- currency: "GHS",
4915
- image: "https://mock.cimplify.io/img/breakfast-box.jpg",
4916
- images: ["https://mock.cimplify.io/img/breakfast-box.jpg"],
4917
- is_available: true,
4918
- category_ids: [categoryIdsBySlug.get("pastries")],
4919
- collection_ids: [],
4920
- add_on_ids: [],
4921
- variant_ids: [],
4922
- created_at: ISO,
4923
- updated_at: ISO
4924
- };
4925
- registry.products.put(breakfastProduct.id, breakfastProduct);
4926
5597
  const pastryComposite = {
4927
5598
  id: "comp_pastry_box",
4928
- business_id: businessId,
5599
+ business_id: BUSINESS_ID,
4929
5600
  product_id: "prod_pastry-box",
4930
5601
  base_price: "60.00",
4931
5602
  pricing_mode: "additive",
@@ -4937,43 +5608,51 @@ function seedDefault(registry) {
4937
5608
  name: "Choose your pastries",
4938
5609
  min: 6,
4939
5610
  max: 12,
4940
- component_ids: ["prod_croissant", "prod_pain-au-chocolat", "prod_almond-danish", "prod_cinnamon-roll", "prod_brownie"]
5611
+ component_ids: [
5612
+ "prod_croissant",
5613
+ "prod_pain-au-chocolat",
5614
+ "prod_almond-danish",
5615
+ "prod_cinnamon-roll",
5616
+ "prod_brownie",
5617
+ "prod_banana-bread",
5618
+ "prod_macaron-box"
5619
+ ]
4941
5620
  }
4942
5621
  ],
4943
5622
  created_at: ISO,
4944
5623
  updated_at: ISO
4945
5624
  };
4946
5625
  registry.composites.put(pastryComposite.id, pastryComposite);
4947
- const pastryBoxProduct = {
4948
- id: "prod_pastry-box",
4949
- business_id: businessId,
4950
- name: "Build-Your-Own Pastry Box",
4951
- slug: "pastry-box",
4952
- description: "Pick 6 to 12 pastries \u2014 mix and match.",
4953
- product_type: "composite",
4954
- base_price: "60.00",
4955
- currency: "GHS",
4956
- image: "https://mock.cimplify.io/img/pastry-box.jpg",
4957
- images: ["https://mock.cimplify.io/img/pastry-box.jpg"],
4958
- is_available: true,
4959
- category_ids: [categoryIdsBySlug.get("pastries")],
4960
- collection_ids: [],
4961
- add_on_ids: [],
4962
- variant_ids: [],
4963
- created_at: ISO,
4964
- updated_at: ISO
5626
+ const tasting = {
5627
+ id: "svc_tasting",
5628
+ business_id: BUSINESS_ID,
5629
+ name: "Bakery Tasting Session",
5630
+ description: "Sample our pastry menu with our head baker.",
5631
+ duration_minutes: 60,
5632
+ price: "150.00",
5633
+ is_available: true
5634
+ };
5635
+ const consult = {
5636
+ id: "svc_custom_cake",
5637
+ business_id: BUSINESS_ID,
5638
+ name: "Custom Cake Consultation",
5639
+ description: "Design a custom cake for your event.",
5640
+ duration_minutes: 30,
5641
+ price: "0.00",
5642
+ is_available: true
4965
5643
  };
4966
- registry.products.put(pastryBoxProduct.id, pastryBoxProduct);
5644
+ registry.services.put(tasting.id, tasting);
5645
+ registry.services.put(consult.id, consult);
4967
5646
  const tags = [
4968
- { id: "tag_signature", business_id: businessId, name: "Signature", slug: "signature", color: "#d97706", sort_order: 0, usage_count: 5, created_at: ISO, updated_at: ISO },
4969
- { id: "tag_vegan", business_id: businessId, name: "Vegan", slug: "vegan", color: "#16a34a", sort_order: 1, usage_count: 3, created_at: ISO, updated_at: ISO },
4970
- { 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 },
4971
- { id: "tag_seasonal", business_id: businessId, name: "Seasonal", slug: "seasonal", color: "#dc2626", sort_order: 3, usage_count: 4, created_at: ISO, updated_at: ISO }
5647
+ { 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 },
5648
+ { 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 },
5649
+ { 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 },
5650
+ { 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 }
4972
5651
  ];
4973
5652
  for (const t of tags) registry.tags.put(t.id, t);
4974
5653
  const baked = {
4975
5654
  id: "tax_baked_goods",
4976
- business_id: businessId,
5655
+ business_id: BUSINESS_ID,
4977
5656
  name: "Baked Goods",
4978
5657
  slug: "baked-goods",
4979
5658
  path: ["Food", "Baked Goods"],
@@ -4983,7 +5662,7 @@ function seedDefault(registry) {
4983
5662
  };
4984
5663
  const beverages = {
4985
5664
  id: "tax_beverages",
4986
- business_id: businessId,
5665
+ business_id: BUSINESS_ID,
4987
5666
  name: "Beverages",
4988
5667
  slug: "beverages",
4989
5668
  path: ["Food", "Beverages"],
@@ -4996,13 +5675,13 @@ function seedDefault(registry) {
4996
5675
  const attrs = [
4997
5676
  {
4998
5677
  id: "attr_allergens",
4999
- business_id: businessId,
5678
+ business_id: BUSINESS_ID,
5000
5679
  namespace: "core",
5001
5680
  name: "Allergens",
5002
5681
  slug: "allergens",
5003
5682
  description: "Common allergens present in this item.",
5004
5683
  attribute_type: "multi_select",
5005
- options: ["wheat", "eggs", "dairy", "nuts", "soy"],
5684
+ options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
5006
5685
  is_required: false,
5007
5686
  is_filterable: true,
5008
5687
  visibility: "public",
@@ -5013,7 +5692,7 @@ function seedDefault(registry) {
5013
5692
  },
5014
5693
  {
5015
5694
  id: "attr_shelf_life",
5016
- business_id: businessId,
5695
+ business_id: BUSINESS_ID,
5017
5696
  namespace: "core",
5018
5697
  name: "Shelf life",
5019
5698
  slug: "shelf-life",
@@ -5028,7 +5707,7 @@ function seedDefault(registry) {
5028
5707
  },
5029
5708
  {
5030
5709
  id: "attr_caffeine",
5031
- business_id: businessId,
5710
+ business_id: BUSINESS_ID,
5032
5711
  namespace: "core",
5033
5712
  name: "Caffeine",
5034
5713
  slug: "caffeine",
@@ -5043,7 +5722,7 @@ function seedDefault(registry) {
5043
5722
  },
5044
5723
  {
5045
5724
  id: "attr_seasonal",
5046
- business_id: businessId,
5725
+ business_id: BUSINESS_ID,
5047
5726
  namespace: "core",
5048
5727
  name: "Seasonal",
5049
5728
  slug: "seasonal",
@@ -5061,10 +5740,10 @@ function seedDefault(registry) {
5061
5740
  const articles = [
5062
5741
  {
5063
5742
  id: "kb_delivery",
5064
- business_id: businessId,
5065
- title: "Delivery & Pickup",
5743
+ business_id: BUSINESS_ID,
5744
+ title: "Delivery & pickup",
5066
5745
  slug: "delivery-pickup",
5067
- content: "Same-day delivery within Accra for orders placed before 2pm. Pickup available 8am\u20138pm.",
5746
+ content: "Same-day delivery within Greater Accra for orders placed before 2pm. Pickup available 8am\u20138pm at our East Legon location.",
5068
5747
  category: "logistics",
5069
5748
  tags: ["delivery", "pickup"],
5070
5749
  created_at: ISO,
@@ -5072,10 +5751,10 @@ function seedDefault(registry) {
5072
5751
  },
5073
5752
  {
5074
5753
  id: "kb_custom_cakes",
5075
- business_id: businessId,
5076
- title: "Ordering custom cakes",
5754
+ business_id: BUSINESS_ID,
5755
+ title: "Custom cakes & celebrations",
5077
5756
  slug: "custom-cakes",
5078
- content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page.",
5757
+ content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page to walk through flavours, sizes, and decoration.",
5079
5758
  category: "products",
5080
5759
  tags: ["cakes", "custom"],
5081
5760
  created_at: ISO,
@@ -5083,10 +5762,10 @@ function seedDefault(registry) {
5083
5762
  },
5084
5763
  {
5085
5764
  id: "kb_allergens",
5086
- business_id: businessId,
5087
- title: "Allergens",
5765
+ business_id: BUSINESS_ID,
5766
+ title: "Allergen information",
5088
5767
  slug: "allergens",
5089
- content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible.",
5768
+ content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible \u2014 let us know about severe allergies before ordering.",
5090
5769
  category: "products",
5091
5770
  tags: ["allergens", "safety"],
5092
5771
  created_at: ISO,
@@ -5094,7 +5773,7 @@ function seedDefault(registry) {
5094
5773
  }
5095
5774
  ];
5096
5775
  for (const a of articles) registry.knowledgeArticles.put(a.id, a);
5097
- return { businessId };
5776
+ return { businessId: BUSINESS_ID };
5098
5777
  }
5099
5778
 
5100
5779
  // src/mock/seeds/empty.ts
@@ -5140,7 +5819,7 @@ function seedReesa(registry) {
5140
5819
  default_address: "5 Allen Avenue, Ikeja, Lagos",
5141
5820
  default_offers_table_service: false,
5142
5821
  default_accepts_online_orders: true,
5143
- image: "https://mock.cimplify.io/img/reesa-logo.png",
5822
+ image: "/img/reesa-logo.png",
5144
5823
  status: "active",
5145
5824
  created_at: ISO3,
5146
5825
  updated_at: ISO3,
@@ -5196,8 +5875,8 @@ function seedReesa(registry) {
5196
5875
  product_type: "product",
5197
5876
  base_price: p.price,
5198
5877
  currency: "NGN",
5199
- image: `https://mock.cimplify.io/img/reesa/${p.slug}.jpg`,
5200
- images: [`https://mock.cimplify.io/img/reesa/${p.slug}.jpg`],
5878
+ image: `/img/reesa/${p.slug}.jpg`,
5879
+ images: [`/img/reesa/${p.slug}.jpg`],
5201
5880
  is_available: true,
5202
5881
  category_ids: [catId],
5203
5882
  collection_ids: [],
@@ -5420,8 +6099,8 @@ function seedReesa(registry) {
5420
6099
  product_type: "bundle",
5421
6100
  base_price: "55000.00",
5422
6101
  currency: "NGN",
5423
- image: "https://mock.cimplify.io/img/reesa/owambe.jpg",
5424
- images: ["https://mock.cimplify.io/img/reesa/owambe.jpg"],
6102
+ image: "/img/reesa/owambe.jpg",
6103
+ images: ["/img/reesa/owambe.jpg"],
5425
6104
  is_available: true,
5426
6105
  category_ids: [catIdsBySlug.get("apparel")],
5427
6106
  collection_ids: [],
@@ -5552,7 +6231,7 @@ function seedRestaurant(registry) {
5552
6231
  default_address: "8 Ring Road, East Legon, Accra",
5553
6232
  default_offers_table_service: true,
5554
6233
  default_accepts_online_orders: true,
5555
- image: "https://mock.cimplify.io/img/mamas-logo.png",
6234
+ image: "/img/mamas-logo.png",
5556
6235
  status: "active",
5557
6236
  created_at: ISO4,
5558
6237
  updated_at: ISO4,
@@ -5623,8 +6302,8 @@ function seedRestaurant(registry) {
5623
6302
  product_type: "product",
5624
6303
  base_price: p.price,
5625
6304
  currency: "GHS",
5626
- image: `https://mock.cimplify.io/img/mamas/${p.slug}.jpg`,
5627
- images: [`https://mock.cimplify.io/img/mamas/${p.slug}.jpg`],
6305
+ image: `/img/mamas/${p.slug}.jpg`,
6306
+ images: [`/img/mamas/${p.slug}.jpg`],
5628
6307
  is_available: true,
5629
6308
  category_ids: [catId],
5630
6309
  collection_ids: [],
@@ -5862,8 +6541,8 @@ function seedRestaurant(registry) {
5862
6541
  product_type: "bundle",
5863
6542
  base_price: "180.00",
5864
6543
  currency: "GHS",
5865
- image: "https://mock.cimplify.io/img/mamas/feast.jpg",
5866
- images: ["https://mock.cimplify.io/img/mamas/feast.jpg"],
6544
+ image: "/img/mamas/feast.jpg",
6545
+ images: ["/img/mamas/feast.jpg"],
5867
6546
  is_available: true,
5868
6547
  category_ids: [catIds.get("mains")],
5869
6548
  collection_ids: [],
@@ -5898,8 +6577,8 @@ function seedRestaurant(registry) {
5898
6577
  product_type: "composite",
5899
6578
  base_price: "0.00",
5900
6579
  currency: "GHS",
5901
- image: "https://mock.cimplify.io/img/mamas/byo.jpg",
5902
- images: ["https://mock.cimplify.io/img/mamas/byo.jpg"],
6580
+ image: "/img/mamas/byo.jpg",
6581
+ images: ["/img/mamas/byo.jpg"],
5903
6582
  is_available: true,
5904
6583
  category_ids: [catIds.get("mains")],
5905
6584
  collection_ids: [],
@@ -6011,7 +6690,7 @@ function seedRetail(registry) {
6011
6690
  default_address: "Atomic Junction, Accra",
6012
6691
  default_offers_table_service: false,
6013
6692
  default_accepts_online_orders: true,
6014
- image: "https://mock.cimplify.io/img/currents-logo.png",
6693
+ image: "/img/currents-logo.png",
6015
6694
  status: "active",
6016
6695
  created_at: ISO5,
6017
6696
  updated_at: ISO5,
@@ -6081,8 +6760,8 @@ function seedRetail(registry) {
6081
6760
  product_type: "product",
6082
6761
  base_price: p.price,
6083
6762
  currency: "GHS",
6084
- image: `https://mock.cimplify.io/img/currents/${p.slug}.jpg`,
6085
- images: [`https://mock.cimplify.io/img/currents/${p.slug}.jpg`],
6763
+ image: `/img/currents/${p.slug}.jpg`,
6764
+ images: [`/img/currents/${p.slug}.jpg`],
6086
6765
  is_available: true,
6087
6766
  category_ids: [catId],
6088
6767
  collection_ids: [],
@@ -6345,8 +7024,8 @@ function seedRetail(registry) {
6345
7024
  product_type: "bundle",
6346
7025
  base_price: "11910.00",
6347
7026
  currency: "GHS",
6348
- image: "https://mock.cimplify.io/img/currents/wfh.jpg",
6349
- images: ["https://mock.cimplify.io/img/currents/wfh.jpg"],
7027
+ image: "/img/currents/wfh.jpg",
7028
+ images: ["/img/currents/wfh.jpg"],
6350
7029
  is_available: true,
6351
7030
  category_ids: [catIds.get("laptops")],
6352
7031
  collection_ids: [],
@@ -6379,8 +7058,8 @@ function seedRetail(registry) {
6379
7058
  product_type: "bundle",
6380
7059
  base_price: "4200.00",
6381
7060
  currency: "GHS",
6382
- image: "https://mock.cimplify.io/img/currents/game-pack.jpg",
6383
- images: ["https://mock.cimplify.io/img/currents/game-pack.jpg"],
7061
+ image: "/img/currents/game-pack.jpg",
7062
+ images: ["/img/currents/game-pack.jpg"],
6384
7063
  is_available: true,
6385
7064
  category_ids: [catIds.get("gaming")],
6386
7065
  collection_ids: [],
@@ -6483,7 +7162,7 @@ function seedServices(registry) {
6483
7162
  default_address: "12 Cantonments Road, Accra",
6484
7163
  default_offers_table_service: false,
6485
7164
  default_accepts_online_orders: true,
6486
- image: "https://mock.cimplify.io/img/serene-logo.png",
7165
+ image: "/img/serene-logo.png",
6487
7166
  status: "active",
6488
7167
  created_at: ISO6,
6489
7168
  updated_at: ISO6,
@@ -6551,8 +7230,8 @@ function seedServices(registry) {
6551
7230
  product_type: "service",
6552
7231
  base_price: s.price,
6553
7232
  currency: "GHS",
6554
- image: `https://mock.cimplify.io/img/serene/${s.slug}.jpg`,
6555
- images: [`https://mock.cimplify.io/img/serene/${s.slug}.jpg`],
7233
+ image: `/img/serene/${s.slug}.jpg`,
7234
+ images: [`/img/serene/${s.slug}.jpg`],
6556
7235
  is_available: true,
6557
7236
  category_ids: [catId],
6558
7237
  collection_ids: [],
@@ -6688,8 +7367,8 @@ function seedServices(registry) {
6688
7367
  product_type: "bundle",
6689
7368
  base_price: "480.00",
6690
7369
  currency: "GHS",
6691
- image: "https://mock.cimplify.io/img/serene/spa-day.jpg",
6692
- images: ["https://mock.cimplify.io/img/serene/spa-day.jpg"],
7370
+ image: "/img/serene/spa-day.jpg",
7371
+ images: ["/img/serene/spa-day.jpg"],
6693
7372
  is_available: true,
6694
7373
  category_ids: [catIds.get("wellness")],
6695
7374
  collection_ids: [],
@@ -6722,8 +7401,8 @@ function seedServices(registry) {
6722
7401
  product_type: "bundle",
6723
7402
  base_price: "850.50",
6724
7403
  currency: "GHS",
6725
- image: "https://mock.cimplify.io/img/serene/couples.jpg",
6726
- images: ["https://mock.cimplify.io/img/serene/couples.jpg"],
7404
+ image: "/img/serene/couples.jpg",
7405
+ images: ["/img/serene/couples.jpg"],
6727
7406
  is_available: true,
6728
7407
  category_ids: [catIds.get("wellness")],
6729
7408
  collection_ids: [],
@@ -6758,8 +7437,8 @@ function seedServices(registry) {
6758
7437
  product_type: "composite",
6759
7438
  base_price: "0.00",
6760
7439
  currency: "GHS",
6761
- image: "https://mock.cimplify.io/img/serene/custom.jpg",
6762
- images: ["https://mock.cimplify.io/img/serene/custom.jpg"],
7440
+ image: "/img/serene/custom.jpg",
7441
+ images: ["/img/serene/custom.jpg"],
6763
7442
  is_available: true,
6764
7443
  category_ids: [catIds.get("wellness")],
6765
7444
  collection_ids: [],
@@ -6862,7 +7541,7 @@ function seedGrocery(registry) {
6862
7541
  default_address: "Spintex Road, Accra",
6863
7542
  default_offers_table_service: false,
6864
7543
  default_accepts_online_orders: true,
6865
- image: "https://mock.cimplify.io/img/freshmart-logo.png",
7544
+ image: "/img/freshmart-logo.png",
6866
7545
  status: "active",
6867
7546
  created_at: ISO7,
6868
7547
  updated_at: ISO7,
@@ -6937,8 +7616,8 @@ function seedGrocery(registry) {
6937
7616
  product_type: "product",
6938
7617
  base_price: p.price,
6939
7618
  currency: "GHS",
6940
- image: `https://mock.cimplify.io/img/freshmart/${p.slug}.jpg`,
6941
- images: [`https://mock.cimplify.io/img/freshmart/${p.slug}.jpg`],
7619
+ image: `/img/freshmart/${p.slug}.jpg`,
7620
+ images: [`/img/freshmart/${p.slug}.jpg`],
6942
7621
  is_available: true,
6943
7622
  category_ids: [catId],
6944
7623
  collection_ids: [],
@@ -7021,8 +7700,8 @@ function seedGrocery(registry) {
7021
7700
  product_type: "bundle",
7022
7701
  base_price: "199.50",
7023
7702
  currency: "GHS",
7024
- image: "https://mock.cimplify.io/img/freshmart/essentials.jpg",
7025
- images: ["https://mock.cimplify.io/img/freshmart/essentials.jpg"],
7703
+ image: "/img/freshmart/essentials.jpg",
7704
+ images: ["/img/freshmart/essentials.jpg"],
7026
7705
  is_available: true,
7027
7706
  category_ids: [catIds.get("pantry")],
7028
7707
  collection_ids: [],
@@ -7197,43 +7876,47 @@ function createDeps(options = {}) {
7197
7876
  }
7198
7877
 
7199
7878
  // src/mock/server.ts
7879
+ var PACKAGE_NAME = "@cimplify/sdk/mock";
7880
+ function corsMiddleware(allow) {
7881
+ return async (c, next) => {
7882
+ const origin = c.req.header("origin") ?? "";
7883
+ const allowOrigin = allow === "*" ? "*" : allow.includes(origin) ? origin : allow[0] ?? "*";
7884
+ const headers = {
7885
+ "Access-Control-Allow-Origin": allowOrigin,
7886
+ "Access-Control-Allow-Credentials": "true",
7887
+ "Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
7888
+ "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
7889
+ "Access-Control-Expose-Headers": "x-session-token",
7890
+ "Access-Control-Max-Age": "86400",
7891
+ Vary: "Origin"
7892
+ };
7893
+ if (c.req.method === "OPTIONS") {
7894
+ return new Response(null, { status: 204, headers });
7895
+ }
7896
+ for (const [k, v] of Object.entries(headers)) c.header(k, v);
7897
+ await next();
7898
+ };
7899
+ }
7200
7900
  function createMockApp(options = {}) {
7201
7901
  const deps = createDeps(options);
7202
7902
  const app = new Hono2();
7203
- const corsOrigins = options.cors === "*" ? "*" : options.cors;
7204
- if (corsOrigins) {
7205
- app.use("*", async (c, next) => {
7206
- const origin = c.req.header("origin") ?? "";
7207
- const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
7208
- const headers = {
7209
- "Access-Control-Allow-Origin": allow,
7210
- "Access-Control-Allow-Credentials": "true",
7211
- "Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
7212
- "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
7213
- "Access-Control-Expose-Headers": "x-session-token",
7214
- "Access-Control-Max-Age": "86400",
7215
- Vary: "Origin"
7216
- };
7217
- if (c.req.method === "OPTIONS") {
7218
- return new Response(null, { status: 204, headers });
7219
- }
7220
- for (const [k, v] of Object.entries(headers)) c.header(k, v);
7221
- await next();
7222
- });
7223
- }
7903
+ const cors = options.cors ?? "*";
7904
+ if (cors) app.use("*", corsMiddleware(cors));
7224
7905
  app.get(
7225
7906
  "/",
7226
7907
  (c) => c.json({
7227
- name: "@cimplify/sdk/mock",
7228
- version: "0.36.0",
7908
+ name: PACKAGE_NAME,
7229
7909
  docs: "https://github.com/cimplify/cimplify",
7230
7910
  api: "/api/v1",
7231
7911
  admin: "/_mock",
7232
7912
  elements: "/elements",
7233
- seed_business_id: deps.defaultBusinessId
7913
+ images: "/img/<slug>",
7914
+ seed_business_id: deps.defaultBusinessId,
7915
+ time: deps.clock.iso()
7234
7916
  })
7235
7917
  );
7236
7918
  app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
7919
+ mountImageRoutes(app);
7237
7920
  app.route("/api/v1", buildV1(deps));
7238
7921
  app.route("/_mock", buildAdmin(deps));
7239
7922
  app.route("/elements", buildElements(deps));
@@ -7241,7 +7924,11 @@ function createMockApp(options = {}) {
7241
7924
  (c) => c.json(
7242
7925
  {
7243
7926
  success: false,
7244
- error: { code: "ROUTE_NOT_REGISTERED", message: `Route ${c.req.method} ${c.req.path} not handled by mock`, retryable: false },
7927
+ error: {
7928
+ code: "ROUTE_NOT_REGISTERED",
7929
+ message: `Route ${c.req.method} ${c.req.path} not handled by mock`,
7930
+ retryable: false
7931
+ },
7245
7932
  hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
7246
7933
  },
7247
7934
  501