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