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